Cara yang lebih cepat untuk wp_insert_post & add_post_meta secara massal

16

Saya memiliki file csv yang ingin saya masukkan yang terdiri dari ~ 1.500 baris dan 97 kolom. Dibutuhkan sekitar 2-3 jam untuk melakukan impor penuh dan saya ingin meningkatkan ini jika ada cara. Saat ini untuk setiap baris saya sedang melakukan $ post_id = wp_insert_post dan kemudian add_post_meta untuk 97 kolom terkait dengan setiap baris. Ini sangat tidak efisien ...

Apakah ada cara yang lebih baik untuk melakukan ini dengan cara yang bisa mendapatkan post_id menjaga hubungan antara post dan nilai post_meta?

Saat ini saya sedang mencoba ini pada mesin lokal saya dengan wamp tetapi akan menjalankannya pada VPS

Corey Rowell
sumber
Selain tip WP di bawah ini, lihat juga menggunakan InnoDB di MySQL dan lakukan transaksi dalam batch, per jawaban ini .
webaware

Jawaban:

21

Saya punya masalah serupa beberapa waktu yang lalu dengan impor CSV kustom, tapi saya akhirnya menggunakan beberapa SQL kustom untuk memasukkan massal. Tetapi saya belum melihat jawaban ini pada saat itu:

Optimalkan penyisipan pos dan hapus untuk operasi massal?

untuk digunakan wp_defer_term_counting()untuk mengaktifkan atau menonaktifkan penghitungan jangka.

Juga jika Anda memeriksa sumber untuk plugin importir WordPress, Anda akan melihat fungsi-fungsi ini tepat sebelum impor massal:

wp_defer_term_counting( true );
wp_defer_comment_counting( true );

dan kemudian setelah memasukkan massal:

wp_defer_term_counting( false );
wp_defer_comment_counting( false );

Jadi ini mungkin sesuatu untuk dicoba ;-)

Mengimpor posting sebagai konsep alih - alih mempublikasikan , juga akan mempercepat, karena proses lambat menemukan siput unik untuk masing-masing dilewati. Misalnya, orang dapat mempublikasikannya dalam langkah-langkah yang lebih kecil, tetapi perhatikan bahwa pendekatan semacam ini perlu menandai pos yang diimpor, jadi kami tidak hanya menerbitkan konsep apa pun nanti! Ini membutuhkan perencanaan yang cermat dan kemungkinan besar beberapa pengkodean khusus.

Jika ada misalnya banyak judul posting yang serupa (sama post_name) yang akan diimpor, maka wp_unique_post_slug()bisa menjadi lambat, karena iterasi loop permintaan untuk menemukan siput yang tersedia. Ini mungkin dapat menghasilkan sejumlah besar permintaan db.

Sejak WordPress 5.1, pre_wp_unique_post_slugfilter tersedia untuk menghindari iterasi loop untuk siput. Lihat tiket inti # 21112 . Ini sebuah contoh:

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

Jika seseorang mencoba misalnya $override_slug = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix"dengan $suffixsebagai $post_id, maka kami akan mencatat bahwa $post_idselalu 0untuk posting baru, seperti yang diharapkan. Ada berbagai cara untuk menghasilkan angka unik di PHP, seperti uniqid( '', true ). Tetapi gunakan filter ini dengan hati-hati untuk memastikan Anda memiliki siput unik. Kita bisa mis. Menjalankan kueri penghitungan grup sesudahnya post_nameuntuk memastikan.

Opsi lain adalah menggunakan WP-CLI untuk menghindari batas waktu. Lihat misalnya jawaban saya diposting untuk Membuat 20.000 Posting atau Halaman menggunakan file .csv?

Kemudian kita dapat menjalankan skrip impor PHP khusus kami import.phpdengan perintah WP-CLI:

wp eval-file import.php

Juga hindari mengimpor sejumlah besar jenis posting hierarkis, karena UI wp-admin saat ini tidak menanganinya dengan baik. Lihat misalnya Jenis posting kustom - daftar posting - layar putih kematian

Inilah tip hebat dari @otto:

Sebelum memasukkan massal , nonaktifkan autocommitmode secara eksplisit:

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

Setelah sisipan massal, jalankan:

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

Saya juga berpikir itu akan menjadi ide yang baik untuk melakukan pembenahan seperti:

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

Saya belum menguji ini pada MyISAM tetapi ini harus bekerja pada InnoDB .

Seperti disebutkan oleh @kovshenin tip ini tidak akan berfungsi untuk MyISAM .

birgire
sumber
6
Selain itu, Anda juga dapat menggunakan fungsi kueri untuk mematikan autocommit sebelumnya, dan kemudian komit secara manual setelah penyisipan dilakukan. Ini sangat mempercepat operasi di tingkat DB saat melakukan sisipan massal. Cukup kirim SET autocommit=0;sebelum sisipan, diikuti oleh COMMIT;sesudahnya.
Otto
Menarik, terima kasih untuk itu! Saya harus mengujinya ketika sampai di rumah.
Corey Rowell
@Otto, terima kasih atas tipnya. Jadi kita bisa melakukannya $wpdb->query('SET autocommit = 0;');sebelum memasukkan tetapi bisakah kita lewati $wpdb->query('START TRANSACTION;');dalam kasus itu? Saya akan memeriksa manual MySQL untuk mempelajari lebih lanjut ;-) cheers.
birgire
1
Poin yang bagus. Jika ini hanya sisipan dan bukan pembaruan, maka wp_suspend_cache_addition( true )seharusnya BUKAN memasukkan barang-barang ke dalam cache objek. @Birgire juga mengatakan mereka tidak menguji ini dengan MyISAM - jangan repot, mesin penyimpanan tidak mendukung transaksi sehingga pengaturan autocommit atau memulai transaksi akan memiliki efek nol.
kovshenin
1
tip besar @Otto. Permintaan saya sebelumnya membutuhkan waktu 38 detik, sekarang dibutuhkan 1 detik.
Annapurna
5

Anda perlu memasukkan posting untuk mendapatkan ID Anda tetapi $wpdb->postmetatabelnya terstruktur sangat sederhana. Anda mungkin dapat menggunakan INSERT INTOpernyataan langsung , seperti ini dari dokumen MySQL:INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);

Dalam kasus Anda ...

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

Itu tidak akan berurusan dengan penyandian, serialisasi, melarikan diri, memeriksa kesalahan, duplikasi, atau apa pun, tapi saya berharap itu akan lebih cepat (meskipun saya belum mencoba).

Saya tidak akan melakukan ini di lokasi produksi tanpa pengujian menyeluruh, dan jika saya hanya perlu melakukannya sekali atau dua kali, saya akan menggunakan fungsi inti dan makan siang yang panjang sementara barang-barang diimpor.

s_ha_dum
sumber
Saya pikir saya akan makan siang yang panjang, daripada tidak memasukkan data mentah ke meja saya dan tidak ada gunanya menulis ulang apa yang sudah dilakukan Wordpress.
Corey Rowell
1
ini adalah bagaimana injeksi mysql terjadi, jadi tolong jangan gunakan ini.
OneOfOne
Semuanya kode-keras, @OneOfOne. Injeksi tidak - tidak bisa menurut definisi - terjadi tanpa input yang diberikan pengguna. Itulah sifat "injeksi". OP sedang mengimpor data dari file .csv yang berada di bawah kendalinya menggunakan kode di bawah kendalinya. Tidak ada peluang bagi pihak ketiga untuk menyuntikkan sesuatu. Mohon perhatikan konteksnya.
s_ha_dum
+1 dari saya, saya perlu menambahkan 20 nilai bidang bea cukai dan ini jauh lebih cepat daripada "add_post_meta"
Zorox
1
Anda tidak dapat mengharapkan OP untuk memeriksa file CSV secara menyeluruh sebelum mengimpornya, dan karenanya Anda harus memperlakukannya sebagai input pengguna, dan setidaknya ->prepare()pernyataan SQL Anda. Dalam skenario Anda, apa yang akan terjadi jika kolom ID di CSV berisi sesuatu seperti 1, 'foo', 'bar'); DROP TABLE wp_users; --? Sesuatu yang buruk mungkin.
kovshenin
5

Saya harus menambahkan ini:

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

Ingatlah bahwa ini akan dilewati do_all_pings, yang memproses pingback, enklosur, trackback, dan ping lainnya (tautan: https://developer.wordpress.org/reference/functions/do_all_pings/ ). Pemahaman saya dari melihat kode adalah pingbacks / trackbacks / lampiran yang tertunda masih akan diproses setelah Anda menghapus remove_actionbaris ini , tapi saya tidak sepenuhnya yakin.

Pembaruan: Saya juga menambahkan

    define( 'WP_IMPORTING', true );

Di luar itu saya menggunakan:

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

Catatan penting tentang 'SET autocommit = 0;'

setelah pengaturan autocommit = 0jika skrip menghentikan eksekusi (karena beberapa alasan, seperti exit, kesalahan fatal atau dll ...), maka perubahan Anda TIDAK AKAN DIKELIMPAMKAN DALAM DB!

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

update_option("something", "value");     

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

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

Dalam hal ini update_optiontidak akan disimpan dalam DB!

Jadi, saran terbaik adalah COMMITmendaftarkan shutdownfungsi sebagai prasyarat (jika terjadi keluar yang tidak terduga).

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