Более быстрый способ wp_insert_post & add_post_meta навалом

16

У меня есть файл CSV, который я хочу вставить, который состоит из ~ 1500 строк и 97 столбцов. Полный импорт занимает около 2-3 часов, и я хотел бы улучшить это, если есть способ. В настоящее время для каждой строки я делаю $ post_id = wp_insert_post и затем add_post_meta для 97 связанных столбцов с каждой строкой. Это довольно неэффективно ...

Есть ли лучший способ сделать это так, чтобы a post_id мог сохранить связь между post и его значениями 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()может замедлиться из-за итерации цикла, чтобы найти доступную порцию. Это может генерировать огромное количество запросов БД.

Начиная с 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 .

birgire
источник
6
В дополнение к этому, вы также можете использовать функцию запроса, чтобы отключить автокоммит до, а затем вручную зафиксировать после того, как вставки были сделаны. Это значительно ускоряет операции на уровне БД при массовых вставках. Просто отправьте SET autocommit=0;перед вставками, а затем COMMIT;после.
Отто
Интересно, спасибо за это! Я должен буду проверить это, когда вернусь домой.
Кори Роуэлл
@ Отто, спасибо за отличный совет. Таким образом, мы могли бы сделать $wpdb->query('SET autocommit = 0;');до вставки, но мы можем пропустить $wpdb->query('START TRANSACTION;');в этом случае? Я проверю руководство MySQL, чтобы узнать больше об этом ;-) ура.
Birgire
1
Хороший вопрос Марк. Если это только вставки, а не обновления, то это wp_suspend_cache_addition( true )должно помочь НЕ помещать вещи в кеш объекта. Кроме того, @birgire упомянул, что они не проверяли это с MyISAM - не беспокойтесь, механизм хранения не поддерживает транзакции, поэтому установка автоматической фиксации или запуск транзакции будут иметь нулевой эффект.
Ковшенин
1
отличный совет @ Отто. Мой запрос раньше занимал 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}");

Это не касается кодирования, сериализации, экранирования, проверки ошибок, дублирования или чего-либо еще, но я ожидаю, что это будет быстрее (хотя я не пробовал).

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

s_ha_dum
источник
Думаю, я возьму длинный обед, а не вставляю необработанные данные в свои таблицы, и нет смысла переписывать то, что Wordpress уже сделает.
Кори Роуэлл
1
Вот как происходит внедрение MySQL, поэтому, пожалуйста, не используйте это.
OneOfOne
Все жестко закодировано, @OneOfOne. Инъекция не может быть по определению невозможна без ввода данных пользователем. Это природа "инъекции". ОП импортирует данные из файла .csv, который находится под его контролем, используя код под его контролем. У третьей стороны нет возможности сделать что-либо. Пожалуйста, обратите внимание на контекст.
s_ha_dum
+1 от меня, мне нужно было добавить 20 значений таможенных полей, и это было намного быстрее, чем "add_post_meta"
Zorox
1
Вы не можете ожидать, что OP тщательно проверит файл CSV перед его импортом, и поэтому вы должны рассматривать его как ввод данных пользователем и, по крайней мере, ->prepare()ваши операторы SQL. В вашем сценарии, что произойдет, если в столбце идентификаторов в CSV будет что-то подобное 1, 'foo', 'bar'); DROP TABLE wp_users; --? Что-то плохое, наверное.
Ковшенин
5

Я должен был добавить это:

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

Имейте в виду, что это будет пропускать do_all_pings, который обрабатывает pingbacks, вложения, trackbacks и другие ping (ссылка: https://developer.wordpress.org/reference/functions/do_all_pings/ ). Насколько я понимаю, глядя на код, можно ожидать, что ожидающие pingbacks / trackbacks / вложений будут обрабатываться после удаления этой 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 );
firasd
источник
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;' );
} );
T.Todua
источник