Modo più veloce per wp_insert_post e add_post_meta in blocco


16

Ho un file CSV che voglio inserire composto da ~ 1.500 righe e 97 colonne. Sono necessarie circa 2-3 ore per eseguire un'importazione completa e mi piacerebbe migliorarlo se esiste un modo. Attualmente per ogni riga sto facendo un $ post_id = wp_insert_post e poi un add_post_meta per le 97 colonne associate con ogni riga. Questo è abbastanza inefficiente ...

C'è un modo migliore per farlo in modo che un post_id possa mantenere la relazione tra post e i suoi valori post_meta?

In questo momento sto provando questo sul mio computer locale con wamp ma lo avrò eseguito su un VPS


Oltre ai suggerimenti WP di seguito, osserva anche l'utilizzo di InnoDB in MySQL e il commit delle transazioni in batch, per questa risposta .
webaware,

Risposte:


21

Qualche tempo fa ho avuto problemi simili con un'importazione CSV personalizzata, ma alla fine ho usato un codice SQL personalizzato per l'inserimento di massa. Ma non avevo ancora visto questa risposta:

Ottimizzare l'inserimento e l'eliminazione di post per operazioni in blocco?

da utilizzare wp_defer_term_counting()per abilitare o disabilitare il conteggio dei termini.

Inoltre, se dai un'occhiata alla fonte per il plug-in importatore di WordPress, vedrai queste funzioni appena prima dell'importazione di massa:

wp_defer_term_counting( true );
wp_defer_comment_counting( true );

e quindi dopo l'inserimento di massa:

wp_defer_term_counting( false );
wp_defer_comment_counting( false );

Quindi questo potrebbe essere qualcosa da provare ;-)

L'importazione di post come bozza invece di pubblicazione accelererà anche le cose, poiché il lento processo di ricerca di una lumaca unica per ognuno viene saltato. Si potrebbe, ad esempio, pubblicarli in seguito in passaggi più piccoli, ma si noti che questo tipo di approccio dovrebbe in qualche modo contrassegnare i post importati, quindi non pubblicheremo solo le bozze in un secondo momento! Ciò richiederebbe un'attenta pianificazione e molto probabilmente una codifica personalizzata.

Se ci sono ad esempio molti titoli di post simili (gli stessi post_name) da importare, allora wp_unique_post_slug()può rallentare, a causa dell'iterazione della query loop per trovare una lumaca disponibile. Questo può eventualmente generare un numero enorme di query db.

A partire da WordPress 5.1 il pre_wp_unique_post_slugfiltro è disponibile per evitare l'iterazione di loop per la lumaca. Vedi il core ticket # 21112 . Ecco un esempio:

add_filter( 'pre_wp_unique_post_slug', 
    function( $override_slug, $slug, $post_id, $post_status, $post_type, $post_parent ) {
        // Set a unique slug value to shortcircuit the slug iteration loop.
        // $override_slug = ...

        return $override_slug;
    }, 10, 6
);

Se uno prova ad es. $override_slug = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix"Con $suffixas $post_id, notiamo che $post_idè sempre 0per i nuovi post, come previsto. Ci sono vari modi per generare numeri univoci in PHP, come uniqid( '', true ). Ma usa questo filtro con cura per assicurarti di avere lumache uniche. Potremmo ad esempio eseguire una query sul conteggio dei gruppi in seguito post_nameper essere sicuri.

Un'altra opzione sarebbe quella di utilizzare WP-CLI per evitare il timeout. Vedi ad esempio la mia risposta pubblicata per la creazione di 20.000 post o pagine utilizzando un file .csv?

Quindi possiamo eseguire il nostro script di importazione PHP personalizzato import.phpcon il comando WP-CLI:

wp eval-file import.php

Evita anche di importare un gran numero di tipi di post gerarchici, poiché l'attuale interfaccia utente di wp-admin non la gestisce bene. Vedi ad esempio il tipo di post personalizzato - elenco post - schermo bianco della morte

Ecco il grande consiglio di @otto:

Prima di inserimenti in blocco , disabilitare autocommitesplicitamente la modalità:

$wpdb->query( 'SET autocommit = 0;' );

Dopo gli inserti di massa, eseguire:

$wpdb->query( 'COMMIT;' );

Penso anche che sarebbe una buona idea fare delle pulizie come:

$wpdb->query( 'SET autocommit = 1;' );

Non l'ho provato su MyISAM ma questo dovrebbe funzionare su InnoDB .

Come accennato da @kovshenin questo suggerimento non funzionerebbe per MyISAM .


6
Inoltre, è anche possibile utilizzare la funzione di query per disattivare in precedenza autocommit e quindi eseguire il commit manuale dopo aver effettuato gli inserimenti. Ciò accelera notevolmente le operazioni a livello di DB quando si eseguono inserimenti di massa. Basta inviare un SET autocommit=0;prima degli inserti, seguito da un COMMIT;successivo.
Otto,

Interessante, grazie per quello! Dovrò provarlo quando torno a casa.
Corey Rowell,

@Otto, grazie per l'ottimo consiglio. Quindi potremmo fare $wpdb->query('SET autocommit = 0;');prima degli inserti ma possiamo saltare $wpdb->query('START TRANSACTION;');in quel caso? Controllerò il manuale di MySQL per saperne di più ;-) evviva.
birgire,

1
Buon punto Mark. Se si tratta solo di inserimenti e non di aggiornamenti, è consigliabile wp_suspend_cache_addition( true )NON inserire elementi nella cache degli oggetti. Inoltre @birgire ha affermato di non averlo verificato con MyISAM - non preoccuparti, il motore di archiviazione non supporta le transazioni, quindi l'impostazione dell'autocommit o l'avvio di una transazione non avrà alcun effetto.
kovshenin,

1
ottimo consiglio @Otto. La mia query in precedenza ha impiegato 38 secondi, ora ci vuole 1 secondo.
Annapurna,

5

Dovrai inserire il post per ottenere il tuo ID ma la $wpdb->postmetatabella è molto semplice nella struttura. Probabilmente potresti usare INSERT INTOun'istruzione semplice , come questa dai documenti di MySQL:INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);

Nel tuo caso...

$ID = 1; // from your wp_insert_post
$values = '($ID,2,3),($ID,5,6),($ID,8,9)'; // build from your 97 columns; I'd use a loop of some kind
$wpdb->query("INSERT INTO {$wpdb->postmeta} (post_id,meta_key,meta_value) VALUES {$values}");

Ciò non affronterà alcuna codifica, serializzazione, escape, controllo degli errori, duplicazioni o qualsiasi altra cosa, ma mi aspetto che sia più veloce (anche se non ci ho provato).

Non lo farei su un sito di produzione senza test approfonditi, e se dovessi farlo solo una o due volte, userei le funzioni principali e pranzerei a lungo mentre le cose venivano importate.


Penso che prenderò un lungo pranzo, piuttosto che non inserire dati grezzi nelle mie tabelle e non ha senso riscrivere ciò che Wordpress farà già.
Corey Rowell,

1
ecco come avviene l'iniezione mysql, quindi per favore non usarla.
OneOfOne

Tutto è hard-coded, @OneOfOne. L'iniezione non può - per definizione - non può avvenire senza l'input fornito dall'utente. Questa è la natura dell '"iniezione". L'OP sta importando dati da un file .csv che è sotto il suo controllo usando il codice sotto il suo controllo. Non vi è alcuna opportunità per una terza parte di iniettare qualcosa. Si prega di prestare attenzione al contesto.
s_ha_dum,

+1 da me, dovevo aggiungere 20 valori di campi doganali e questo era molto più veloce di "add_post_meta"
Zorox,

1
Non puoi aspettarti che l'OP controlli accuratamente il file CSV prima di importarlo, quindi dovresti trattarlo come input dell'utente e almeno le ->prepare()tue istruzioni SQL. Nel tuo scenario, cosa accadrebbe se la colonna ID nel CSV contenesse qualcosa del genere 1, 'foo', 'bar'); DROP TABLE wp_users; --? Qualcosa di brutto probabilmente.
kovshenin,

5

Ho dovuto aggiungere questo:

    remove_action('do_pings', 'do_all_pings', 10, 1);

Tieni presente che questo salterà do_all_pings, che elabora pingback, enclosure, trackback e altri ping (link: https://developer.wordpress.org/reference/functions/do_all_pings/ ). La mia comprensione dall'esame del codice è che i pingback / trackback / enclosure in sospeso verranno comunque elaborati dopo aver rimosso questa remove_actionriga, ma non sono del tutto sicuro.

Aggiornamento: ho anche aggiunto

    define( 'WP_IMPORTING', true );

Oltre a ciò che sto usando:

    ini_set("memory_limit",-1);
    set_time_limit(0);
    ignore_user_abort(true);

    wp_defer_term_counting( true );
    wp_defer_comment_counting( true );
    $wpdb->query( 'SET autocommit = 0;' );

    /* Inserting 100,000 posts at a time
       including assigning a taxonomy term and adding meta keys
       (i.e. a `foreach` loop with each loop containing:
       `wp_insert_post`, `wp_set_object_terms`, `add_post_meta`.)
    */

    $wpdb->query( 'COMMIT;' );
    wp_defer_term_counting( false );
    wp_defer_comment_counting( false );

1

Nota importante su 'SET autocommit = 0;'

dopo aver impostato autocommit = 0se lo script interrompe l'esecuzione (per qualche motivo, ad esempio exit, errore irreversibile o ecc ...), le modifiche NON SARANNO SALVATE IN DB!

$wpdb->query( 'SET autocommit = 0;' );

update_option("something", "value");     

exit; //lets say, here happens error or anything...

$wpdb->query( 'COMMIT;' );

In questo caso update_optionnon verrà salvato nel DB!

Quindi, il miglior consiglio è di essersi COMMITregistrati in shutdownfunzione come precatuione (nel caso in cui si verifichi un'uscita imprevista).

register_shutdown_function( function(){
    $GLOBALS['wpdb']->query( 'COMMIT;' );
} );
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.