ほとんどの場合、競合状態に遭遇しています。 。関数を1000回連続して実行すると、個別のトランザクションが実行されます。 、次のようなことが起こります:
T1 T2 T3 ...
SELECT max(id) -- id 1
SELECT max(id) -- id 1
SELECT max(id) -- id 1
...
Row id 1 locked, wait ...
Row id 1 locked, wait ...
UPDATE id 1
...
COMMIT
Wake up, UPDATE id 1 again!
COMMIT
Wake up, UPDATE id 1 again!
COMMIT
...
SQL関数として大幅に書き直され、簡略化されています:
CREATE OR REPLACE FUNCTION get_result(val1 text, val2 text)
RETURNS text AS
$func$
UPDATE table t
SET id_used = 'Y'
, col1 = val1
, id_used_date = now()
FROM (
SELECT id
FROM table
WHERE id_used IS NULL
AND id_type = val2
ORDER BY id
LIMIT 1
FOR UPDATE -- lock to avoid race condition! see below ...
) t1
WHERE t.id_type = val2
-- AND t.id_used IS NULL -- repeat condition (not if row is locked)
AND t.id = t1.id
RETURNING id;
$func$ LANGUAGE sql;
より多くの説明を伴う関連質問:
説明
-
2つの別々のSQLステートメントを実行しないでください。これはより高価であり、競合状態の時間枠を広げます。 1つの
UPDATE
サブクエリを使用する方がはるかに優れています。 -
単純なタスクにはPL/pgSQLは必要ありません。あなたはまだできます PL / pgSQL、
UPDATE
を使用します 同じままです。 -
競合状態から防御するには、選択した行をロックする必要があります。ただし、ドキュメントごと :
-
大胆な強調鉱山。幸い、
min(id)
を置き換えることができます 同等のORDER BY
で簡単に /LIMIT 1
上記で提供しました。インデックスも同様に使用できます。 -
テーブルが大きい場合は、必要
id
のインデックス 少なくとも。id
と仮定します すでにPRIMARY KEY
としてインデックス付けされています 、それは助けになるでしょう。ただし、この追加の部分的な複数列のインデックス おそらくもっと多く役立つでしょう :CREATE INDEX foo_idx ON table (id_type, id) WHERE id_used IS NULL;
代替ソリューション
アドバイザリーロック ここでは優れたアプローチかもしれません:
または、一度に多くの行をロックすることもできます。 :