MySQL并发事务中已删除行仍被查询到的原因及与PG差异解析
MySQL与PostgreSQL可重复读隔离级别下并发删除的行为差异解析
测试环境与初始化数据
CREATE TABLE memberships ( id SERIAL PRIMARY KEY, user_id INT, group_id INT ); INSERT INTO memberships(user_id, group_id) VALUES (1, 1), (2, 1), (1, 2), (2, 2);
并发事务时间线
| 时间 | 事务1 | 事务2 |
|---|---|---|
| T1 | start transaction | |
| T2 | delete from memberships where group_id = 2 | start transaction |
| T3 | select * from memberships(触发事务2快照生成,MySQL判定事务2启动早于事务1) | |
| T4 | select * from memberships(仅返回group_id=1的行) | |
| T5 | commit | |
| T6 | delete from memberships where group_id = 2 | |
| T7 | select * from memberships(返回所有行,包含group_id=2的行) |
T7查询结果
select * from memberships; +----+---------+----------+ | id | user_id | group_id | +----+---------+----------+ | 1 | 1 | 1 | | 2 | 2 | 1 | | 3 | 1 | 2 | | 4 | 2 | 2 | +----+---------+----------+ 4 rows in set (0.00 sec)
1. MySQL出现该问题的原因及与MVCC的交互
- 快照生成规则:MySQL可重复读隔离级别下,事务的快照在第一次执行查询语句时生成且固定不变。事务2在T3执行
select时生成快照,此时事务1的delete尚未提交,快照中仍包含group_id=2的行,MySQL会判定事务2的启动时间早于事务1。 - 删除操作的MVCC处理:MySQL的
delete是给行打删除标记并记录事务ID,而非物理删除。事务2在T6执行delete时,基于自身快照匹配行,此时事务1已提交,但事务2的快照仍能看到group_id=2的行。不过这些行已被事务1标记删除,事务2的delete实际未修改任何行,且MySQL不会触发冲突检测。 - 查询的快照一致性:事务2的所有查询都沿用T3生成的快照,即便执行了
delete也不会更新快照。因此T7的查询会返回快照中存在的所有行,包括事务1已删除但事务2快照中仍可见的group_id=2行。
2. PostgreSQL无此问题的原因及与MVCC的交互
- 快照与冲突检测机制:PostgreSQL的可重复读隔离级别遵循严格快照隔离,会主动检测并发写冲突。事务2在T3生成快照后,事务1提交了对
group_id=2行的删除。当事务2在T6执行delete时,PostgreSQL会检查目标行的最新版本。 - 删除操作的冲突处理:PostgreSQL执行
delete时会尝试获取行锁,并对比行的最新事务ID与自身快照的事务ID范围。发现目标行已被快照外的提交事务修改后,会判定序列化冲突,直接抛出could not serialize access due to concurrent delete错误,终止事务2操作,避免不一致结果。 - MVCC版本管理:PostgreSQL每行版本都标记了事务ID范围,当事务尝试修改行时,若发现行已被快照外的提交事务修改,就会触发冲突报错,保证可重复读级别下的一致性。
内容的提问来源于stack exchange,提问作者Stack Underflow 11111
相关产品推荐
相关产品推荐

