Schnellerer Weg zu wp_insert_post & add_post_meta in loser Schüttung

15

Ich habe eine CSV-Datei, die ich einfügen möchte, die aus ~ 1.500 Zeilen und 97 Spalten besteht. Ein vollständiger Import dauert ca. 2-3 Stunden und ich würde dies gerne verbessern, wenn es einen Weg gibt. Derzeit führe ich für jede Zeile ein $ post_id = wp_insert_post und dann ein add_post_meta für die 97 zugeordneten Spalten mit jeder Zeile aus. Das ist ziemlich ineffizient ...

Gibt es eine bessere Möglichkeit, dies so zu tun, dass eine post_id die Beziehung zwischen post und seinen post_meta-Werten beibehält?

Im Moment versuche ich dies auf meinem lokalen Computer mit Wamp, aber ich werde es auf einem VPS laufen lassen

Corey Rowell
quelle
Sehen Sie sich neben den WP-Tipps unten auch die Verwendung von InnoDB in MySQL an und schreiben Sie Transaktionen in Stapeln fest, wie in dieser Antwort angegeben .
webaware

Antworten:

20

Ich hatte vor einiger Zeit ähnliche Probleme mit einem benutzerdefinierten CSV-Import, habe jedoch letztendlich einige benutzerdefinierte SQL-Anweisungen für die Masseneinfügung verwendet. Aber ich hatte diese Antwort bis dahin noch nicht gesehen:

Post Insert und Delete für Bulk-Operationen optimieren?

benutzen wp_defer_term_counting() , um die Zählung von Begriffen zu aktivieren oder zu deaktivieren.

Auch wenn Sie den Quellcode für das WordPress-Import-Plugin auschecken, sehen Sie diese Funktionen unmittelbar vor dem Massenimport:

wp_defer_term_counting( true );
wp_defer_comment_counting( true );

und dann nach dem Bulk Insert:

wp_defer_term_counting( false );
wp_defer_comment_counting( false );

Das könnte man also ausprobieren ;-)

Beiträge als Entwurf importieren statt veröffentlichen beschleunigt die Arbeit ebenfalls, da der langsame Prozess, für jeden einen eindeutigen Slug zu finden, übersprungen wird. Man könnte sie zB später in kleineren Schritten veröffentlichen, aber beachten Sie, dass diese Art der Vorgehensweise die importierten Beiträge irgendwie markieren müsste, damit wir später nicht nur irgendwelche Entwürfe veröffentlichen! Dies würde eine sorgfältige Planung und höchstwahrscheinlich eine benutzerdefinierte Codierung erfordern.

Wenn zB viele ähnliche Post-Titel (gleich post_name) importiert werden sollen, dannwp_unique_post_slug() kann dies aufgrund der Iteration der Schleifenabfrage langsam werden, um einen verfügbaren Slug zu finden. Dies kann möglicherweise eine große Anzahl von Datenbankabfragen erzeugen.

Seit WordPress 5.1 ist der pre_wp_unique_post_slugFilter verfügbar, um die Schleifeniteration für den Slug zu vermeiden. Siehe Kernticket # 21112 . Hier ist ein Beispiel:

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

Wenn man es zB $override_slug = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix"mit $suffixas versucht $post_id, dann würden wir feststellen, dass dies erwartungsgemäß $post_idimmer 0für neue Beiträge gilt. Es gibt jedoch verschiedene Möglichkeiten, eindeutige Zahlen in PHP zu generieren, wie z uniqid( '', true ). Verwenden Sie diesen Filter jedoch mit Sorgfalt, um sicherzustellen, dass Sie eindeutige Schnecken haben. Wir könnten zB später eine Gruppenzählungsabfrage durchführen post_name, um sicherzugehen.

Eine andere Möglichkeit wäre die Verwendung von WP-CLI , um eine Zeitüberschreitung zu vermeiden. Siehe z. B. meine Antwort für das Erstellen von 20.000 Posts oder Pages mit einer CSV-Datei?

Dann können wir unser benutzerdefiniertes PHP-Importskript import.phpmit dem WP-CLI-Befehl ausführen :

wp eval-file import.php

Vermeiden Sie auch den Import einer großen Anzahl hierarchischer Beitragstypen, da die aktuelle Benutzeroberfläche von wp-admin dies nicht gut handhabt. Siehe zB Benutzerdefinierter Beitragstyp - Liste der Beiträge - weißer Bildschirm des Todes

Hier ist der großartige Tipp von @otto:

Vor Bulk - Einsätze , deaktivieren Sie den autocommitModus explizit:

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

Führen Sie nach den Masseneinfügungen Folgendes aus:

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

Ich denke auch, dass es eine gute Idee wäre, etwas Haushalt zu führen wie:

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

Ich habe dies nicht auf MyISAM getestet, aber dies sollte auf InnoDB funktionieren .

Wie von @kovshenin erwähnt, würde dieser Tipp für MyISAM nicht funktionieren .

birgire
quelle
6
Darüber hinaus können Sie die Abfragefunktion auch verwenden, um das automatische Festschreiben vor und das manuelle Festschreiben nach dem Einfügen zu deaktivieren. Dies beschleunigt die Operationen auf DB-Ebene bei Masseneinsätzen erheblich. Senden Sie einfach eine SET autocommit=0;vor den Einsätzen, gefolgt von einem COMMIT;danach.
Otto
Interessant, danke dafür! Ich muss es testen, wenn ich nach Hause komme.
Corey Rowell
@Otto, danke für den tollen Tipp. Das könnten wir also $wpdb->query('SET autocommit = 0;');vor den Einfügungen machen, aber können wir $wpdb->query('START TRANSACTION;');in diesem Fall überspringen ? Ich werde das MySQL-Handbuch durchsehen, um mehr darüber zu erfahren ;-) Prost.
Birgire
1
Guter Punkt Mark. Wenn es sich nur um Einfügungen und nicht um Aktualisierungen handelt, wp_suspend_cache_addition( true )sollten Sie KEINE Informationen in den Objektcache stellen. @Birgire erwähnte auch, dass sie dies nicht mit MyISAM getestet haben - stören Sie sich nicht, die Speicher-Engine unterstützt keine Transaktionen, so dass das Setzen von Autocommit oder das Starten einer Transaktion keine Auswirkung hat.
Kovshenin
1
toller Tipp @Otto. Meine Anfrage hat früher 38 Sekunden gedauert, jetzt dauert es 1 Sekunde.
Annapurna
5

Sie müssen den Beitrag einfügen, um Ihre ID zu erhalten, aber die $wpdb->postmetaTabelle ist sehr einfach aufgebaut. Sie könnten wahrscheinlich eine einfache INSERT INTOAnweisung wie die folgende aus den MySQL-Dokumenten verwenden:INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);

In deinem Fall...

$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}");

Das wird nichts mit Kodierung, Serialisierung, Escape, Fehlerprüfung, Vervielfältigung oder anderem zu tun haben, aber ich würde erwarten, dass es schneller ist (obwohl ich es nicht versucht habe).

Ich würde dies auf einer Produktionsstätte nicht ohne gründliche Tests tun, und wenn ich es nur ein- oder zweimal tun müsste, würde ich die Kernfunktionen nutzen und ein langes Mittagessen einnehmen, während Dinge importieren.

s_ha_dum
quelle
Ich nehme mir ein langes Mittagessen und füge keine Rohdaten in meine Tabellen ein. Es macht keinen Sinn, das, was Wordpress bereits tut, neu zu schreiben.
Corey Rowell
1
Dies ist, wie MySQL-Injektion geschieht, also bitte nicht verwenden.
OneOfOne
Alles ist hart codiert, @OneOfOne. Die Einspritzung kann definitionsgemäß nicht ohne vom Benutzer eingegebene Eingaben erfolgen. Das ist die Natur der "Injektion". Das OP importiert Daten aus einer .csv-Datei, die von ihm gesteuert wird, und verwendet dazu den von ihm gesteuerten Code. Es gibt keine Möglichkeit für Dritte, etwas zu injizieren. Bitte achten Sie auf den Kontext.
s_ha_dum
+1 von mir, ich musste 20 Zollfelder Werte hinzufügen und dies war viel schneller als "add_post_meta"
Zorox
1
Sie können nicht erwarten, dass das OP die CSV-Datei vor dem Import gründlich überprüft. Sie sollten sie daher als Benutzereingabe und zumindest als ->prepare()Ihre SQL-Anweisungen behandeln. Was würde in Ihrem Szenario passieren, wenn die ID-Spalte in der CSV so etwas enthalten würde 1, 'foo', 'bar'); DROP TABLE wp_users; --? Wahrscheinlich etwas Schlimmes.
Kovshenin
5

Ich musste folgendes hinzufügen:

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

Beachten Sie, dass dabei do_all_pingsPingbacks, Enclosures, Trackbacks und andere Pings übersprungen werden (Link: https://developer.wordpress.org/reference/functions/do_all_pings/ ). Mein Verständnis beim Betrachten des Codes ist, dass ausstehende Pingbacks / Trackbacks / Enclosures weiterhin verarbeitet werden, nachdem Sie diese remove_actionZeile entfernt haben, aber ich bin nicht ganz sicher.

Update: habe ich auch hinzugefügt

    define( 'WP_IMPORTING', true );

Darüber hinaus benutze ich:

    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 );
firasd
quelle
1

Wichtiger Hinweis zu 'SET autocommit = 0;'

nach dem Einstellen autocommit = 0 ob das Skript die Ausführung stoppt (aus irgendeinem Grund, wie z. B. einem exitschwerwiegenden Fehler oder usw.), werden Ihre Änderungen NICHT IN DB GESPEICHERT!

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

update_option("something", "value");     

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

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

In diesem Fall update_optionwird nicht in DB gespeichert!

Der beste Rat ist also, sich als Vorsichtsmaßnahme COMMITregistrieren zu shutdownlassen (falls ein unerwartetes Beenden eintritt).

register_shutdown_function( function(){
    $GLOBALS['wpdb']->query( 'COMMIT;' );
} );
T.Todua
quelle