9.5 以降:
PostgreSQL 9.5 以降のサポートINSERT ... ON CONFLICT (key) DO UPDATE
(およびON CONFLICT (key) DO NOTHING
)、つまり upsert。
との比較ON DUPLICATE KEY UPDATE
。
簡単な説明。
使用方法については、マニュアルを参照してください。具体的には、構文図のconflict_action句と説明文を参照してください。
以下に示す 9.4 以前のソリューションとは異なり、この機能は複数の競合する行で機能し、排他ロックや再試行ループを必要としません。
機能を追加するコミットはここにあり、その開発に関する議論はここにあります。
9.5 を使用していて、下位互換性を維持する必要がない場合は、ここで読むのをやめてください。
9.4 以前:
PostgreSQL にはビルトインUPSERT
(またはMERGE
) 機能がなく、同時使用に直面して効率的に行うことは非常に困難です。
この記事では、この問題について有益な詳細を説明しています。
一般に、次の 2 つのオプションから選択する必要があります。
- 再試行ループ内の個々の挿入/更新操作。また
- テーブルをロックしてバッチマージを行う
個々の行の再試行ループ
再試行ループで個々の行のアップサートを使用することは、挿入を同時に実行しようとする多数の接続が必要な場合に適したオプションです。
PostgreSQL のドキュメントには、データベース内のループでこれを実行できる便利な手順が含まれています。ほとんどの単純なソリューションとは異なり、更新の損失や競合の挿入を防ぎます。ただし、READ COMMITTED
モードでのみ機能し、トランザクションで行う唯一のことである場合にのみ安全です。トリガーまたはセカンダリ一意キーによって一意違反が発生した場合、関数は正しく機能しません。
この戦略は非常に非効率的です。実用的な場合は常に、作業をキューに入れ、代わりに以下で説明するように一括 upsert を実行する必要があります。
この問題に対して試みられた解決策の多くは、ロールバックを考慮していないため、更新が不完全になります。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 ステートメントですが、実際には同時実行セーフな upsert には有用なソリューションではありません。他のDBMS を使用MERGE
して upsert に使用する人へのアドバイスはたくさんありますが、実際には間違っています。
その他のデータベース: