SELECT FOR UPDATE错误结果

时间:2016-11-01 14:39:09

标签: sql multithreading postgresql transactions isolation-level

我遇到了PostgreSQL(可能不仅仅是psql)交易竞争条件的麻烦。我试图使用多个线程来完成这么简单的任务:

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语句行为正确。为什么呢?

1 个答案:

答案 0 :(得分:0)

原因是被阻止语句的快照比插入新行的事务旧,因此一旦删除锁就无法看到它。

您可以在以下语句中看到它,因为在READ COMMITTED隔离级别,每个语句都有自己的快照,因此第二个语句的快照包括新插入的行。

您可以使用REPEATABLE READ隔离级别。在这种情况下,你应该得到序列化错误(我没有测试,所以请尝试一下 - 也许你需要SERIALIZABLE)。然后你必须编写你的程序,以便在它发生序列化错误时重试事务,并且一切都应该有效。