BEGIN;
SELECT * FROM t WHERE id = 1;
DELETE FROM t WHERE id = 1;
INSERT INTO t (id, value) VALUES (1, 'thread X'); -- X = 1,2,3,..
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation
COMMIT;
但是线程在这些事务中发生冲突,因此会执行多个插入(主键冲突错误)。所以我尝试使用SELECT FOR UPDATE语句:
BEGIN;
SELECT * FROM t WHERE id = 1 FOR UPDATE;
DELETE FROM t WHERE id = 1;
INSERT INTO t (id, value) VALUES (1, 'thread X'); -- X = 1,2,3,..
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation
COMMIT;
事务正在阻止FOR UPDATE语句等待其他线程提交。
然而,"信号量上升" (在另一个线程事务提交后唤醒该语句)虽然数据在表中正确可用(从更快线程的INSERT语句中),但DBMS返回空结果集:
BEGIN;
SELECT * FROM t WHERE id = 1 FOR UPDATE; -- blocking ... then return 0 records WRONG
SELECT * FROM t WHERE id = 1 FOR UPDATE; -- second try ... returns 1 record CORRECT
DELETE FROM t WHERE id = 1;
INSERT INTO t (id, value) VALUES (1, 'thread X'); -- X = 1,2,3,..
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation
COMMIT;
如上所示,第二个(重复的)select语句行为正确。为什么呢?
答案 0 :(得分:0)
原因是被阻止语句的快照比插入新行的事务旧,因此一旦删除锁就无法看到它。
您可以在以下语句中看到它,因为在READ COMMITTED隔离级别,每个语句都有自己的快照,因此第二个语句的快照包括新插入的行。
您可以使用REPEATABLE READ隔离级别。在这种情况下,你应该得到序列化错误(我没有测试,所以请尝试一下 - 也许你需要SERIALIZABLE)。然后你必须编写你的程序,以便在它发生序列化错误时重试事务,并且一切都应该有效。