Metodă mai rapidă pentru wp_insert_post și add_post_meta în masă

8 iun. 2013, 19:37:53
Vizualizări: 16.2K
Voturi: 21

Am un fișier CSV pe care vreau să îl import, care conține aproximativ 1.500 de rânduri și 97 de coloane. În prezent, un import complet durează între 2-3 ore și aș dori să îmbunătățesc acest proces dacă există o posibilitate. Momentan, pentru fiecare rând fac un $post_id = wp_insert_post și apoi un add_post_meta pentru cele 97 de coloane asociate fiecărui rând. Acest proces este destul de ineficient...

Există o metodă mai bună de a aborda această situație, astfel încât să pot obține un post_id și să păstrez relația dintre post și valorile sale post_meta?

În prezent testez acest proces pe mașina mea locală cu WAMP, dar ulterior va rula pe un VPS.

1
Comentarii

Pe lângă sfaturile WP de mai jos, ia în considerare și utilizarea InnoDB în MySQL și comiterea tranzacțiilor în loturi, conform acestui răspuns.

webaware webaware
9 iun. 2013 01:20:00
Toate răspunsurile la întrebare 4
14
32

Am avut probleme similare acum ceva timp cu un import CSV personalizat, dar am rezolvat folosind niște interogări SQL personalizate pentru inserarea în masă. Dar nu văzusem acest răspuns pe atunci:

Optimizarea inserării și ștergerii postărilor pentru operațiuni în masă?

să folosești wp_defer_term_counting() pentru a activa sau dezactiva numărarea termenilor.

De asemenea, dacă verifici sursa plugin-ului WordPress importer, vei vedea aceste funcții chiar înainte de importul în masă:

wp_defer_term_counting( true );
wp_defer_comment_counting( true );

și apoi după inserarea în masă:

wp_defer_term_counting( false );
wp_defer_comment_counting( false );

Așadar, acesta ar putea fi ceva de încercat ;-)

Importarea postărilor ca draft în loc de publish va accelera și ea procesul, deoarece este omis procesul lent de găsire a unui slug unic pentru fiecare. Se poate, de exemplu, să le publicăm mai târziu în pași mai mici, dar reține că acest tip de abordare ar necesita marcarea postărilor importate într-un fel, ca să nu publicăm orice draft-uri mai târziu! Acest lucru ar necesita o planificare atentă și cel mai probabil niște codare personalizată.

Dacă există, de exemplu, multe titluri de postări similare (același post_name) de importat, atunci wp_unique_post_slug() poate deveni lent, datorită iterației prin interogări pentru a găsi un slug disponibil. Acest lucru poate genera un număr enorm de interogări la baza de date.

Începând cu WordPress 5.1, filtrul pre_wp_unique_post_slug este disponibil pentru a evita iterația pentru slug. Vezi ticket-ul de core #21112. Iată un exemplu:

add_filter( 'pre_wp_unique_post_slug', 
    function( $override_slug, $slug, $post_id, $post_status, $post_type, $post_parent ) {
        // Setează o valoare unică pentru slug pentru a scurcircuita bucla de iterație.
        // $override_slug = ...

        return $override_slug;
    }, 10, 6
);

Dacă încerci, de exemplu, $override_slug = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix" cu $suffix ca $post_id, atunci vom observa că $post_id este întotdeauna 0 pentru postările noi, așa cum e de așteptat. Există însă diverse modalități de a genera numere unice în PHP, cum ar fi uniqid( '', true ). Dar folosește acest filtru cu grijă pentru a te asigura că ai slug-uri unice. Am putea, de exemplu, să rulăm o interogare de numărare după grup pe post_name pentru a fi siguri.

O altă opțiune ar fi să folosești WP-CLI pentru a evita timeout-ul. Vezi, de exemplu, răspunsul meu postat pentru Crearea a 20.000 de Postări sau Pagini folosind un fișier .csv?

Apoi putem rula script-ul PHP personalizat de import import.php cu comanda WP-CLI:

wp eval-file import.php

De asemenea, evită importul unui număr mare de tipuri de postări ierarhice, deoarece interfața actuală wp-admin nu le gestionează bine. Vezi, de exemplu, Custom post type - listă de postări - ecran alb al morții

Iată sfatul minunat de la @otto:

Înainte de inserări în masă, dezactivează modul autocommit explicit:

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

După inserările în masă, rulează:

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

De asemenea, cred că ar fi o idee bună să faci niște curățenie precum:

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

Nu am testat acest lucru pe MyISAM, dar ar trebui să funcționeze pe InnoDB.

După cum a menționat @kovshenin, acest sfat nu ar funcționa pentru MyISAM.

8 iun. 2013 22:36:03
Comentarii

Pe lângă aceasta, poți folosi și funcția de interogare pentru a dezactiva autocommit înainte, iar apoi să faci commit manual după ce inserțiile au fost efectuate. Acest lucru accelerează semnificativ operațiunile la nivel de bază de date atunci când faci inserții în masă. Pur și simplu trimite un SET autocommit=0; înaintea inserțiilor, urmat de un COMMIT; după aceea.

Otto Otto
8 iun. 2013 23:27:00

Interesant, mulțumesc pentru asta! Va trebui să testez când ajung acasă.

Corey Rowell Corey Rowell
8 iun. 2013 23:31:19

@Otto, mulțumesc pentru pontul excelent. Deci am putea face $wpdb->query('SET autocommit = 0;'); înaintea inserțiilor, dar putem sărim peste $wpdb->query('START TRANSACTION;'); în acest caz? O să verific manualul MySQL pentru a afla mai multe despre asta ;-) noroc.

birgire birgire
9 iun. 2013 00:05:30

Iată o referință utilă (MySQL 5.1) http://dev.mysql.com/doc/refman/5.1/en/commit.html pe această temă - cel puțin pentru mine ;-)

birgire birgire
9 iun. 2013 00:15:43

Începerea unei tranzacții dezactivează implicit autocommit-ul până când tranzacția este finalizată cu un commit. Cu toate acestea, pentru scripturile de import de unică folosință, consider mult mai clar să dezactivez eu manual autocommit-ul și apoi să fac COMMIT când doresc. Logica tranzacțională este excelentă pentru operații multiple, dar pentru un import unic, este mai ușor să o forțezi manual.

Otto Otto
9 iun. 2013 00:46:58

Dacă aș face mult mai multe importuri decât 1500 de articole (cum am făcut odată cu 400k), atunci aș dezactiva autocommit-ul și l-aș configura să facă COMMIT la fiecare, să zicem, 500 de articole.. În acest fel pot obține o logică restartabilă de la punctul de eșec, menținând totuși o viteză mare.

Otto Otto
9 iun. 2013 00:48:13

mersi, voi ține cont de asta. Am căutat pe svn.wp-plugins.org pentru "autocommit" dar nu am găsit prea multe (doar un rezultat pentru un caz de testare) deci ar putea fi o idee bună ca opțiune pentru plugin-urile de import ;-)

birgire birgire
9 iun. 2013 12:23:28

nici nu știam că svn.wp-plugins.org încă funcționează. Numele oficial acum este plugins.svn.wordpress.org. :)

Otto Otto
10 iun. 2013 19:26:09

deci am pus $wpdb->query('SET autocommit = 0;'); înainte de inserările mele și după inserări execut $wpdb->query('COMMIT'); asta e tot?

yeahman yeahman
6 mai 2014 20:27:25

da, cred că asta ar trebui să funcționeze și poate adăugați $wpdb->query('SET autocommit = 1;'); din nou după aceea.

birgire birgire
6 mai 2014 20:36:19

Când aveți un cache de obiecte, logica tranzacției poate avea rezultate ciudate, mai ales dacă codul va eșua înainte de commit, deoarece cache-ul va avea date care nu sunt în baza de date, ceea ce poate duce la bug-uri foarte greu de depanat.

Mark Kaplun Mark Kaplun
15 dec. 2014 07:30:16

Bun punct, Mark. Dacă acestea sunt doar inserări și nu actualizări, atunci wp_suspend_cache_addition( true ) ar trebui să ajute să NU pună date în cache-ul de obiecte. De asemenea, @birgire a menționat că nu au testat acest lucru cu MyISAM - nu vă deranjați, motorul de stocare nu suportă tranzacții, așa că setarea autocommit sau începerea unei tranzacții nu va avea niciun efect.

kovshenin kovshenin
2 aug. 2016 03:01:42

sfat excelent @Otto. Interogarea mea anterior dura 38 de secunde, acum durează 1 secundă.

Annapurna Annapurna
22 aug. 2017 13:47:20

MyIsam și InnoDB au abordări diferite. https://stackoverflow.com/a/32913817/2377343

T.Todua T.Todua
14 iun. 2021 16:35:24
Arată celelalte 9 comentarii
0

A trebuit să adaug asta:

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

Rețineți că aceasta va omite do_all_pings, care procesează pingback-uri, enclosures, trackback-uri și alte ping-uri (link: https://developer.wordpress.org/reference/functions/do_all_pings/). După cum înțeleg din analizarea codului, pingback-urile/trackback-urile/enclosures în așteptare vor fi în continuare procesate după ce eliminați această linie remove_action, dar nu sunt complet sigur.

Actualizare: Am adăugat și

    define( 'WP_IMPORTING', true );

În plus, folosesc:

    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;' );

    /* Inserarea a 100.000 de posturi simultan
       inclusiv atribuirea unui termen de taxonomie și adăugarea de meta chei
       (adică o buclă `foreach` unde fiecare iterație conține:
       `wp_insert_post`, `wp_set_object_terms`, `add_post_meta`.)
    */

    $wpdb->query( 'COMMIT;' );
    wp_defer_term_counting( false );
    wp_defer_comment_counting( false );
2 aug. 2016 00:41:38
6

Va trebui să inserați postarea pentru a obține ID-ul, dar tabelul $wpdb->postmeta are o structură foarte simplă. Probabil că puteți folosi o instrucțiune directă INSERT INTO, ca aceasta din documentația MySQL: INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);

În cazul dumneavoastră...

$ID = 1; // de la wp_insert_post
$values = '($ID,2,3),($ID,5,6),($ID,8,9)'; // construiți din cele 97 de coloane; aș folosi un fel de buclă
$wpdb->query("INSERT INTO {$wpdb->postmeta} (post_id,meta_key,meta_value) VALUES {$values}");

Aceasta nu va gestiona codificarea, serializarea, escaparea, verificarea erorilor, duplicările sau orice altceva, dar mă aștept să fie mai rapid (deși nu am încercat).

Nu aș face acest lucru pe un site de producție fără teste amănunțite, iar dacă ar trebui să o fac doar o dată sau de două ori, aș folosi funcțiile de bază și aș lua o pauză lungă de prânz în timp ce datele se importă.

8 iun. 2013 20:13:34
Comentarii

Cred că o să iau o pauză lungă de prânz, mai degrabă decât să introduc date brute în tabelele mele și nu are sens să rescriu ceea ce Wordpress va face deja.

Corey Rowell Corey Rowell
8 iun. 2013 21:27:24

așa se întâmplă injecțiile mysql, așa că te rog să nu folosești asta.

OneOfOne OneOfOne
9 mar. 2015 19:04:46

Totul este hard-codat, @OneOfOne. Injecția nu se poate - prin definiție - întâmpla fără input furnizat de utilizator. Aceasta este natura "injecției". OP importă date dintr-un fișier .csv care este sub controlul său folosind cod sub controlul său. Nu există posibilitatea ca o terță parte să injecteze ceva. Te rog să fii atent la context.

s_ha_dum s_ha_dum
10 apr. 2015 03:00:16

+1 de la mine, a trebuit să adaug 20 de valori de câmpuri personalizate și asta a fost mult mai rapid decât "add_post_meta"

Zorox Zorox
5 oct. 2015 15:48:10

Nu poți aștepta ca OP să verifice amănunțit fișierul CSV înainte de import, așadar ar trebui să-l tratezi ca pe o intrare de la utilizator și cel puțin să folosești ->prepare() pentru instrucțiunile tale SQL. În scenariul tău, ce s-ar întâmpla dacă coloana ID din CSV ar conține ceva de genul 1, 'foo', 'bar'); DROP TABLE wp_users; --? Ceva rău, probabil.

kovshenin kovshenin
2 aug. 2016 02:58:08

această funcție va sări peste toate hook-urile externe (dacă există acțiuni atașate la update_post_meta)

T.Todua T.Todua
26 apr. 2018 00:13:57
Arată celelalte 1 comentarii
0

Notă importantă despre 'SET autocommit = 0;'

după setarea autocommit = 0, dacă scriptul se oprește din execuție (din orice motiv, cum ar fi exit, eroare fatală sau altele), atunci modificările NU VOR FI SALVATE ÎN BAZA DE DATE!

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

update_option("ceva", "valoare");     

exit; //să presupunem că aici apare o eroare sau orice altceva...

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

În acest caz update_option nu va fi salvat în baza de date!

Deci, cel mai bun sfat este să înregistrăm COMMIT într-o funcție shutdown ca măsură de precauție (în cazul în care apare orice încheiere neașteptată).

register_shutdown_function( function(){     $GLOBALS['wpdb']->query( 'COMMIT;' );    } );
14 sept. 2018 20:04:11