3

テーブルにレコードを挿入したいのですが、レコードが既に存在する場合はその ID を取得し、そうでない場合は挿入を実行して新しいレコードの ID を取得します。

何百万ものレコードを挿入する予定ですが、これを効率的に行う方法がわかりません。私が今していることは、選択を実行してレコードが既に存在するかどうかを確認し、存在しない場合は挿入して、挿入されたレコードの ID を取得することです。テーブルが大きくなるにつれて、それSELECTが私を殺そうとしていると思います。

python で psycopg2 を使用して現在行っていることは次のようになります。

select = ("SELECT id FROM ... WHERE ...", [...])
cur.execute(*select)
if not cur.rowcount:
    insert = ("INSERT INTO ... VALUES ... RETURNING id", [...])
    cur.execute(*insert)
rid = cur.fetchone()[0]

次のようなストアド プロシージャで何かを行うことは可能でしょうか。

BEGIN
    EXECUTE sql_insert;
    RETURN id;
    EXCEPTION WHEN unique_violation THEN
        -- return id of already existing record
        -- from the exception info ?
END;

このようなケースを最適化する方法についてのアイデアはありますか?

4

1 に答える 1

2

まず第一に、これは明らかに決して言及されていないものではありません。ただし、同様の同時実行の問題が適用されます。UPSERTUPDATE

この種のタスクでは常に競合状態が発生しますが、データ変更 CTE (PostgreSQL 9.1 で導入) を使用して ID を 1回だけ照会しな​​がら、それを非常に小さなタイム スロットに最小限に抑えることができます。

与えられたテーブルtbl:

CREATE TABLE tbl(tbl_id serial PRIMARY KEY, some_col text UNIQUE);

次のクエリを使用します。

WITH x AS (SELECT 'baz'::text AS some_col) -- enter value(s) once

   , y AS (
   SELECT x.some_col
        , (SELECT t.tbl_id FROM tbl t WHERE t.some_col = x.some_col) AS tbl_id
   FROM   x    
   )

   , z AS (
   INSERT INTO tbl(some_col)
   SELECT y.some_col
   FROM   y
   WHERE  y.tbl_id IS NULL
   RETURNING tbl_id
)

SELECT COALESCE(
         (SELECT tbl_id FROM z)
        ,(SELECT tbl_id FROM y)
       );
  • CTExは便宜上のものです。値は 1 回入力してください。
  • CTEyは tbl_id を取得します (既に存在する場合)。
  • CTEzは新しい行を挿入します - そうでない場合。
  • 最終的SELECTには、構造を持つテーブルで別のクエリを実行することを回避しますCOALESCE

現在、同時トランザクションが some_col = 'foo' で CTEyとの間で新しい行をコミットすると、これはまだ失敗するz可能性がありますが、それは非常にまれです。これが発生すると、重複キー違反が発生し、再試行する必要があります。何も失われませんでした。同時書き込みに直面していない場合は、これを忘れることができます。

これをplpgsql関数に入れて、重複キーエラーでクエリを自動的に再実行できます。

このセットアップでは 2 つのインデックスが必要であることは言うまでもありません (CREATE TABLE上記のステートメントに表示されているように)。

  • UNIQUEorPRIMARY KEY制約tbl_id(これは型serialです!)
  • 別のUNIQUEまたはPRIMARY KEY制約some_col

どちらもインデックスを自動的に実装します。

于 2012-11-09T19:12:02.327 に答える