Forma más rápida de wp_insert_post & add_post_meta en masa

16

Tengo un archivo csv que quiero insertar que consta de ~ 1,500 filas y 97 columnas. Se necesitan entre 2 y 3 horas para realizar una importación completa y me gustaría mejorar esto si hay alguna manera. Actualmente para cada fila estoy haciendo un $ post_id = wp_insert_post y luego un add_post_meta para las 97 columnas asociadas con cada fila. Esto es bastante ineficiente ...

¿Hay una mejor manera de hacer esto de una manera que pueda obtener un post_id para mantener la relación entre post y sus valores post_meta?

En este momento estoy probando esto en mi máquina local con wamp pero lo tendré funcionando en un VPS

Corey Rowell
fuente
Además de los consejos de WP a continuación, también mire el uso de InnoDB en MySQL y confirme las transacciones en lotes, según esta respuesta .
webaware

Respuestas:

21

Hace algún tiempo tuve problemas similares con una importación CSV personalizada, pero terminé usando un SQL personalizado para la inserción masiva. Pero no había visto esta respuesta para entonces:

¿Optimizar la inserción y eliminación de publicaciones para operaciones masivas?

para wp_defer_term_counting()habilitar o deshabilitar el conteo de términos.

Además, si revisa la fuente del complemento importador de WordPress, verá estas funciones justo antes de la importación masiva:

wp_defer_term_counting( true );
wp_defer_comment_counting( true );

y luego después de la inserción masiva:

wp_defer_term_counting( false );
wp_defer_comment_counting( false );

Entonces esto podría ser algo para probar ;-)

Importar publicaciones como borrador en lugar de publicar también acelerará las cosas, ya que se omite el lento proceso de encontrar una babosa única para cada una. Uno podría, por ejemplo, publicarlos más tarde en pasos más pequeños, pero tenga en cuenta que este tipo de enfoque necesitaría marcar las publicaciones importadas de alguna manera, ¡así que no publicaremos ningún borrador más adelante! Esto necesitaría una planificación cuidadosa y muy probablemente una codificación personalizada.

Si, por ejemplo, se importan muchos títulos de publicaciones similares (iguales post_name), wp_unique_post_slug()puede volverse lento, debido a la iteración de la consulta de bucle para encontrar una babosa disponible. Esto posiblemente puede generar una gran cantidad de consultas db.

Desde WordPress 5.1, el pre_wp_unique_post_slugfiltro está disponible para evitar la iteración del bucle de la babosa. Ver boleto principal # 21112 . Aquí hay un ejemplo:

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

Si uno intenta, por ejemplo, $override_slug = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix"con $suffixas $post_id, entonces notaríamos que $post_idsiempre es 0para nuevas publicaciones, como se esperaba. Sin embargo, hay varias formas de generar números únicos en PHP, como uniqid( '', true ). Pero use este filtro con cuidado para asegurarse de tener babosas únicas. Podríamos, por ejemplo, ejecutar una consulta de recuento grupal después post_namepara estar seguros.

Otra opción sería usar WP-CLI para evitar el tiempo de espera. ¿Ve, por ejemplo, mi respuesta publicada para Crear 20,000 publicaciones o páginas usando un archivo .csv?

Luego podemos ejecutar nuestro script de importación PHP personalizado import.phpcon el comando WP-CLI:

wp eval-file import.php

También evite importar una gran cantidad de tipos de publicaciones jerárquicas, ya que la interfaz de usuario actual de wp-admin no lo maneja bien. Ver, por ejemplo, tipo de publicación personalizada - lista de publicaciones - pantalla blanca de la muerte

Aquí está el gran consejo de @otto:

Antes de las inserciones masivas , deshabilite el autocommitmodo explícitamente:

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

Después de las inserciones masivas, ejecute:

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

También creo que sería una buena idea hacer algunas tareas de limpieza como:

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

No he probado esto en MyISAM pero debería funcionar en InnoDB .

Como mencionó @kovshenin, este consejo no funcionaría para MyISAM .

Birgire
fuente
66
Además de esto, también puede usar la función de consulta para desactivar la confirmación automática antes, y luego confirmar manualmente después de que se hayan realizado las inserciones. Esto acelera enormemente las operaciones a nivel de base de datos cuando se realizan inserciones masivas. Simplemente envíe un SET autocommit=0;antes de las inserciones, seguido de un COMMIT;después.
Otto
Interesante, gracias por eso! Tendré que probarlo cuando llegue a casa.
Corey Rowell
@ Otto, gracias por el gran consejo. Entonces podríamos hacer $wpdb->query('SET autocommit = 0;');antes de las inserciones, pero ¿podemos omitir $wpdb->query('START TRANSACTION;');en ese caso? Revisaré el manual de MySQL para obtener más información al respecto ;-) saludos.
Birgire
1
Buen punto Mark. Si estos son solo insertos y no actualizaciones, entonces wp_suspend_cache_addition( true )NO debería ayudar a poner cosas en el caché de objetos. También @birgire mencionó que no probaron esto con MyISAM; no se moleste, el motor de almacenamiento no admite transacciones, por lo que configurar la confirmación automática o iniciar una transacción no tendrá ningún efecto.
kovshenin
1
gran consejo @Otto. Mi consulta antes tardó 38 segundos, ahora toma 1 segundo.
Annapurna
5

Deberá insertar la publicación para obtener su ID, pero la $wpdb->postmetatabla tiene una estructura muy simple. Probablemente podría usar una INSERT INTOdeclaración directa , como esta de los documentos de MySQL:INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);

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

Eso no se ocupará de ninguna codificación, serialización, escape, verificación de errores, duplicaciones o cualquier otra cosa, pero esperaría que fuera más rápido (aunque no lo he intentado).

No haría esto en un sitio de producción sin una prueba exhaustiva, y si solo tuviera que hacerlo una o dos veces, usaría las funciones principales y tomaría un largo almuerzo mientras las cosas se importan.

s_ha_dum
fuente
Creo que tomaré un largo almuerzo, en lugar de no insertar datos sin procesar en mis tablas y no tiene sentido reescribir lo que Wordpress ya hará.
Corey Rowell
1
así es como ocurre la inyección mysql, así que por favor no use esto.
OneOfOne
Todo está codificado, @OneOfOne. La inyección no ocurre, por definición, sin la entrada proporcionada por el usuario. Esa es la naturaleza de la "inyección". El OP está importando datos de un archivo .csv que está bajo su control utilizando código bajo su control. No hay oportunidad para que un tercero inyecte nada. Por favor, preste atención al contexto.
s_ha_dum
+1 de mi parte, necesitaba agregar 20 valores de campos de aduanas y esto fue mucho más rápido que "add_post_meta"
Zorox
1
No puede esperar que el OP verifique minuciosamente el archivo CSV antes de importarlo y, por lo tanto, debe tratarlo como una entrada del usuario y, al menos, ->prepare()sus declaraciones SQL. En su escenario, ¿qué pasaría si la columna de ID en el CSV contuviera algo así 1, 'foo', 'bar'); DROP TABLE wp_users; --? Algo malo probablemente.
kovshenin
5

Tuve que agregar esto:

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

Tenga en cuenta que esto saltará do_all_pings, lo que procesa pingbacks, recintos, trackbacks y otros pings (enlace: https://developer.wordpress.org/reference/functions/do_all_pings/ ). Según remove_actiontengo entendido al mirar el código, los pingbacks / trackbacks / recintos pendientes aún se procesarán después de eliminar esta línea, pero no estoy completamente seguro.

Actualización: también agregué

    define( 'WP_IMPORTING', true );

Más allá de eso estoy 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
fuente
1

Nota importante sobre 'SET autocommit = 0;'

después de configurar autocommit = 0si el script detiene la ejecución (por alguna razón, como un exiterror fatal o etc.), entonces sus cambios NO SE GUARDARÁN EN DB!

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

update_option("something", "value");     

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

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

¡En este caso update_optionno se guardará en DB!

Por lo tanto, el mejor consejo es haberse COMMITregistrado en shutdownfunción como precaución (en caso de que ocurra una salida inesperada).

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