sql >> データベース >  >> RDS >> PostgreSQL

PostgreSQLでUPSERT(MERGE、INSERT ... ON DUPLICATE UPDATE)する方法は?

    9.5以降:

    PostgreSQL9.5以降のサポートINSERT... ON CONFLICT(key)DO UPDATE (および ON CONFLICT(key)DO NOTHING )、つまりアップサート。

    ON DUPLICATE KEY UPDATEとの比較 。

    簡単な説明。

    使用法については、マニュアル、具体的には conflict_actionを参照してください。 シンタックスダイアグラムの句と説明テキスト。

    以下に示す9.4以前のソリューションとは異なり、この機能は複数の競合する行で機能し、排他的ロックや再試行ループを必要としません。

    機能を追加するコミットはここにあり、その開発に関する議論はここにあります。

    9.5を使用していて、下位互換性が必要ない場合は、今すぐ読むのをやめることができます

    9.4以前:

    PostgreSQLにはUPSERTが組み込まれていません (または MERGE )設備、および同時使用に直面してそれを効率的に行うことは非常に困難です。

    この記事では、問題について詳細に説明します。

    一般に、次の2つのオプションから選択する必要があります。

    • 再試行ループでの個々の挿入/更新操作。または
    • テーブルをロックしてバッチマージを実行する

    個々の行の再試行ループ

    挿入を同時に実行しようとする多数の接続が必要な場合は、再試行ループで個々の行のアップサートを使用するのが妥当なオプションです。

    PostgreSQLのドキュメントには、データベース内のループでこれを実行できる便利な手順が含まれています。ほとんどの単純なソリューションとは異なり、更新の損失や競合の挿入を防ぎます。 READ COMMITTEDでのみ機能します モードであり、それがトランザクションで行う唯一のことである場合にのみ安全です。トリガーまたはセカンダリ一意キーが一意の違反を引き起こす場合、この関数は正しく機能しません。

    この戦略は非常に非効率的です。実用的な場合はいつでも、作業をキューに入れ、代わりに以下に説明するように一括アップサートを実行する必要があります。

    この問題に対して試みられた多くの解決策はロールバックを考慮に入れていないため、更新が不完全になります。 2つのトランザクションは互いに競合します。それらの1つは正常にINSERT s;もう一方は重複キーエラーを受け取り、 UPDATEを実行します 代わりは。 UPDATE INSERTを待機しているブロック ロールバックまたはコミットします。ロールバックすると、 UPDATE 条件の再チェックはゼロ行に一致するため、 UPDATE 期待したアップサートが実際に行われていないことをコミットします。結果の行数を確認し、必要に応じて再試行する必要があります。

    いくつかの試みられた解決策はまた、SELECTレースを考慮していません。明白で単純なものを試してみる場合:

    -- THIS IS WRONG. DO NOT COPY IT. It's an EXAMPLE.
    
    BEGIN;
    
    UPDATE testtable
    SET somedata = 'blah'
    WHERE id = 2;
    
    -- Remember, this is WRONG. Do NOT COPY IT.
    
    INSERT INTO testtable (id, somedata)
    SELECT 2, 'blah'
    WHERE NOT EXISTS (SELECT 1 FROM testtable WHERE testtable.id = 2);
    
    COMMIT;
    

    次に、2つを同時に実行すると、いくつかの障害モードが発生します。 1つは、更新の再チェックに関するすでに説明した問題です。もう1つは、両方の UPDATE 同時に、ゼロ行を照合して続行します。次に、両方が EXISTSを実行します テスト。に行われます INSERT 。両方ともゼロ行を取得するため、両方とも INSERTを実行します 。 1つは重複キーエラーで失敗します。

    これが、再試行ループが必要な理由です。巧妙なSQLを使用すると、キーの重複エラーや更新の損失を防ぐことができると思うかもしれませんが、それはできません。行数を確認するか、重複するキーエラーを処理して(選択したアプローチに応じて)、再試行する必要があります。

    このために独自のソリューションを展開しないでください。メッセージキューの場合と同様に、おそらく間違っています。

    ロック付きバルクアップサート

    古い既存のデータセットにマージしたい新しいデータセットがある場合、一括アップサートを実行したい場合があります。これは非常に 個々の行のアップサートよりも効率的であり、実用的な場合は常に優先する必要があります。

    この場合、通常は次のプロセスに従います。

    • CREATE TEMPORARY テーブル

    • コピー または、新しいデータを一時テーブルに一括挿入します

    • LOCK ターゲットテーブルINEXCLUSIVE MODE 。これにより、他のトランザクションが SELECTできるようになります 、ただし、テーブルには変更を加えません。

    • UPDATE ... FROMを実行します 一時テーブルの値を使用した既存のレコードの数;

    • INSERTを実行します ターゲットテーブルにまだ存在していない行の数;

    • COMMIT 、ロックを解除します。

    たとえば、質問で与えられた例では、複数値の INSERTを使用します 一時テーブルにデータを入力するには:

    BEGIN;
    
    CREATE TEMPORARY TABLE newvals(id integer, somedata text);
    
    INSERT INTO newvals(id, somedata) VALUES (2, 'Joe'), (3, 'Alan');
    
    LOCK TABLE testtable IN EXCLUSIVE MODE;
    
    UPDATE testtable
    SET somedata = newvals.somedata
    FROM newvals
    WHERE newvals.id = testtable.id;
    
    INSERT INTO testtable
    SELECT newvals.id, newvals.somedata
    FROM newvals
    LEFT OUTER JOIN testtable ON (testtable.id = newvals.id)
    WHERE testtable.id IS NULL;
    
    COMMIT;
    

    関連資料

    • UPSERTwikiページ
    • PostgresのUPSERTisms
    • PostgreSQLでの重複更新時に挿入しますか?
    • http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
    • トランザクションでアップサート
    • 関数内のSELECTまたはINSERTは競合状態になりやすいですか?
    • SQL MERGE PostgreSQLwikiで
    • 最近のPostgresqlにUPSERTを実装するための最も慣用的な方法

    MERGEはどうですか ?

    SQL標準のMERGE 実際には、同時実行セマンティクスが十分に定義されておらず、最初にテーブルをロックせずにアップサーティングするのには適していません。

    これは、データのマージに非常に役立つOLAPステートメントですが、実際には、同時実行に安全なアップサートに役立つソリューションではありません。 MERGEを使用するために他のDBMSを使用している人々へのアドバイスはたくさんあります アップサートの場合ですが、実際には間違っています。

    その他のDB:

    • INSERT ... ON DUPLICATE KEY UPDATE MySQLで
    • MERGE MS SQL Serverから(ただし、 MERGEについては上記を参照してください 問題)
    • MERGE Oracleから(ただし、 MERGE については上記を参照) 問題)


    1. PHPPDOを使用してMacからSQLServerに接続するにはどうすればよいですか?

    2. MySQLでの大文字と小文字を区別する照合

    3. Oracle12cR2は現在ベータ版です

    4. JulianDay()関数がSQLiteでどのように機能するか