Maneira mais rápida de wp_insert_post e add_post_meta em massa

16

Eu tenho um arquivo CSV que eu quero inserir que consiste em ~ 1.500 linhas e 97 colunas. Demora cerca de 2-3 horas para fazer uma importação completa e eu gostaria de melhorar isso, se houver uma maneira. Atualmente, para cada linha, estou fazendo um $ post_id = wp_insert_post e, em seguida, um add_post_meta para as 97 colunas associadas a cada linha. Isso é bastante ineficiente ...

Existe uma maneira melhor de fazer isso de uma maneira que um post_id possa manter o relacionamento entre post e seus valores post_meta?

No momento, estou tentando fazer isso na minha máquina local com wamp, mas o executarei em um VPS

Corey Rowell
fonte
Além das dicas do WP abaixo, observe também o uso do InnoDB no MySQL e confirme transações em lotes, de acordo com esta resposta .
Webware

Respostas:

21

Eu tive problemas semelhantes há algum tempo com uma importação CSV personalizada, mas acabei usando algum SQL personalizado para a inserção em massa. Mas eu não tinha visto essa resposta até então:

Otimizar pós-inserção e exclusão para operações em massa?

para wp_defer_term_counting()ativar ou desativar a contagem de termos.

Além disso, se você verificar a fonte do plug-in importador do WordPress, verá estas funções imediatamente antes da importação em massa:

wp_defer_term_counting( true );
wp_defer_comment_counting( true );

e depois da inserção em massa:

wp_defer_term_counting( false );
wp_defer_comment_counting( false );

Portanto, isso pode ser algo para experimentar ;-)

A importação de postagens como rascunho, em vez de publicação , também acelerará as coisas, pois o processo lento de encontrar uma lesma exclusiva para cada uma é ignorado. Pode-se, por exemplo, publicá-las posteriormente em etapas menores, mas observe que esse tipo de abordagem precisaria marcar as postagens importadas de alguma forma, para que não publicemos apenas os rascunhos posteriormente! Isso exigiria um planejamento cuidadoso e provavelmente uma codificação personalizada.

Se houver, por exemplo, muitos títulos de postagem semelhantes (iguais post_name) a serem importados, wp_unique_post_slug()pode ficar lento, devido à iteração da consulta de loop para encontrar uma lesma disponível. Isso pode gerar um grande número de consultas de banco de dados.

Desde o WordPress 5.1, o pre_wp_unique_post_slugfiltro está disponível para evitar a iteração de loop da lesma. Veja o tíquete principal # 21112 . Aqui está um exemplo:

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

Se alguém tentar, por exemplo, $override_slug = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix"com $suffixas $post_id, notaremos que $post_idé sempre 0para novas postagens, como esperado. Existem várias maneiras de gerar números únicos em PHP, como uniqid( '', true ). Mas use esse filtro com cuidado para garantir que você tenha lesmas exclusivas. Por exemplo, poderíamos executar uma consulta de contagem de grupos posteriormente post_namepara ter certeza.

Outra opção seria usar o WP-CLI para evitar o tempo limite. Veja, por exemplo, minha resposta publicada em Criando 20.000 postagens ou páginas usando um arquivo .csv?

Em seguida, podemos executar nosso script de importação PHP personalizado import.phpcom o comando WP-CLI:

wp eval-file import.php

Evite também importar um grande número de tipos hierárquicos de postagem, pois a atual interface do usuário wp-admin não lida bem com isso. Veja, por exemplo, tipo de publicação personalizada - lista de publicações - tela branca da morte

Aqui está a ótima dica do @otto:

Antes das inserções em massa , desative o autocommitmodo explicitamente:

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

Após as inserções em massa, execute:

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

Também acho que seria uma boa ideia fazer algumas tarefas domésticas, como:

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

Eu não testei isso no MyISAM, mas isso deve funcionar no InnoDB .

Como mencionado por @kovshenin, essa dica não funcionaria para o MyISAM .

Birgire
fonte
6
Além disso, você também pode usar a função de consulta para desativar a confirmação automática antes e, em seguida, confirmar manualmente após a conclusão das inserções. Isso acelera bastante as operações no nível do banco de dados ao fazer inserções em massa. Basta enviar um SET autocommit=0;antes das inserções, seguido por um COMMIT;depois.
Otto
Interessante, obrigado por isso! Vou ter que testar quando chegar em casa.
precisa
@Otto, obrigado pela ótima dica. Então poderíamos fazer $wpdb->query('SET autocommit = 0;');antes das inserções, mas podemos pular $wpdb->query('START TRANSACTION;');nesse caso? Vou verificar o manual do MySQL para aprender mais sobre ele ;-) aplausos.
birgire
1
Bom ponto Mark. Se essas são apenas inserções e não atualizações, wp_suspend_cache_addition( true )NÃO deve ajudar a colocar coisas no cache do objeto. Além disso, a @birgire mencionou que não testou isso com o MyISAM - não se preocupe, o mecanismo de armazenamento não suporta transações, portanto, configurar o autocommit ou iniciar uma transação terá efeito zero.
precisa saber é o seguinte
1
ótima dica @Otto. Minha consulta anterior levou 38 segundos, agora leva 1 segundo.
Annapurna
5

Você precisará inserir a postagem para obter seu ID, mas a $wpdb->postmetatabela é muito simples em estrutura. Você provavelmente poderia usar uma INSERT INTOdeclaração direta , como esta nos documentos do MySQL:INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);

No seu caso...

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

Isso não lida com codificação, serialização, escape, verificação de erros, duplicações ou qualquer outra coisa, mas espero que seja mais rápido (embora ainda não tenha tentado).

Eu não faria isso em um local de produção sem testes completos e, se tivesse que fazê-lo uma ou duas vezes, usaria as funções principais e almoçaria enquanto as coisas importam.

s_ha_dum
fonte
Acho que vou almoçar muito, em vez de inserir dados brutos em minhas tabelas e não há sentido em reescrever o que o Wordpress já fará.
Corey Rowell
1
é assim que a injeção do mysql acontece, portanto, não use isso.
OneOfOne
Tudo é codificado, @OneOfOne. A injeção não pode, por definição, acontecer sem a entrada fornecida pelo usuário. Essa é a natureza da "injeção". O OP está importando dados de um arquivo .csv que está sob seu controle usando o código sob seu controle. Não há oportunidade para terceiros injetarem nada. Por favor, preste atenção ao contexto.
s_ha_dum
+1 de mim, eu precisava para adicionar 20 aduaneiros valores campos e isso foi muito mais rápido do que "add_post_meta"
Zorox
1
Você não pode esperar que o OP verifique cuidadosamente o arquivo CSV antes de importá-lo e, portanto, deve tratá-lo como entrada do usuário e, pelo menos, ->prepare()suas instruções SQL. No seu cenário, o que aconteceria se a coluna ID no CSV contivesse algo como 1, 'foo', 'bar'); DROP TABLE wp_users; --? Algo ruim, provavelmente.
Kovshenin
5

Eu tive que adicionar isso:

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

Lembre-se de que isso será ignorado do_all_pings, o que processa pingbacks, gabinetes, trackbacks e outros pings (link: https://developer.wordpress.org/reference/functions/do_all_pings/ ). Meu entendimento ao analisar o código é que os pingbacks / trackbacks / gabinetes pendentes ainda serão processados ​​após a remoção desta remove_actionlinha, mas não tenho certeza.

Atualização: eu também adicionei

    define( 'WP_IMPORTING', true );

Além do que estou usando:

    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
fonte
1

Nota importante sobre 'SET autocommit = 0;'

após definir autocommit = 0se o script interrompe a execução (por algum motivo, como exiterro fatal ou etc ...), suas alterações NÃO SERÃO SALVAS NO DB!

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

update_option("something", "value");     

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

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

Nesse caso update_option, não será salvo no DB!

Portanto, o melhor conselho é ter se COMMITregistrado shutdowncomo uma precatuação (no caso de uma saída inesperada).

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