为什么 PostgreSQL 可序列化事务认为这是冲突?
Why does PostgreSQL serializable transaction think this as conflict?
据我了解,PostgreSQL 使用某种监视器来猜测可序列化隔离级别是否存在冲突。许多示例都是关于在并发事务中修改相同资源的,而可序列化事务效果很好。但我想用另一种方式测试并发问题。
我决定测试 2 个用户修改自己的帐户余额,并希望 PostgreSQL 足够聪明,不会将其检测为冲突,但结果不是我想要的。
下面是我的table,有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);
table数据是这样的:
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 认为这两个事务是冲突的?我为所有SQL添加了user_id条件,并没有修改user_id,但这些都没有效果。
- 这是否意味着可序列化事务不允许并发事务发生在同一个 table 上,即使它们 read/write 没有冲突?
- 每个用户做某事很常见,我是否应该避免对频繁发生的操作使用可序列化事务?
据我所知,serializable 具有最高级别的隔离,因此具有最低级别的并发性。事务一个接一个地发生,并发为零。
您可以使用以下索引修复此问题:
CREATE INDEX accounts_user_idx ON accounts(user_id);
由于您的示例中的数据太少 table,您必须告诉 PostgreSQL 使用索引扫描:
SET enable_seqscan=off;
现在您的示例可以运行了!
如果这看起来像是黑魔法,请查看您的 SELECT
和 UPDATE
语句的查询执行计划。
如果没有索引,两者都将在 table、 上使用顺序扫描,从而读取 table 中的所有行。所以这两笔交易最终都会以 SIReadLock
整体 table.
这会触发序列化失败。
据我了解,PostgreSQL 使用某种监视器来猜测可序列化隔离级别是否存在冲突。许多示例都是关于在并发事务中修改相同资源的,而可序列化事务效果很好。但我想用另一种方式测试并发问题。
我决定测试 2 个用户修改自己的帐户余额,并希望 PostgreSQL 足够聪明,不会将其检测为冲突,但结果不是我想要的。
下面是我的table,有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);
table数据是这样的:
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 认为这两个事务是冲突的?我为所有SQL添加了user_id条件,并没有修改user_id,但这些都没有效果。
- 这是否意味着可序列化事务不允许并发事务发生在同一个 table 上,即使它们 read/write 没有冲突?
- 每个用户做某事很常见,我是否应该避免对频繁发生的操作使用可序列化事务?
据我所知,serializable 具有最高级别的隔离,因此具有最低级别的并发性。事务一个接一个地发生,并发为零。
您可以使用以下索引修复此问题:
CREATE INDEX accounts_user_idx ON accounts(user_id);
由于您的示例中的数据太少 table,您必须告诉 PostgreSQL 使用索引扫描:
SET enable_seqscan=off;
现在您的示例可以运行了!
如果这看起来像是黑魔法,请查看您的 SELECT
和 UPDATE
语句的查询执行计划。
如果没有索引,两者都将在 table、 上使用顺序扫描,从而读取 table 中的所有行。所以这两笔交易最终都会以 SIReadLock
整体 table.
这会触发序列化失败。