Kemungkinan besar Anda mengalami kondisi balapan . Saat Anda menjalankan fungsi Anda 1000 kali berturut-turut dengan cepat dalam transaksi terpisah , sesuatu seperti ini terjadi:
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
...
Sebagian besar ditulis ulang dan disederhanakan sebagai fungsi 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;
Pertanyaan terkait dengan lebih banyak penjelasan:
Jelaskan
-
Jangan menjalankan dua pernyataan SQL terpisah. Itu lebih mahal dan memperlebar kerangka waktu untuk kondisi balapan. Satu
UPDATE
dengan subquery jauh lebih baik. -
Anda tidak perlu PL/pgSQL untuk tugas sederhana. Anda tetap bisa gunakan PL/pgSQL,
UPDATE
tetap sama. -
Anda perlu mengunci baris yang dipilih untuk bertahan melawan kondisi balapan. Tetapi Anda tidak dapat melakukan ini dengan fungsi agregat yang Anda tuju karena, per dokumentasi :
-
Penekanan saya yang berani. Untungnya, Anda dapat mengganti
min(id)
mudah denganORDER BY
yang setara /LIMIT 1
saya sediakan di atas. Dapat menggunakan indeks juga. -
Jika mejanya besar, Anda perlu indeks pada
id
paling sedikit. Dengan asumsi bahwaid
sudah diindeks sebagaiPRIMARY KEY
, itu akan membantu. Tetapi indeks multikolom parsial tambahan ini mungkin akan membantu lebih banyak :CREATE INDEX foo_idx ON table (id_type, id) WHERE id_used IS NULL;
Solusi alternatif
Kunci saran Mungkin pendekatan yang unggul di sini:
Atau Anda mungkin ingin mengunci banyak baris sekaligus :