[解決済み】PostgreSQLでUPSERT(MERGE、INSERT ... ON DUPLICATE UPDATE)する方法とは?
質問
よくある質問は、アップサートを行う方法です。
INSERT ... ON DUPLICATE UPDATE
の一部として、標準ではサポートされています。
MERGE
演算を行う。
PostgreSQLが(pg 9.5以前は)直接サポートしていないことを考えると、どのようにこれを行うのでしょうか?次のように考えてみてください。
CREATE TABLE testtable (
id integer PRIMARY KEY,
somedata text NOT NULL
);
INSERT INTO testtable (id, somedata) VALUES
(1, 'fred'),
(2, 'bob');
ここで、タプルを "upsert" したい場合を想像してください。
(2, 'Joe')
,
(3, 'Alan')
ということで、新しいテーブルのコンテンツはこうなります。
(1, 'fred'),
(2, 'Joe'), -- Changed value of existing tuple
(3, 'Alan') -- Added new tuple
を議論するとき、人々はそれについて話しているのです。
upsert
. 重要なのは、どのようなアプローチであっても
同じテーブルで作業している複数のトランザクションがあっても安全です。
- 明示的なロックをかけるか、レースコンディションを防ぐか、どちらかです。
このトピックは、以下のサイトで詳しく説明されています。 PostgreSQLで重複更新の挿入は可能ですか? しかし、それは MySQL 構文の代替案に関するものであり、時間の経過とともに関連性のない詳細がかなり増えています。私は決定的な答えに取り組んでいるところです。
これらのテクニックは、quot;insert if not exists, otherwise do nothing" つまり "insert ... on duplicate key ignore" にも有効です。
解決方法は?
9.5以降です。
PostgreSQL 9.5 およびそれ以降のサポート
INSERT ... ON CONFLICT (key) DO UPDATE
(そして
ON CONFLICT (key) DO NOTHING
)、すなわちアップサートを行います。
との比較
ON DUPLICATE KEY UPDATE
.
簡単な説明 .
使用方法については マニュアル - 特に コンフリクトアクション という節があり、シンタックスダイアグラムでは 説明文 .
以下に示す 9.4 以前の解決策とは異なり、この機能は競合する複数の行に対して動作し、排他ロックや再試行ループは必要ありません。
この機能を追加したコミットはこちら と 開発に関するディスカッションはこちら .
9.5を使用していて、後方互換性が必要ない場合は、今すぐ読むのをやめてもかまいません。 .
9.4以前のバージョン。
PostgreSQLには組み込みの
UPSERT
(または
MERGE
そのため、同時使用に対して効率的に行うことは非常に困難です。
この記事では、この問題について有益な詳細を説明しています。 .
一般的には、2つの選択肢のどちらかを選ぶ必要があります。
- 再試行ループ内の個々の挿入/更新操作、または
- テーブルをロックしてバッチマージする
個別行リトライループ
再試行ループで個別の行のアップサートを使用することは、挿入を実行するために多くの接続を同時に使用する場合、合理的なオプションです。
PostgreSQLのドキュメントには、これをデータベース内のループで実行させる便利なプロシジャが含まれています。
. これは、多くの素朴な解決策とは異なり、更新の喪失や挿入の競合から保護します。この方法は
READ COMMITTED
モードであり、トランザクションで行うことがこれだけである場合のみ安全ですが。この関数は、トリガーやセカンダリユニークキーがユニーク違反を引き起こす場合、正しく動作しません。
この戦略は非常に非効率的です。実用的な場合はいつでも、作業をキューに入れ、代わりに以下のようにバルクアップサートを行うべきです。
この問題に対する多くの解決策は、ロールバックを考慮していないため、不完全な更新になってしまう。2つのトランザクションが互いに競合し、そのうちの1つが正常に
INSERT
もう一方は重複キーエラーになり
UPDATE
の代わりに その
UPDATE
を待つブロックがあります。
INSERT
条件を再確認すると、0 行にマッチします。
UPDATE
をコミットしても、実際には期待したアップサートは行われません。結果の行数をチェックし、必要な場合は再試行する必要があります。
また、いくつかの解決策は、SELECTレースについて考慮していません。もし、明白で単純なものを試すなら。
UPDATE
では、2つ同時に実行した場合、いくつかの失敗モードがあります。ひとつは、すでに述べた更新の再チェックの問題です。もうひとつは、両方の
-- 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;
を同時に行い、0列をマッチングして続ける。次に二人は
UPDATE
テストが発生します。
以前
は
EXISTS
. どちらも0行になるので、どちらも
INSERT
. 一方は重複キーエラーで失敗します。
そのため、再試行ループが必要なのです。巧妙なSQLを使えば重複キーエラーや更新の失敗を防げると思うかもしれませんが、そうではありません。行数をチェックするか、重複キーエラーを処理するか(選択した方法による)、そして再試行が必要です。
これについては、自分で解決策を講じないでください。メッセージ・キューイングと同じで、おそらく間違っています。
ロック付き一括アップサート
新しいデータセットを古い既存のデータセットにマージしたい場合、一括アップサートを行いたいことがあります。これは 大いに は、個々の行のアップサートに比べてより効率的であり、実用的な場合は常に優先されるべきです。
この場合、一般的には以下のような流れになります。
-
INSERT
aCREATE
テーブル -
TEMPORARY
あるいは、新しいデータを temp テーブルに一括挿入します。 -
COPY
ターゲットテーブルLOCK
. これにより、他のトランザクションがIN EXCLUSIVE MODE
しかし、テーブルには一切変更を加えません。 -
を実行します。
SELECT
を、temp テーブルの値を使って既存のレコードに追加する。 -
を行う。
UPDATE ... FROM
ターゲットテーブルにまだ存在しない行の -
INSERT
ロックを解除します。
例えば、質問で出された例では、多値の
COMMIT
を使用して、temp テーブルに入力します。
INSERT
関連する読み物
- UPSERTのWikiページ
- Postgres における UPSERTisms
- PostgreSQLで重複更新時の挿入(Insert)?
- http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
- トランザクションを伴うアップサート
- 関数内のSELECTやINSERTは、レースコンディションを起こしやすいですか?
-
SQL
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;
PostgreSQL の wiki にある - Postgresql で UPSERT を実装する最も一般的な方法です。
についてはどうでしょうか?
MERGE
?
標準SQL
MERGE
実際、同時実行のセマンティクスがうまく定義されておらず、最初にテーブルをロックせずにアップサートを行うのには適していません。
OLAPのデータマージにはとても便利な文ですが、実はコンカレンシーセーフなアップサートに役立つソリューションではありません。他の DBMS を使っている人には
MERGE
をアップサートに使用することはできますが、実はそれは間違いです。
その他のDB
-
MERGE
MySQLで -
INSERT ... ON DUPLICATE KEY UPDATE
MS SQL Serverから (ただし、上記のMERGE
の問題) -
MERGE
オラクルより (ただし、上記のMERGE
の問題)
関連
-
[解決済み] libpqのソースはどこで手に入りますか?
-
[解決済み] PostgreSQL コマンドラインユーティリティ: psql を終了する方法
-
[解決済み] PostgreSQLのユーザーパスワードを変更する方法を教えてください。
-
[解決済み] MySQLテーブルへの挿入または存在する場合の更新
-
[解決済み] PostgreSQLで重複して更新された場合の挿入?
-
[解決済み] SQL ServerにおけるINSERT OR UPDATEに関する解決策
-
[解決済み] SQLite - UPSERT *not* INSERT or REPLACE
-
[解決済み】Mac OS XでPostgreSQLサーバーを起動するには?
-
[解決済み】最後に挿入されたIDを取得するPostgreSQL関数
-
[解決済み】ubuntuでpostgresqlを徹底的にパージして再インストールする方法とは?終了
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] Postgresql - DBへの自動接続があるため、データベースを削除できない。
-
[解決済み] ERROR: テーブル "tablename" の更新または削除は外部キー制約に違反します。
-
[解決済み] Postgresの最大接続数を増やすには?
-
[解決済み] Ubuntu 18.04でPostgreSQLを再起動する方法
-
[解決済み] PostgreSQL の 'NOT IN' とサブクエリ
-
[解決済み】PostgreSQLのエラーです。Fatal: ロール "username" が存在しません。
-
[解決済み】psql: FATAL: ユーザー "postgres" の Ident 認証に失敗しました。
-
[解決済み】Postgresに一括挿入を行う最速の方法は何ですか?
-
[解決済み】psql: FATAL: ユーザー "dev" のピア認証に失敗しました。
-
[解決済み】Docker化したPostgresデータベースのデータをボリュームで永続化する方法