首页/数据/SQL 速查/SERIALIZABLE 的两种实现:SSI 与锁
变更与事务52 条

SERIALIZABLE 的两种实现:SSI 与锁

同为 SERIALIZABLE:PostgreSQL 用 SSI(乐观检测、冲突中止),MySQL InnoDB 把普通读升级为共享锁(悲观、等待增多)。

SQL 速查数据更新于 2026-10-13

串行化的两种实现

最高隔离级别的名字两边一样,实现路径完全不同。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;

-- 应用侧统一封装:捕获可重试错误、回滚、退避重试
-- 重试必须从事务开头开始,不能从失败语句处续跑

两种机制对照

对比项PostgreSQLMySQL InnoDB
冲突处理检测到危险结构后中止事务通过锁等待串行化执行
失败表现可重试的序列化失败错误锁等待超时与死锁
读操作是否加锁不会隐式加锁普通读会升级为共享锁
重试要求应用必须实现重试同样需要重试与去重保障
压测结论可复用性不可直接套用不可直接套用
没有重试机制的应用在串行化隔离下高峰期报错率会明显上升。上线前先把「捕获错误、回滚、退避重试」这条链路补齐,并统计重试率作为容量指标。
两个数据库在该级别下的行为差异很大,跨库压测得到的并发容量结论不能互相套用。迁移前用生产同规模的并发模型各压一遍。

易错点

  • 应用没有重试机制就使用串行化隔离,高峰期可重试错误集中爆发,接口失败率飙升
  • 把 MySQL 的锁等待经验套用到 PostgreSQL:一个是等待、一个是中止,容量评估完全错位
  • 重试时从失败语句处续跑而不是重跑整个事务,事务上下文已失效
  • 串行化下写热点数据,冲突率极高,重试成本超过收益,应考虑分段或改队列串行
  • 只在低并发环境验证,串行化的冲突特征在高并发下才显现

数据来源与参考

  • PostgreSQL Documentation · Serializable Isolation Level
  • MySQL 8.0 Reference Manual · Transaction Isolation Levels
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌