Szybszy sposób na wp_insert_post i add_post_meta luzem

16

Mam plik csv, który chcę wstawić, który składa się z ~ 1500 wierszy i 97 kolumn. Pełny import zajmuje około 2-3 godzin i chciałbym to poprawić, jeśli istnieje sposób. Obecnie dla każdego wiersza wykonuję $ post_id = wp_insert_post, a następnie add_post_meta dla 97 powiązanych kolumn z każdym rzędem. To jest dość nieefektywne ...

Czy istnieje lepszy sposób, aby to zrobić w taki sposób, aby post_id mógł zachować związek między wartością post a jej wartościami post_meta?

W tej chwili próbuję tego na moim komputerze lokalnym z wamp, ale będzie działał na VPS

Corey Rowell
źródło
Oprócz wskazówek WP poniżej, spójrz także na używanie InnoDB w MySQL i zatwierdzaj transakcje partiami, zgodnie z tą odpowiedzią .
webware

Odpowiedzi:

21

Miałem kiedyś podobne problemy z niestandardowym importem CSV, ale skończyło się na użyciu niestandardowego kodu SQL dla wstawiania zbiorczego. Ale do tej pory nie widziałem tej odpowiedzi:

Zoptymalizować wstawianie i usuwanie wpisów dla operacji masowych?

używać, wp_defer_term_counting()aby włączyć lub wyłączyć liczenie terminów.

Ponadto, jeśli sprawdzisz źródło wtyczki importera WordPress, zobaczysz te funkcje tuż przed importem zbiorczym:

wp_defer_term_counting( true );
wp_defer_comment_counting( true );

a następnie po wkładce zbiorczej:

wp_defer_term_counting( false );
wp_defer_comment_counting( false );

To może być coś do wypróbowania ;-)

Importowanie postów w wersji roboczej zamiast publikowania również przyspieszy, ponieważ powolny proces znajdowania unikalnego ślimaka dla każdego z nich jest pomijany. Można np. Opublikować je później w mniejszych krokach, ale należy pamiętać, że tego rodzaju podejście musiałoby w jakiś sposób oznaczać zaimportowane posty, więc nie publikujemy żadnych wersji roboczych później! Wymagałoby to starannego planowania i prawdopodobnie niestandardowego kodowania.

Jeśli istnieje np. Wiele podobnych tytułów postów (takich samych post_name) do zaimportowania, wp_unique_post_slug()może stać się powolny z powodu iteracji zapytania pętli w celu znalezienia dostępnego ślimaka. Może to generować ogromną liczbę zapytań db.

Od WordPress 5.1 pre_wp_unique_post_slugfiltr jest dostępny, aby uniknąć iteracji pętli dla ślimaka. Zobacz bilet podstawowy nr 21112 . Oto przykład:

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

Jeśli spróbujemy np. $override_slug = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix"Z $suffixas $post_id, to zauważymy, że $post_idzawsze 0dotyczy to nowych postów, zgodnie z oczekiwaniami. Istnieją różne sposoby generowania unikalnych liczb w PHP, na przykład uniqid( '', true ). Ale używaj tego filtra ostrożnie, aby upewnić się, że masz unikalne ślimaki. Możemy na przykład uruchomić zapytanie dotyczące liczby grup, post_nameaby się upewnić.

Inną opcją byłoby użycie WP-CLI, aby uniknąć przekroczenia limitu czasu. Zobacz np. Moją odpowiedź opublikowaną na temat tworzenia 20 000 postów lub stron przy użyciu pliku .csv?

Następnie możemy uruchomić nasz niestandardowy skrypt importu PHP import.phpza pomocą komendy WP-CLI:

wp eval-file import.php

Unikaj także importowania dużej liczby hierarchicznych typów postów, ponieważ bieżący interfejs użytkownika wp-admin nie radzi sobie z tym dobrze. Zobacz np. Niestandardowy typ postu - lista postów - biały ekran śmierci

Oto świetna wskazówka od @otto:

Przed wstawieniem zbiorczym wyłącz autocommittryb jawnie:

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

Po wstawieniu luzem uruchom:

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

Myślę również, że dobrym pomysłem byłoby wykonanie pewnych czynności porządkowych, takich jak:

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

Nie testowałem tego na MyISAM, ale to powinno działać na InnoDB .

Jak wspomniano przez @kovshenin, ta wskazówka nie zadziała w MyISAM .

birgire
źródło
6
Oprócz tego możesz również użyć funkcji zapytania, aby wcześniej wyłączyć automatyczne zatwierdzanie, a następnie ręcznie zatwierdzić po zakończeniu wstawiania. Przyspiesza to znacznie operacje na poziomie bazy danych podczas wykonywania wstawień luzem. Po prostu wyślij SET autocommit=0;przed wkładkami, a następnie COMMIT;po.
Otto,
Ciekawe, dzięki za to! Będę musiał to przetestować, kiedy wrócę do domu.
Corey Rowell,
@Otto, dziękuję za świetną wskazówkę. Więc moglibyśmy zrobić $wpdb->query('SET autocommit = 0;');przed wstawkami, ale czy możemy $wpdb->query('START TRANSACTION;');w takim przypadku pominąć ? Sprawdzę instrukcję MySQL, aby dowiedzieć się więcej na ten temat ;-) na zdrowie.
birgire,
1
Dobry punkt Mark. Jeśli są to tylko wstawki, a nie aktualizacje, to wp_suspend_cache_addition( true )NIE powinno to pomagać w umieszczaniu elementów w pamięci podręcznej obiektów. @ Birgire wspomniał również, że nie testowali tego z MyISAM - nie przejmuj się, silnik pamięci nie obsługuje transakcji, więc ustawienie automatycznego zatwierdzania lub rozpoczęcie transakcji będzie miało zerowy efekt.
kovshenin
1
świetna wskazówka @Otto. Moje zapytanie wcześniej zajęło 38 sekund, teraz zajmuje 1 sekundę.
Annapurna
5

Będziesz musiał wstawić post, aby uzyskać swój identyfikator, ale $wpdb->postmetatabela jest bardzo prosta w strukturze. Prawdopodobnie możesz użyć prostej INSERT INTOinstrukcji, takiej jak ta z dokumentów MySQL:INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);

W Twoim przypadku...

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

To nie poradzi sobie z żadnym kodowaniem, serializacją, ucieczką, sprawdzaniem błędów, duplikacjami ani czymkolwiek innym, ale spodziewałbym się, że będzie szybszy (chociaż nie próbowałem).

Nie zrobiłbym tego na miejscu produkcji bez dokładnych testów, a gdybym musiał to zrobić tylko raz lub dwa razy, skorzystałbym z podstawowych funkcji i wziąłbym długi lunch podczas importowania rzeczy.

s_ha_dum
źródło
Myślę, że wezmę długi lunch, a raczej nie wstawię surowych danych do moich tabel i przepisywanie tego, co Wordpress już zrobi, nie ma sensu.
Corey Rowell,
1
w ten sposób dzieje się zastrzyk mysql, więc nie używaj tego.
OneOfOne
Wszystko jest zakodowane na stałe, @OneOfOne. Wtrysk nie odbywa się - z definicji nie może się zdarzyć bez wkładu użytkownika. Taka jest natura „zastrzyku”. OP importuje dane z pliku .csv, który jest pod jego kontrolą, za pomocą kodu pod jego kontrolą. Osoba trzecia nie ma możliwości wstrzyknięcia czegokolwiek. Proszę zwrócić uwagę na kontekst.
s_ha_dum
+1 ode mnie, musiałem dodać 20 wartości pól celnych, a to było znacznie szybsze niż „add_post_meta”
Zorox,
1
Nie możesz oczekiwać, że OP dokładnie sprawdzi plik CSV przed jego zaimportowaniem, dlatego powinieneś traktować go jako dane wejściowe użytkownika, a przynajmniej ->prepare()instrukcje SQL. W twoim scenariuszu, co by się stało, gdyby kolumna identyfikatora w pliku CSV zawierała coś takiego 1, 'foo', 'bar'); DROP TABLE wp_users; --? Prawdopodobnie coś złego.
kovshenin
5

Musiałem dodać to:

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

Pamiętaj, że spowoduje to pominięcie do_all_pings, które przetwarza pingbacki, załączniki, trackbacki i inne pingi (link: https://developer.wordpress.org/reference/functions/do_all_pings/ ). Rozumiem po spojrzeniu na kod, że oczekujące pingbacki / trackbacki / obudowy będą nadal przetwarzane po usunięciu tego remove_actionwiersza, ale nie jestem całkowicie pewien.

Aktualizacja: Dodałem również

    define( 'WP_IMPORTING', true );

Poza tym używam:

    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
źródło
1

Ważna uwaga na temat 'SET autocommit = 0;'

po ustawieniu, autocommit = 0czy skrypt przestaje działać (z jakiegoś powodu, na przykład exitbłąd krytyczny itp.), to zmiany NIE ZOSTANĄ ZAPISANE W DB!

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

update_option("something", "value");     

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

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

W tym przypadku update_option nie zostaną zapisane w DB!

Dlatego najlepiej jest COMMITzarejestrować się w shutdownfunkcji jako warunek wstępny (na wypadek nieoczekiwanego wyjścia).

register_shutdown_function( function(){
    $GLOBALS['wpdb']->query( 'COMMIT;' );
} );
T.Todua
źródło