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を使用すると、重複キーエラーや更新の損失を防ぐことができると思うかもしれませんが、それはできません。行数を確認するか、重複したキーエラーを処理して(選択したアプローチに応じて)、再試行する必要があります。
このために独自のソリューションをロールバックしないでください。メッセージのキューイングと同様に、おそらく間違っています。
ロック付きバルクアップサート
古いデータセットにマージする新しいデータセットがある場合に、一括アップサートを実行したい場合があります。これは、個々の行のアップサートよりもはるかに効率的であり、実用的であればいつでも推奨されます。
この場合、通常は次のプロセスに従います。
CREATE
TEMPORARY
テーブル
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: