Hầu hết có thể bạn đang gặp phải điều kiện cuộc đua . Khi bạn chạy chức năng của mình liên tiếp 1000 lần trong các giao dịch riêng biệt , một cái gì đó như thế này sẽ xảy ra:
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
...
Được viết lại nhiều hơn và đơn giản hóa dưới dạng hàm 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;
Câu hỏi liên quan với nhiều lời giải thích hơn:
Giải thích
-
Không chạy hai câu lệnh SQL riêng biệt. Điều đó tốn kém hơn và mở rộng khung thời gian cho các điều kiện cuộc đua. Một
UPDATE
với một truy vấn con thì tốt hơn nhiều. -
Bạn không cần PL / pgSQL cho nhiệm vụ đơn giản. Bạn vẫn có thể sử dụng PL / pgSQL,
UPDATE
giữ nguyên. -
Bạn cần phải khóa hàng đã chọn để bảo vệ khỏi các điều kiện của cuộc đua. Nhưng bạn không thể thực hiện việc này với hàm tổng hợp mà bạn đang sử dụng vì theo tài liệu :
-
Tôi nhấn mạnh đậm. May mắn thay, bạn có thể thay thế
min(id)
dễ dàng vớiORDER BY
tương đương /LIMIT 1
Tôi đã cung cấp ở trên. Cũng có thể sử dụng một chỉ mục. -
Nếu bàn lớn, bạn cần một chỉ mục trên
id
ít nhất. Giả sử rằngid
đã được lập chỉ mục dưới dạngPRIMARY KEY
, Điều đó sẽ giúp. Nhưng một phần chỉ mục đa cột bổ sung này có thể sẽ giúp nhiều hơn nữa :CREATE INDEX foo_idx ON table (id_type, id) WHERE id_used IS NULL;
Các giải pháp thay thế
Khóa tư vấn Có thể là cách tiếp cận ưu việt ở đây:
Hoặc bạn có thể muốn khóa nhiều hàng cùng một lúc :