为啥 PostgreSQL 可序列化事务认为这是冲突?

Posted

技术标签:

【中文标题】为啥 PostgreSQL 可序列化事务认为这是冲突?【英文标题】:Why does PostgreSQL serializable transaction think this as conflict?为什么 PostgreSQL 可序列化事务认为这是冲突? 【发布时间】:2017-07-06 10:35:29 【问题描述】:

据我了解,PostgreSQL 使用某种监视器来猜测可序列化隔离级别是否存在冲突。许多例子都是关于在并发事务中修改相同的资源,并且可序列化事务效果很好。但我想以另一种方式测试并发问题。

我决定测试 2 个用户修改他们自己的帐户余额,并希望 PostgreSQL 足够聪明,不会将其检测为冲突,但结果不是我想要的。

下面是我的表格,有 4 个账户,属于 2 个用户,每个用户有一个活期账户和一个储蓄账户。

create table accounts (
  id serial primary key,
  user_id int,
  type varchar,
  balance numeric
);

insert into accounts (user_id, type, balance) values
  (1, 'checking', 1000),
  (1, 'saving', 1000),
  (2, 'checking', 1000),
  (2, 'saving', 1000);

表格数据是这样的:

 id | user_id |   type   | balance
----+---------+----------+---------
  1 |       1 | checking |    1000
  2 |       1 | saving   |    1000
  3 |       2 | checking |    1000
  4 |       2 | saving   |    1000

现在我为 2 个用户运行 2 个并发事务。在每笔交易中,我都会用一些钱减少支票账户,并检查该用户的总余额。如果大于1000,则提交,否则回滚。

用户1的例子:

begin;

-- Reduce checking account for user 1
update accounts set balance = balance - 200 where user_id = 1 and type = 'checking';

-- Make sure user 1's total balance > 1000, then commit
select sum(balance) from accounts where user_id = 1;

commit;

用户2是一样的,除了where中的user_id = 2

begin;
update accounts set balance = balance - 200 where user_id = 2 and type = 'checking';
select sum(balance) from accounts where user_id = 2;
commit;

我首先提交用户 1 的事务,毫无疑问它成功了。当我提交用户 2 的事务时,它失败了。

ERROR:  could not serialize access due to read/write dependencies among transactions
DETAIL:  Reason code: Canceled on identification as a pivot, during commit attempt.
HINT:  The transaction might succeed if retried.

我的问题是:

    为什么 PostgreSQL 认为这 2 个事务是冲突的?我为所有SQL添加了user_id条件,并没有修改user_id,但是这些都没有效果。 这是否意味着可序列化事务不允许在同一张表上发生并发事务,即使它们的读/写没有冲突? 每个用户做某事是很常见的,我应该避免对频繁发生的操作使用可序列化事务吗?

【问题讨论】:

你可能想在 Postgres 邮件列表中问这个问题 【参考方案1】:

您可以使用以下索引解决此问题:

CREATE INDEX accounts_user_idx ON accounts(user_id);

由于您的示例表中的数据太少,您必须告诉 PostgreSQL 使用索引扫描:

SET enable_seqscan=off;

现在你的例子可以工作了!

如果这看起来像黑魔法,请查看您的 SELECTUPDATE 语句的查询执行计划。

如果没有索引,两者都将使用对表的顺序扫描,从而读取表中的所有行。因此,两个事务最终都会在整个表上以 SIReadLock 结束。

这会触发序列化失败。

【讨论】:

所以关键是要避免全表扫描,如果表数据太少也不会触发索引,我的理解对吗?【参考方案2】:

据我所知,可序列化具有***别的隔离,因此具有最低级别的并发性。事务一个接一个地发生,并发性为零。

【讨论】:

为什么 PostgreSQL 认为这两个事务是冲突的?我为所有SQL添加了user_id条件,并没有修改user_id,但是这些都没有效果。 --> 不知道为什么你有这个冲突伙伴,也许当它正在寻找记录时,因为它有一个“检查”类型的公共字段可能是一个原因。你试过一种“储蓄”和一种“检查”吗 我试过了,还是有冲突。实际上,即使所有条件都简化为where user_id = x,这也是冲突。

以上是关于为啥 PostgreSQL 可序列化事务认为这是冲突?的主要内容,如果未能解决你的问题,请参考以下文章

Postgresql 可序列化事务未按预期工作

为啥两个具有隔离可序列化的事务在写入不同的行时被阻塞

使用Django和PostgreSQL进行事务(@atomic)的默认隔离级别

SQLAlchemy,以惯用的 Python 方式进行可序列化事务隔离和重试

MVCC Postgresql 和 MYSQL 到底谁更......?

org.postgresql.util.PSQLException:错误:由于事务之间的读/写依赖关系,无法序列化访问