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
については上記を参照) 問題)