PostgreSQL で UPSERT (MERGE、INSERT ... ON DUPLICATE UPDATE) を実行する方法は? 質問する

PostgreSQL で UPSERT (MERGE、INSERT ... ON DUPLICATE UPDATE) を実行する方法は? 質問する

INSERT ... ON DUPLICATE UPDATEここで非常によく尋ねられる質問は、MySQL が呼び出し、操作の一部として標準でサポートされているupsert を実行する方法ですMERGE

PostgreSQL がこれを直接サポートしていないことを考えると (9.5 ページより前)、これをどのように行うのでしょうか? 次の点を考慮してください。

CREATE TABLE testtable (
    id integer PRIMARY KEY,
    somedata text NOT NULL
);

INSERT INTO testtable (id, somedata) VALUES
(1, 'fred'),
(2, 'bob');

ここで、タプル(2, 'Joe')、を「upsert」(3, 'Alan')すると、新しいテーブルの内容は次のようになります。

(1, 'fred'),
(2, 'Joe'),    -- Changed value of existing tuple
(3, 'Alan')    -- Added new tuple

について議論するときに人々が話しているのはこれですupsert。重要なのは、明示的なロックを使用するか、または結果として生じる競合状態を防ぐことによって、同じテーブルで複数のトランザクションが動作している場合でも、どのアプローチも安全でなければならないということです。

このトピックについては、PostgreSQL で重複更新時に挿入しますか?ただし、これは MySQL 構文の代替に関するもので、時間の経過とともに無関係な詳細がかなり増えてきました。私は決定的な回答を作成中です。

これらのテクニックは、「存在しない場合は挿入し、存在しない場合は何もしない」、つまり「重複キーを無視して ... を挿入する」場合にも役立ちます。

ベストアンサー1

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ただし、これは モードでのみ機能し、トランザクションで実行する唯一の操作である場合にのみ安全です。 トリガーまたはセカンダリ一意キーによって一意違反が発生する場合、この関数は正しく機能しません。

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

この問題に対する多くの解決策はロールバックを考慮していないため、不完全な更新になります。2 つのトランザクションが競合し、一方は正常にINSERTs を実行し、もう一方は重複キー エラーが発生し、UPDATE代わりに を実行します。UPDATEブロックは、 がロールバックまたはコミットするのを待機しますINSERT。ロールバックすると、UPDATE条件の再チェックで 0 行が一致するため、UPDATEコミットしても、期待した upsert は実際には実行されません。結果の行数をチェックし、必要に応じて再試行する必要があります。

試みられた解決策の中には、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とも0 行を取得するため、両方とも を実行します。一方は重複キー エラーで失敗します。INSERTINSERT

このため、再試行ループが必要になります。巧妙な 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 ステートメントですが、同時実行安全なアップサートには実際には便利なソリューションではありません。他の DBMS を使用してMERGEアップサートを使用する人へのアドバイスはたくさんありますが、実際には間違っています。

その他のDB:

おすすめ記事