SERIALIZABLE 不能靠全局配置一劳永逸启用,必须在每个会话中显式设置;其下 SELECT 隐式加共享锁,易引发阻塞与死锁,且不等于绝对安全,需结合索引与锁路径重新设计事务。

不能靠配置文件或全局设置“一劳永逸”地启用 SERIALIZABLE,必须在每个会话里显式执行 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE,否则事务仍运行在默认的 REPEATABLE READ 级别下。
为什么 SET GLOBAL transaction_isolation = 'SERIALIZABLE' 不生效?
MySQL 的 transaction_isolation 全局变量只影响新建立的连接,默认值会被继承;已存在的连接(包括你当前正在用的命令行、应用连接池里的活跃连接)完全不受影响。改完配置没重启 MySQL、也没重建连接,SELECT @@transaction_isolation 依然显示 REPEATABLE-READ——这是最常被忽略的前提错误。
-
SET GLOBAL只对后续新建连接生效,不触发现有会话 - 应用层(如 Python/Java 连接池)通常复用连接,改了全局配置但连接没重连,等于没改
- 即使配置文件写了
transaction-isolation = SERIALIZABLE,也必须重启 MySQL 才加载,且仍需确认连接是否为新连接
SERIALIZABLE 下 SELECT 为什么会阻塞?
和 REPEATABLE READ 不同,SERIALIZABLE 让普通 SELECT 语句隐式加共享锁(等价于自动追加 LOCK IN SHARE MODE),不是“只锁写”。这意味着读操作也会参与锁竞争。
- 事务 A 执行
SELECT * FROM orders WHERE user_id = 123,事务 B 随后执行UPDATE orders SET status = 'paid' WHERE id = 456(哪怕该行与 A 查询无关),只要涉及同一张表且 InnoDB 判定存在冲突风险,B 就可能被挂起 - 两个事务都只做
SELECT * FROM t(无 WHERE 或无索引条件),表面看不冲突,但一旦其中一方后续执行INSERT,另一方的SELECT就可能被锁住——尤其在范围扫描时触发临键锁(Next-Key Lock) - 全表扫描、缺失索引、WHERE 条件无法走索引,都容易让锁升级为表级效果,大幅增加死锁概率
SERIALIZABLE 并不等于“绝对安全”,它只是换了一种代价方式
它靠锁机制实现串行化,放弃 MVCC 快照读,所以并发吞吐会明显下降,且锁等待逻辑比预期更复杂。
- 幻读确实被锁机制压制了(新插入会被阻塞),但业务上仍可能出现逻辑异常:比如事务 A 查不到某订单,决定创建;事务 B 同时查到并修改该订单;A 提交时插入成功,B 的更新却基于旧快照——这不是数据库层面的幻读,而是应用层未协调好判断与写入顺序
- 两个 SERIALIZABLE 事务对同一张表做不同范围的
SELECT,再各自INSERT,极易触发Lock wait timeout exceeded或死锁 - 多数真实场景中,用
SELECT ... FOR UPDATE配合REPEATABLE READ更可控:只在真正要更新前加锁,读操作保持非阻塞,锁粒度更明确
真正要用 SERIALIZABLE,得逐条检查事务里每个 SELECT 是否有索引支撑、是否涉及范围扫描、是否可能与其他事务的写路径交叉——不是设个级别就高枕无忧,而是要把整条 SQL 路径当成锁路径来重新设计。


















