串行化的两种实现
最高隔离级别的名字两边一样,实现路径完全不同。PostgreSQL 用的是乐观检测:事务照常并行执行,系统在提交阶段检查是否存在危险的读写依赖结构,发现冲突就中止其中一方并报出可重试错误。MySQL 的 InnoDB 走的是悲观路线:普通查询会被隐式升级成带锁读取,冲突变成等待,等待多了就出现锁超时与死锁。同一套并发代码在两个数据库上表现出的症状完全不是一回事。
基本信息
| 适用平台 | PostgreSQL 12 及以上 / MySQL 8.0(InnoDB) |
|---|---|
| PostgreSQL 机制 | 串行化快照隔离,提交时检测冲突并中止事务 |
| MySQL 机制 | 普通读隐式加共享锁,并发写出现等待 |
| 应用要求 | 必须实现捕获错误、回滚、重试的完整链路 |
| 错误可重试性 | 序列化失败与死锁都应当被应用捕获并重试 |
典型使用场景
- 账务一致性要求极高的场景:宁可重试也不要脏读
- 跨行约束校验:写入前需要确认区间内没有冲突记录
- 批量结算任务:允许部分事务重试,但结果必须严格一致
- 从 MySQL 迁到 PostgreSQL 后重新压测并发行为
- 评估把隔离级别降到读已提交的可行性
两种机制下的写法
两种机制下的写法
-- PostgreSQL:会话级开启串行化,冲突时中止事务
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT SUM(balance) FROM accounts WHERE user_id = 1;
-- 并发冲突时返回可重试的错误(序列化失败)
COMMIT; -- 失败后应用需要重试整个事务
-- 查看当前隔离级别
SHOW transaction_isolation;
-- MySQL:同一级别下普通查询会自动加共享锁
-- BEGIN;
-- SELECT SUM(balance) FROM accounts WHERE user_id = 1; ← 锁等待概率上升
-- COMMIT;
-- 应用侧统一封装:捕获可重试错误、回滚、退避重试
-- 重试必须从事务开头开始,不能从失败语句处续跑两种机制对照
| 对比项 | PostgreSQL | MySQL InnoDB |
|---|---|---|
| 冲突处理 | 检测到危险结构后中止事务 | 通过锁等待串行化执行 |
| 失败表现 | 可重试的序列化失败错误 | 锁等待超时与死锁 |
| 读操作是否加锁 | 不会隐式加锁 | 普通读会升级为共享锁 |
| 重试要求 | 应用必须实现重试 | 同样需要重试与去重保障 |
| 压测结论可复用性 | 不可直接套用 | 不可直接套用 |
没有重试机制的应用在串行化隔离下高峰期报错率会明显上升。上线前先把「捕获错误、回滚、退避重试」这条链路补齐,并统计重试率作为容量指标。
两个数据库在该级别下的行为差异很大,跨库压测得到的并发容量结论不能互相套用。迁移前用生产同规模的并发模型各压一遍。