mysql默认隔离级别是repeatable read,因innodb通过mvcc+间隙锁兼顾不可重复读与幻读防控,保障一致性快照稳定;但幻读未被完全杜绝,仅限已执行select条件范围内。

MySQL 默认隔离级别为什么是 REPEATABLE READ 而不是 READ COMMITTED
因为 InnoDB 在 REPEATABLE READ 下通过多版本并发控制(MVCC)+ 间隙锁(Gap Lock)能同时解决「不可重复读」和「幻读」问题,而多数业务场景更看重一致性快照的稳定性。但要注意:这不等于完全杜绝幻读——仅针对当前事务中已执行过的 SELECT 语句,后续新条件的 SELECT 仍可能看到新插入行(除非加 SELECT ... for UPDATE 或显式锁表)。
常见误解是认为 REPEATABLE READ 能彻底屏蔽幻读,实际它靠的是“一致性读视图在事务开启时就固定”,所以同一事务内多次 SELECT 结果一致;但其他事务插入并提交的新记录,在本事务下次 SELECT 时仍可见(除非用了范围条件且触发了间隙锁)。
SELECT ... FOR UPDATE 在什么情况下会锁住整张表
当查询条件无法使用索引时,InnoDB 会退化为表级锁(本质是给每一行加 X 锁,但因无索引扫描全表,效果等同于锁表)。比如:
SELECT * FROM orders WHERE status = 'pending' FOR UPDATE;
若 status 字段没有索引,这条语句将对 orders 表中所有行加行锁,导致高并发下严重阻塞。
- 确认是否走索引:用
EXPLAIN查看type是否为ALL或index - 避免隐式类型转换:如
WHERE user_id = '123'(user_id是int),会导致索引失效 - 注意
OR条件:部分OR组合会让优化器放弃索引合并,改走全表扫描
如何判断一个事务是否被死锁并自动回滚
MySQL 检测到死锁后,会主动选择一个事务作为牺牲者(victim),将其回滚,并返回错误:Error 1213 (40001): Deadlock found when trying to get lock; try restarting transaction。这个过程由 InnoDB 自动完成,无需人工干预。
但要注意以下几点:
- 死锁检测默认开启(
innodb_deadlock_detect = ON),关闭后虽降低开销,但可能导致事务长时间等待 - 被选为 victim 的事务不一定是最小或最短的——InnoDB 基于事务持有锁的数量与权重综合判断
- 应用层必须捕获该错误并重试,否则业务逻辑会中断(尤其在支付、库存扣减等场景)
- 可通过
SHOW ENGINE INNODB STATUSG查看最近一次死锁详情,包括每个事务持有的锁、等待的锁、SQL 语句等
并发更新同一行时,UPDATE ... WHERE 和 SELECT ... FOR UPDATE 的行为差异
二者都会上写锁(X 锁),但时机和粒度不同:
-
UPDATE ... WHERE id = 1:先定位行,再加 X 锁,然后修改。如果WHERE条件匹配多行,会逐行加锁;若无匹配行,则不加任何锁 -
SELECT ... FOR UPDATE:先加锁再读取,即使后续不做修改,锁也会持续到事务结束。它更适合“读-改-写”分离明确的场景(如先查余额,再判断是否足够,最后扣减) - 性能影响:单纯更新建议直接用
UPDATE,减少一次网络往返;需要前置校验逻辑时,用SELECT ... FOR UPDATE更安全,避免检查与更新之间的窗口期被篡改 - 注意
UPDATE的原子性:即使两个事务同时执行UPDATE t SET x = x + 1 WHERE id = 1,InnoDB 保证最终结果是 +2,但中间会串行化执行,第二个事务需等待第一个释放锁
真正容易被忽略的是:隔离级别只约束普通 SELECT,而所有带锁读(FOR UPDATE、LOCK IN SHARE MODE)和写操作(INSERT/UPDATE/delete)始终使用当前最新版本数据,不受 MVCC 快照影响。这意味着你在 REPEATABLE READ 事务里执行了两次 SELECT ... FOR UPDATE,第二次看到的可能是其他事务刚提交的新值——这不是 bug,是设计如此。