PostgreSQL FOR UPDATE with ORDER BY和LIMIT interation

时间:2017-09-13 04:13:21

标签: sql postgresql

根据https://www.postgresql.org/docs/9.5/static/sql-select.html

  

可以在READ COMMITTED上运行SELECT命令   事务隔离级别并使用ORDER BY和一个锁定子句来实现   无序返回行。这是因为首先应用ORDER BY。   该命令对结果进行排序,但可能会阻止尝试获取结果   锁定一行或多行。一旦SELECT解除阻塞,一些   排序列值可能已被修改,导致这些行   似乎是出了问题(尽管它们按顺序排列   原始列值)。这可以通过放置在需要时解决   子查询中的FOR UPDATE / SHARE子句,例如

     

SELECT * FROM (SELECT * FROM mytable FOR UPDATE) ss ORDER BY column1;   请注意,这将导致锁定mytable的所有行,而FOR   顶级的UPDATE只会锁定实际返回的行。   这尤其可以带来显着的性能差异   如果ORDER BY与LIMIT或其他限制相结合。所以这   只有在订购的并发更新时才推荐使用技术   列是预期的,并且需要严格排序的结果。

     

在REPEATABLE READ或SERIALIZABLE事务隔离级别   这会导致序列化失败(SQLSTATE为'40001'),   所以在这些下面不可能无序接收行   隔离级别。

假设我运行此查询,

SELECT * FROM banks as x WHERE x.date < {inputDate} ORDER BY x.date DESC LIMIT 1 FOR UPDATE;

  • 假设行日期无法修改
  • 可以为银行插入/删除行
  • 无论种族条件如何,我每次都能获得正确的结果吗?
  • 我检索的单行是否会锁定更新? (我读了很多关于LIMIT和FOR UPDATE交互的消息来源,但仍然很困惑,有人可以确认这种情况会发生什么事吗?)

文章提到了不正确的顺序,但我真的可以申请我的用例是否会在添加/删除时获得正确的结果。

1 个答案:

答案 0 :(得分:0)

是的,您的查询可能会返回错误的结果。

见这个例子:

CREATE TABLE banks (
   id integer PRIMARY KEY,
   date timestamp with time zone NOT NULL
);
INSERT INTO banks VALUES (1, '2017-09-01 00:00:00');
INSERT INTO banks VALUES (2, '2017-09-01 01:00:00');

现在,在一个会话中,我们启动一个更新行的事务:

BEGIN;
UPDATE banks
   SET date = '2017-08-01 00:00:00'
   WHERE id = 2;

然后,在另一个会话中,我们运行您的声明:

SELECT *
   FROM banks AS x
   WHERE x.date < current_timestamp
   ORDER BY x.date DESC
   LIMIT 1
   FOR UPDATE;

此会话在尝试使用id = 2锁定行时会挂起。

现在在第一个会话中,关闭交易:

COMMIT;

然后第二个会话将解锁并返回错误的结果:

┌────┬────────────────────────┐
│ id │          date          │
├────┼────────────────────────┤
│  2 │ 2017-08-01 00:00:00+02 │
└────┴────────────────────────┘
(1 row)

这是因为查询在锁定之前返回标识的行,但显示的值是锁定成功后的当前值。