一括でwp_insert_postとadd_post_metaへの高速な方法


16

〜1,500行と97列で構成されるcsvファイルを挿入します。完全なインポートには約2〜3時間かかりますが、方法があればこれを改善したいと思います。現在、各行に対して$ post_id = wp_insert_postを実行してから、各行に関連する97個の列に対してadd_post_metaを実行しています。これはかなり非効率的です...

post_idを取得してpostとそのpost_meta値の関係を維持できるように、これを実行するより良い方法はありますか?

今私はこれをローカルマシンでwampで試していますが、VPSで実行します


以下のWPのヒントに加えて、MySQLでInnoDBを使用し、この回答に従ってバッチでトランザクションをコミットすることもご覧ください。
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 );

だから、これは試してみるものかもしれません;-)

投稿をインポートする publishの代わりにドラフトすることも、それぞれのユニークなスラッグを見つける遅いプロセスがスキップされるので、物事をスピードアップします。たとえば、後で小さなステップで公開することもできますが、この種のアプローチではインポートされた投稿に何らかの方法でマークを付ける必要があるため、後で下書きを公開するだけではありません。これには、慎重な計画と、おそらくカスタムコーディングが必要です。

たとえば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
);

たとえばasを使用$override_slug = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix"$suffixて試行すると$post_id、予想どおり、$post_id常に0新しい投稿用であることに注意してください。ただし、PHPで一意の数値を生成するにはさまざまな方法がありますuniqid( '', true )。ただし、このフィルターは慎重に使用して、固有のスラッグがあることを確認してください。たとえば、後でpost_name確実にグループカウントクエリを実行できます。

別のオプションは使用することです WP-CLIをタイムアウトを回避することです。たとえば、.csvファイルを使用して20,000の投稿またはページを作成するために投稿された私の回答を参照してください

次にimport.php、WP-CLIコマンドでカスタムPHPインポートスクリプトを実行できます。

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レベルでの操作が大幅に高速化されます。SET autocommit=0;挿入の前にaを送信し、COMMIT;その後に送信します。
オットー

興味深い、ありがとう!家に帰ったらテストする必要があります。
コーリーローウェル

@Otto、すばらしいヒントをありがとう。したがって$wpdb->query('SET autocommit = 0;');、挿入の前に行うことができますが$wpdb->query('START TRANSACTION;');、その場合はスキップできますか?MySQLマニュアルをチェックして、それについてさらに学習します;-)乾杯。
バージール

1
良い点マーク。これらが挿入のみで更新ではない場合wp_suspend_cache_addition( true )、オブジェクトキャッシュにデータを入れないでください。また、@ birgireは、これをMyISAMでテストしなかったことに言及しました-気にしないでください。ストレージエンジンはトランザクションをサポートしないため、自動コミットの設定またはトランザクションの開始による影響はありません。
コブシェニン

1
素晴らしいヒント@Otto。以前のクエリでは38秒かかっていましたが、今では1秒かかります。
アンナプルナ

5

IDを取得するには投稿を挿入する必要がありますが、$wpdb->postmetaテーブルの構造は非常に単純です。おそらく、MySQL docsの次のようなストレートなINSERT INTOステートメントを使用できます。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}");

エンコード、シリアル化、エスケープ、エラーチェック、複製などは処理されませんが、より高速になると思います(試したことはありませんが)。

徹底的なテストを行わずに実稼働サイトでこれを行うことはありません。1〜2回しか実行しなければならない場合は、コア機能を使用し、インポート中に長い昼食をとります。


生データをテーブルに挿入するのではなく、長い昼食を取ると思います。Wordpressがすでに行うことを書き直す意味はありません。
コーリーローウェル

1
これがmysqlインジェクションの発生方法であるため、これを使用しないでください。
OneOfOne

すべてがハードコードされている@OneOfOne。注入は、定義上、ユーザーが入力しなければ発生しません。それが「注入」の性質です。OPは、彼の制御下にあるコードを使用して、彼の制御下にある.csvファイルからデータをインポートしています。第三者が何かを注入する機会はありません。コンテキストに注意してください。
s_ha_dum

私から+1、私は20個のカスタムフィールド値を追加する必要があり、これは「add_post_meta」よりはるかに高速
でした-Zorox

1
OPがCSVファイルをインポートする前に徹底的にチェックすることは期待できないため、ユーザー入力として、少なくとも->prepare()SQLステートメントとして扱う必要があります。あなたのシナリオでは、CSVのID列に次のようなものが含まれていると1, 'foo', 'bar'); DROP TABLE wp_users; --どうなりますか?おそらく何か悪い。
コブシェニン

5

これを追加する必要がありました。

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

これはdo_all_pingspingback、エンクロージャ、トラックバック、およびその他のpingを処理します(リンク:https : //developer.wordpress.org/reference/functions/do_all_pings/)をスキップすることに注意してください。コードを見ると、このremove_action行を削除した後も保留中のpingbacks / trackbacks / enclosuresは処理されますが、完全にはわかりません。

更新:私も追加しました

    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、致命的なエラーかなど...)、その後、変更は保存され、IN DB BE文句を言いません!

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

update_option("something", "value");     

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

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

この場合update_option、DBには保存されません!

そのため、(予期しない終了が発生した場合COMMITshutdown備えて)機能として事前登録として登録することをお勧めします。

register_shutdown_function( function(){
    $GLOBALS['wpdb']->query( 'COMMIT;' );
} );
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.