9.5以降:
PostgreSQL 9.5以降のサポートINSERT ... ON CONFLICT UPDATE(およびON CONFLICT DO NOTHING)、つまりupsert。
との比較ON DUPLICATE KEY UPDATE。
簡単な説明。
使用方法について参照マニュアル、具体的- CONFLICT_ACTIONの構文図の句、および説明文を。
以下に示す9.4以前のソリューションとは異なり、この機能は複数の競合する行で機能し、排他ロックや再試行ループを必要としません。
機能を追加するコミットはここにあり、その開発に関する議論はここにあります。
9.5を使用していて、下位互換性を維持する必要がない場合は、今すぐ読むのをやめることができます。
9.4以前:
PostgreSQLには組み込みUPSERT(またはMERGE)の機能がなく、同時に使用する場合に効率的に行うことは非常に困難です。
この記事では、問題について有用な詳細を説明します。
通常、次の2つのオプションから選択する必要があります。
- 再試行ループでの個々の挿入/更新操作。または
- テーブルのロックとバッチマージの実行
個別の行再試行ループ
挿入を実行しようとする多数の接続が同時に必要な場合は、再試行ループで個々の行のアップサートを使用するのが妥当なオプションです。
PostgreSQLのドキュメントには、データベース内のループでこれを実行できる便利な手順が含まれています。ほとんどの素朴なソリューションとは異なり、更新の喪失や競合の挿入を防ぎます。ただし、これはREAD COMMITTEDモードでのみ機能し、トランザクションで唯一実行する場合にのみ安全です。トリガーまたは2次固有キーが固有違反を引き起こした場合、関数は正しく機能しません。
この戦略は非常に非効率的です。実用的な場合は常に、作業をキューに入れて、以下で説明するように一括アップサートを実行する必要があります。
この問題に対して試行された解決策の多くはロールバックを考慮していないため、更新が不完全になります。2つのトランザクションは互いに競合します。それらの1つが正常に実行されINSERTます。もう1つは重複キーエラーを受け取り、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を使用すると、重複キーエラーや更新の損失を防ぐことができると思うかもしれませんが、それはできません。行数を確認するか、重複したキーエラーを処理して(選択したアプローチに応じて)、再試行する必要があります。
このために独自のソリューションをロールバックしないでください。メッセージのキューイングと同様に、おそらく間違っています。
ロック付きバルクアップサート
古いデータセットにマージする新しいデータセットがある場合に、一括アップサートを実行したい場合があります。これは、個々の行のアップサートよりもはるかに効率的であり、実用的であればいつでも推奨されます。
この場合、通常は次のプロセスに従います。
CREATETEMPORARYテーブル
COPY または新しいデータを一時テーブルに一括挿入する
LOCKターゲット表IN EXCLUSIVE 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;
関連読書
どうMERGEですか?
SQL標準は、MERGE実際には不十分に定義された並行性セマンティクスを持ち、最初にテーブルをロックせずに更新することには適していません。
これは、データのマージに非常に役立つOLAPステートメントですが、同時実行セーフアップサートに実際に役立つソリューションではありません。MERGEアップサートに使用するために他のDBMS を使用している人々へのアドバイスはたくさんありますが、それは実際には間違っています。
その他のDB: