繁体   English   中英

具有显式锁和并发事务的错误PostgreSQL查询结果

[英]Wrong PostgreSQL query results with explicit locks and concurrent transaction

在为PostgreSQL编写一些SQL查询时,我发现了一些异常行为,这些行为我觉得有些不安。

假设我们有下表“ test”:

+----+-------+---------------------+
| id | value |     created_at      |
+----+-------+---------------------+
|  1 | A     | 2014-01-01 00:00:00 |
|  2 | A     | 2014-01-02 00:00:00 |
|  3 | B     | 2014-01-03 00:00:00 |
|  4 | B     | 2014-01-04 00:00:00 |
|  5 | A     | 2014-01-05 00:00:00 |
|  6 | B     | 2014-01-06 00:00:00 |
|  7 | A     | 2014-01-07 00:00:00 |
|  8 | B     | 2014-01-08 00:00:00 |
+----+-------+---------------------+

有两个事务并行运行。

A: begin;           /* Begin transaction A */
B: begin;           /* Begin transaction B */
A: select * from test where id = 1 for update; /* Lock one row */
B: select * from test where value = 'B' order by created_at limit 3 for update; /* This query returns immediately since it does not need to return row with id=1 */
B: select * from test where value = 'A' order by created_at limit 3 for update; /* This query blocks because row id=1 is locked by transaction A */
A: update test set created_at = '2014-01-09 00:00:00' where id = 1; /* Modify the locked row */
A: commit;

事务A提交并释放id = 1的行后,事务B的阻塞查询将返回以下结果:

+----+-------+---------------------+
| id | value |     created_at      |
+----+-------+---------------------+
|  1 | A     | 2014-01-09 00:00:00 |
|  2 | A     | 2014-01-02 00:00:00 |
|  5 | A     | 2014-01-05 00:00:00 |
+----+-------+---------------------+

这些行最肯定不是按“ created_at”排序的,id = 1的行甚至不应该位于返回的行中。 事务A和B同时运行的事实导致了事务B的错误结果,如果一个接一个地执行事务,则不会发生。 这似乎违反了事务隔离。

这是错误吗?

如果这不是错误,并且可以预期得到这些结果,那么就DB返回的结果的可靠性而言,这意味着什么? 如果我有一个高度并发的环境,并且随后的代码依赖于按日期实际排序的行,则将出现错误。

但是,如果我们执行与上述相同的指令序列,但将update语句替换为以下内容:

update test set value = 'B', created_at = '2014-01-09 00:00:00' where id = 1;

...然后被阻止的查询返回正确的结果:

+----+-------+---------------------+
| id | value |     created_at      |
+----+-------+---------------------+
|  2 | A     | 2014-01-02 00:00:00 |
|  5 | A     | 2014-01-05 00:00:00 |
|  7 | A     | 2014-01-07 00:00:00 |
+----+-------+---------------------+

在这种情况下, 由于查询的初始结果无效,阻塞查询是否会执行两次

我对PostgreSQL最感兴趣,但是我也想知道其他支持行级锁定的RDBMS(例如Oracle,SQL Server和MySQL)是否属于这种情况。

这里发生了几件事。 首先,这是记录的行为。 其次,您看不到整个故事,因为您没有尝试在会话“ B”中更新任何内容。

这似乎违反了事务隔离。

取决于您所运行的隔离级别。 PostgreSQL的默认事务隔离级别READ COMMITTED

这是PostgreSQL中记录的行为

SELECT命令可能以READ COMMITTED事务隔离级别运行,并且使用ORDER BY和锁定子句可以使行无序返回。 这是因为ORDER BY首先被应用。 该命令对结果进行排序,但随后可能会阻止尝试获得对一个或多个行的锁定。 一旦SELECT解除阻塞,某些排序列值可能已被修改,从而导致这些行看起来是乱序的(尽管就原始列值而言,它们是有序的)。

一种解决方法(也已记录,同一链接)是将FOR UPDATE移到子查询中,但这需要使用表锁。

要查看的PostgreSQL 确实在这种情况下,请在会议上“B”的更新。

create table test (
  id integer primary key,
  value char(1) not null,
  created_at timestamp not null
);
insert into test values
(1, 'A', '2014-01-01 00:00:00'),
(2, 'A', '2014-01-02 00:00:00'),
(3, 'B', '2014-01-03 00:00:00'),
(4, 'B', '2014-01-04 00:00:00'),
(5, 'A', '2014-01-05 00:00:00'),
(6, 'B', '2014-01-06 00:00:00'),
(7, 'A', '2014-01-07 00:00:00'),
(8, 'B', '2014-01-08 00:00:00');
A: begin;           /* Begin transaction A */
B: begin;           /* Begin transaction B */
A: select * from test where id = 1 for update; /* Lock one row */
B: select * from test where value = 'B' order by created_at limit 3 for update; /* This query returns immediately since it does not need to return row with id=1 */
B: select * from test where value = 'A' order by created_at limit 3 for update; /* This query blocks because row id=1 is locked by transaction A */
A: update test set created_at = '2014-01-09 00:00:00' where id = 1; /* Modify the locked row */
A: commit;
B: update test set value = 'C' where id in (select id from test where value = 'A' order by created_at limit 3); /* Updates 3 rows */
B: commit;

现在,看看桌子。

scratch=# select * from test order by id;
 id | value |     created_at      
----+-------+---------------------
  1 | A     | 2014-01-09 00:00:00
  2 | C     | 2014-01-02 00:00:00
  3 | B     | 2014-01-03 00:00:00
  4 | B     | 2014-01-04 00:00:00
  5 | C     | 2014-01-05 00:00:00
  6 | B     | 2014-01-06 00:00:00
  7 | C     | 2014-01-07 00:00:00
  8 | B     | 2014-01-08 00:00:00

会话“ A”成功将ID为1的行更新为“ 2014-01-09”。 会话“ B”成功更新了剩余的三行,其值为“ A”。 更新语句获取ID号2、5和7的锁; 我们知道,因为这些是实际更新的行。 较早的select语句锁定了不同的行-第1、2和5行。

如果启动第三个终端会话,则可以阻止会话B的更新,并锁定第7行以进行更新。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM