วิธีที่เร็วกว่าในการ wp_insert_post & add_post_meta เป็นกลุ่ม


16

ฉันมีไฟล์ csv ที่ฉันต้องการแทรกซึ่งประกอบด้วย ~ 1,500 แถวและ 97 คอลัมน์ ใช้เวลาประมาณ 2-3 ชั่วโมงในการนำเข้าอย่างเต็มรูปแบบและฉันต้องการปรับปรุงสิ่งนี้หากมีวิธี ขณะนี้สำหรับแต่ละแถวฉันกำลังทำ $ post_id = wp_insert_post จากนั้นเป็น add_post_meta สำหรับคอลัมน์ 97 คอลัมน์ที่เชื่อมโยงกับแต่ละแถว มันไม่มีประสิทธิภาพเลย ...

มีวิธีที่ดีกว่าในการทำเรื่องนี้ในแบบที่ a จะได้รับ 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"ด้วย$suffixขณะ$post_idนั้นเราจะทราบว่า$post_idจะมี0การโพสต์ใหม่เสมอตามที่คาดไว้ มีหลายวิธี แต่การสร้างตัวเลขที่ไม่ซ้ำกันใน PHP uniqid( '', true )มีเช่น แต่ใช้ตัวกรองนี้ด้วยความระมัดระวังเพื่อให้แน่ใจว่าคุณมีทากที่ไม่ซ้ำกัน เราสามารถเรียกใช้แบบสอบถามจำนวนกลุ่มหลังจากนั้นpost_nameเพื่อให้แน่ใจ

ตัวเลือกอื่นจะใช้WP-CLIเพื่อหลีกเลี่ยงการหมดเวลา ดูเช่นคำตอบของฉันโพสต์สำหรับการสร้าง 20,000 โพสต์หรือหน้าโดยใช้ไฟล์. csv?

จากนั้นเราสามารถเรียกใช้สคริปต์นำเข้า PHP แบบกำหนดเองของเราimport.phpด้วยคำสั่ง WP-CLI:

wp eval-file import.php

ยังหลีกเลี่ยงการนำเข้าชนิดโพสต์ลำดับชั้นจำนวนมากเนื่องจาก wp-admin UI ปัจจุบันจัดการได้ไม่ดี ดูเช่นประเภทโพสต์ที่กำหนดเอง - รายการโพสต์ - หน้าจอสีขาวแห่งความตาย

นี่คือเคล็ดลับที่ยอดเยี่ยมจาก @otto:

ก่อนแทรกจำนวนมากให้ปิดใช้งานautocommitโหมดอย่างชัดเจน:

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

หลังจากแทรกจำนวนมากให้รัน:

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

ฉันยังคิดว่ามันเป็นความคิดที่ดีที่จะทำความสะอาดเช่น:

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

ฉันไม่ได้ทดสอบนี้ในMyISAMแต่ควรจะทำงานในInnoDB

ในฐานะที่เป็นที่กล่าวถึงโดย @kovshenin เคล็ดลับนี้จะไม่ทำงานสำหรับMyISAM


6
นอกจากนี้คุณยังสามารถใช้ฟังก์ชันแบบสอบถามเพื่อปิดใช้งานการจัดรูปแบบอัตโนมัติก่อนหน้านี้และจากนั้นกระทำด้วยตนเองหลังจากการแทรกเสร็จสิ้น สิ่งนี้จะช่วยเพิ่มความเร็วในการทำงานที่ระดับ DB เมื่อทำการแทรกจำนวนมาก เพียงส่ง a SET autocommit=0;ก่อนหน้าแทรกแล้วตามด้วยCOMMIT;ภายหลัง
อ็อตโต

น่าสนใจขอบคุณมากสำหรับสิ่งนั้น! ฉันจะต้องทดสอบมันเมื่อฉันกลับถึงบ้าน
Corey Rowell

@Otto ขอบคุณสำหรับเคล็ดลับที่ดี ดังนั้นเราสามารถทำได้$wpdb->query('SET autocommit = 0;');ก่อนที่จะแทรก แต่เราสามารถข้าม$wpdb->query('START TRANSACTION;');ในกรณีนั้นได้หรือไม่? ฉันจะตรวจสอบคู่มือ MySQL เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับมัน ;-) ไชโย
Birgire

1
จุดที่ดีมาร์ค หากสิ่งเหล่านี้เป็นเพียงส่วนแทรกและไม่ใช่การอัปเดตคุณwp_suspend_cache_addition( true )ควรช่วยอย่าวางสิ่งของในแคชวัตถุ @birgire ยังกล่าวว่าพวกเขาไม่ได้ทดสอบกับ MyISAM - ไม่ต้องกังวลเครื่องมือจัดเก็บข้อมูลไม่รองรับการทำธุรกรรมดังนั้นการตั้งค่าการเติมข้อความอัตโนมัติหรือการเริ่มต้นธุรกรรมจะไม่มีผล
kovshenin

1
สุดยอดเคล็ดลับ @Otto ข้อความค้นหาของฉันก่อนหน้านี้ใช้เวลา 38 วินาทีในขณะนี้ใช้เวลา 1 วินาที
Annapurna

5

คุณจะต้องแทรกการโพสต์เพื่อรับ ID ของคุณ แต่$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 จะทำอยู่แล้ว
คอเรย์ Rowell

1
นี่เป็นวิธีที่การฉีด mysql เกิดขึ้นดังนั้นโปรดอย่าใช้สิ่งนี้
OneOfOne

ทุกอย่างถูกเข้ารหัสยาก @OneOfOne การฉีดไม่ได้ - ไม่สามารถนิยามได้ - เกิดขึ้นโดยไม่ต้องป้อนข้อมูลจากผู้ใช้ นั่นคือธรรมชาติของ "การฉีด" OP กำลังนำเข้าข้อมูลจากไฟล์. csv ซึ่งอยู่ภายใต้การควบคุมของเขาโดยใช้รหัสภายใต้การควบคุมของเขา ไม่มีโอกาสสำหรับบุคคลที่สามที่จะฉีดอะไร โปรดใส่ใจบริบท
s_ha_dum

+1 จากฉันฉันต้องการเพิ่ม 20 ค่าเขตข้อมูลศุลกากรและนี่เร็วกว่า "add_post_meta"
Zorox

1
คุณไม่สามารถคาดหวังได้ว่า OP จะตรวจสอบไฟล์ CSV อย่างละเอียดก่อนที่จะนำเข้าดังนั้นคุณควรถือว่าเป็นข้อมูลป้อนเข้าของผู้ใช้และอย่างน้อย->prepare()คำสั่ง SQL ของคุณ ในสถานการณ์ของคุณสิ่งที่จะเกิดขึ้นถ้า ID คอลัมน์ใน CSV ที่มีสิ่งที่ชอบ1, 'foo', 'bar'); DROP TABLE wp_users; --? อาจเป็นสิ่งที่ไม่ดี
kovshenin

5

ฉันต้องเพิ่มสิ่งนี้:

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

โปรดทราบว่าสิ่งนี้จะข้ามdo_all_pingsซึ่งดำเนินการ pingbacks, สิ่งที่แนบมา, trackbacks และ pings อื่น ๆ (ลิงก์: 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 );

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จะไม่ถูกบันทึกใน DB!

ดังนั้นคำแนะนำที่ดีที่สุดคือให้ทำการCOMMITลงทะเบียนshutdownเป็น precatuion (ในกรณีที่มีทางออกที่ไม่คาดคิดเกิดขึ้น)

register_shutdown_function( function(){
    $GLOBALS['wpdb']->query( 'COMMIT;' );
} );
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.