【问题标题】:SELECT FOR UPDATE wrong resultSELECT FOR UPDATE 错误结果
【发布时间】:2016-11-01 14:39:09
【问题描述】:

我遇到了 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 语句表现正确。为什么?

【问题讨论】:

    标签: sql multithreading postgresql transactions isolation-level


    【解决方案1】:

    原因是被阻塞语句的快照比插入新行的事务更旧,所以一旦锁被移除它就看不到它。

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

    您可以使用 REPEATABLE READ 隔离级别。在这种情况下,您应该会收到一个序列化错误(我没有对此进行测试,所以请尝试一下——也许您需要 SERIALIZABLE)。然后你必须编写你的程序,以便它在出现序列化错误时重试事务,并且一切都应该正常工作。

    【讨论】:

      猜你喜欢
      • 2011-06-28
      • 1970-01-01
      • 1970-01-01
      • 2016-07-19
      • 2013-09-23
      • 2017-12-15
      • 2017-12-05
      • 2014-03-10
      • 1970-01-01
      相关资源
      最近更新 更多