Швидший спосіб оптом wp_insert_post & add_post_meta


16

У мене є файл csv, який я хочу вставити, що складається з ~ 1500 рядків і 97 стовпців. Для повного імпорту потрібно близько 2-3 годин, і я хотів би покращити це, якщо є спосіб. В даний час для кожного рядка я роблю $ post_id = wp_insert_post, а потім add_post_meta для 97 асоційованих стовпців із кожним рядком. Це досить неефективно ...

Чи є кращий спосіб зробити це таким чином, щоб атрибут post_id міг зберігати зв’язок між публікацією та її значеннями post_meta?

Зараз я пробую це на своїй локальній машині з wamp, але він буде працювати на VPS


Окрім наведених нижче порад щодо WP, також зверніть увагу на використання InnoDB в MySQL та здійснюйте транзакції партіями відповідно до цієї відповіді .
webaware

Відповіді:


21

У мене були подібні проблеми колись тому зі спеціальним імпортом CSV, але я закінчив, використовуючи якийсь спеціальний SQL для масової вставки. Але я ще не бачив такої відповіді:

Оптимізувати вставку та видалити повідомлення для масових операцій?

використовувати wp_defer_term_counting()для включення або відключення підрахунку термінів.

Також якщо ви перевірите джерело плагіна імпортера WordPress, ви побачите ці функції безпосередньо перед масовим імпортом:

wp_defer_term_counting( true );
wp_defer_comment_counting( true );

а потім після масової вставки:

wp_defer_term_counting( false );
wp_defer_comment_counting( false );

Так що це може щось спробувати ;-)

Імпорт публікацій у вигляді чернетки замість публікації також пришвидшить роботу, оскільки пропускається повільний процес пошуку унікального для кожного з них. Можна, наприклад, опублікувати їх пізніше, але зазначити, що такий підхід повинен якось позначати імпортовані публікації, тому ми не просто публікуємо будь-які чернетки пізніше! Це потребує ретельного планування та, швидше за все, певного кодування.

Якщо є, наприклад, безліч подібних заголовків публікацій (однакових post_name), які потрібно імпортувати, то вони wp_unique_post_slug()можуть стати повільними, завдяки ітерації запитів циклу, щоб знайти доступний слизь. Це, можливо, може генерувати величезну кількість db запитів.

Оскільки WordPress 5.1 pre_wp_unique_post_slugфільтр доступний, щоб уникнути ітерації циклу для слизи. Дивіться основний квиток № 21112 . Ось приклад:

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

Якщо спробувати, наприклад $override_slug = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix" з " $suffixas" $post_id, то зауважимо, що $post_idце завжди 0для нових постів, як очікувалося. Однак існують різні способи генерування унікальних чисел у PHP uniqid( '', true ). Але використовуйте цей фільтр з обережністю, щоб переконатися, що у вас є унікальні кулі. Ми можемо, наприклад, запустити запит підрахунку групи після цього, post_nameщоб бути впевненим.

Іншим варіантом буде використання WP-CLI, щоб уникнути тайм-ауту. Дивіться, наприклад, мою відповідь, опубліковану для створення 20000 повідомлень або сторінок за допомогою .csv-файлу?

Тоді ми можемо запустити наш спеціальний сценарій імпорту PHP за import.phpдопомогою команди WP-CLI:

wp eval-file import.php

Також уникайте імпорту великої кількості ієрархічних типів публікацій, оскільки поточний інтерфейс wp-admin не справляється з цим. Див., Наприклад, Спеціальний тип публікації - список публікацій - білий екран смерті

Ось чудова порада від @otto:

Перед масовими вставками вимкніть autocommitрежим прямо:

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

Після масових вставок запустіть:

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

Я також думаю, що було б непогано зайнятися господарством на зразок:

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

Я не перевіряв цього на MyISAM, але над цим слід працювати InnoDB .

Як згадував @kovshenin, ця порада не працюватиме для MyISAM .


6
На додаток до цього, ви також можете використовувати функцію запиту, щоб вимкнути автокомісію раніше, а потім виконати вручну фіксацію після вставки. Це значно прискорює операції на рівні БД при виконанні об'ємних вставок. Просто надішліть а SET autocommit=0;перед вставками, а потім - COMMIT;після.
Отто

Цікаво, дякую за це! Мені доведеться перевірити це, коли повернусь додому.
Corey Rowell

@ Отто, дякую за чудову пораду. Отже, ми могли зробити $wpdb->query('SET autocommit = 0;');перед вставками, але чи можемо ми пропустити $wpdb->query('START TRANSACTION;');в такому випадку? Я перегляньте посібник з MySQL, щоб дізнатися більше про нього ;-) ура.
birgire

1
Добрий бал Марк. Якщо це лише вставки, а не оновлення, то це wp_suspend_cache_addition( true )повинно допомогти НЕ поміщати речі в кеш об'єктів. Також @birgire згадав, що вони не тестували це з MyISAM - не турбуйтеся, система зберігання даних не підтримує транзакції, тому встановлення автокомісії або запуск транзакції матиме нульовий ефект.
ковшенін

1
чудова порада @Otto. Мій запит раніше займав 38 секунд, зараз він займає 1 сек.
Аннапурна

5

Вам потрібно буде вставити публікацію, щоб отримати свій ідентифікатор, але $wpdb->postmetaтаблиця за структурою дуже проста. Можливо, ви можете використовувати прямий INSERT INTOвислів, наприклад, з документів MySQL:INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);

У вашому випадку ...

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

Це не стосується жодного кодування, серіалізації, виходу, перевірки помилок, дублювання чи будь-чого іншого, але я б очікував, що це буде швидше (хоча я не намагався).

Я б не робив цього на виробничому майданчику без ретельного тестування, і якби мені довелося це робити лише один-два рази, я б користувався основними функціями і довго обідав, коли речі імпортували.


Подумайте, я задовго пообідаю, замість того, щоб не вставляти необроблені дані в свої таблиці, і немає сенсу переписувати те, що вже буде робити Wordpress.
Корі Роуелл

1
ось як відбувається ін'єкція mysql, тому, будь ласка, не використовуйте це.
OneOfOne

Все важко закодовано, @OneOfOne. Ін'єкція не може - за визначенням не може відбутися без введення користувачем даних. Така природа "ін'єкції". ОП імпортує дані з .csv-файлу, який знаходиться під його контролем, використовуючи код, що знаходиться під його контролем. Немає можливості третьої сторони щось робити ін'єкційно. Зверніть увагу на контекст.
s_ha_dum

+1 від мене, мені потрібно було додати 20 значень митних полів, і це було набагато швидше, ніж "add_post_meta"
Zorox

1
Ви не можете очікувати, що ОП ретельно перевірить файл CSV перед тим, як імпортувати його, і тому слід ставитися до нього як до вводу користувача та принаймні до ->prepare()ваших операторів SQL. У вашому сценарії, що трапиться, якби стовпець ідентифікатора в CSV містив щось подібне 1, 'foo', 'bar'); DROP TABLE wp_users; --? Можливо, щось погане.
ковшенін

5

Мені довелося додати це:

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

Майте на увазі, що це буде пропущено do_all_pings, що обробляє пінгбеки, корпуси, трекбеки та інші пінг (посилання: https://developer.wordpress.org/reference/functions/do_all_pings/ ). Я розумію, як дивитись на код, полягає в тому, що відкладені пінгбеки / трекбеки / корпуси все одно будуть оброблені після видалення цього remove_actionрядка, але я не зовсім впевнений.

Оновлення: я також додав

    define( 'WP_IMPORTING', true );

Крім того, я використовую:

    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

Важлива примітка про 'SET autocommit = 0;'

після встановлення, autocommit = 0якщо скрипт припиняє виконання (з якоїсь причини, наприклад exit, фатальної помилки чи т. д. ...), ваші зміни НЕ будуть збережені в БД!

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

update_option("something", "value");     

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

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

В цьому випадку update_option в БД не буде збережено!

Отож, найкраще порадити - COMMITзареєструватися у shutdownфункції як попередній вихід (у випадку, якщо трапиться якийсь несподіваний вихід).

register_shutdown_function( function(){
    $GLOBALS['wpdb']->query( 'COMMIT;' );
} );
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.