MySQL的Serializable是唯一能自动防止写偏斜的内置隔离级别,它通过将所有SELECT转为锁定读(S锁)、结合行锁与间隙锁实现,但仅在查询能精确命中并锁定全部相关数据时有效。

MySQL 的 Serializable 隔离级别是唯一能**自动防止写偏斜(Write Skew)**的内置方案,但它不是简单 SET 一下就万事大吉——它会彻底改变锁行为和并发模型。
MySQL Serializable 实际上是“加锁读 + 行锁 + 间隙锁”
和 PostgreSQL / Oracle 基于 MVCC + 提交检测的 Serializable 不同,MySQL InnoDB 的 Serializable 是通过强制所有普通 SELECT 都变成锁定读来实现的:
-
SELECT ...自动等价于SELECT ... LOCK IN SHARE MODE(加 S 锁) -
SELECT ... WHERE ...会触发 next-key lock(行锁 + 间隙锁),阻塞其他事务在该范围插入或更新 - 这意味着原本无锁的快照读全部消失,读操作也会阻塞写、写操作也会阻塞读
- 不是“更严格的快照”,而是“退回到悲观并发控制”
写偏斜场景下 Serializable 确实生效,但代价明显
以医生值班例子为例:两个事务都先 SELECT COUNT(*) FROM doctors WHERE on_duty = 1,再判断是否允许自己 off_duty。
- 在
Repeatable Read下:两次查询都看到快照值 2,各自提交 → 写偏斜发生 - 在
Serializable下:第一个事务的SELECT加了 S 锁,第二个事务的同条件SELECT会被阻塞,直到第一个事务提交或回滚 - 结果:第二个事务读到的是第一个事务提交后的真实状态(比如 1),自然无法继续执行 off_duty 逻辑
- 但注意:如果两个事务 SELECT 的是不同主键(如
WHERE id = 1和WHERE id = 2),它们互不加锁,Serializable也防不住写偏斜 —— 因为锁粒度不在“业务语义层”
别忽略隐式转换和索引失效导致的锁失效
Serializable 的防护能力完全依赖 SELECT 是否真能命中并加锁。以下情况会让它“形同虚设”:
- WHERE 条件字段没走索引 → 退化为全表扫描 + 表级意向锁,无法精确保护业务逻辑涉及的数据集
- 字符串比较时发生隐式类型转换,例如
WHERE phone = 13800138000(phone 是 VARCHAR),导致索引失效 - 使用函数包裹索引列,如
WHERE YEAR(created_at) = 2026,同样跳过索引,锁不住实际行 - 事务中混用
SELECT和SELECT FOR UPDATE:前者在Serializable下加 S 锁,后者加 X 锁;但若 S 锁范围过大或过小,仍可能漏锁关键行
真正难处理的写偏斜,往往藏在多表联合校验、跨行业务约束里。MySQL 的 Serializable 只对“单次查询能覆盖全部依赖数据”的场景有效。一旦业务逻辑需要聚合、分页、或动态条件拼接,就得靠应用层显式加锁(如 SELECT ... FOR UPDATE 锁住所有相关行)或引入外部协调机制(如分布式锁 + 版本号)。


















