并发问题:脏读、不可重复读、幻读
多个事务同时读写同一行时,会出现三类问题:脏读读到别的事务还没提交的修改;不可重复读同一事务内两次读同一行结果不一样(被别的事务改了);幻读同一查询两次返回的行数不同(别的事务插入了新行)。隔离级别就是用来在"正确性"和"并发性能"之间做取舍的档位。
- 脏读读到未提交数据。
- 不可重复读同行两次读结果不同。
- 幻读同查询两次行数不同。
- Next-Key Lock行锁加间隙锁防幻读。
四种事务隔离级别
SQL 标准定义四档:读未提交(RU)最低、读已提交(RC)、可重复读(RR)、串行化(Serializable)最高。MySQL InnoDB 默认是 RR。RU 什么问题都不防,RC 防脏读、RR 再防不可重复读,串行化几乎等于全表加锁、并发最差。
行锁与表锁
InnoDB 默认走行锁,只锁涉及的行,并发好。但行锁是加在索引上的:如果更新语句没走索引,它会退化成扫全表、实际上锁住所有行(近似表锁)。所以更新语句一定要带上能命中索引的条件,否则并发能力骤降。
间隙锁与 Next-Key Lock
RR 级别下,InnoDB 用间隙锁(Gap Lock)锁住索引记录之间的"空隙",配合行锁组成 Next-Key Lock,防止别的事务在这个范围里插入新行,从而解决幻读。代价是锁的范围变大、更容易冲突,但在 RR 下这是保证可重复读的关键。
MVCC 怎么实现快照读
MVCC(多版本并发控制)让普通读(快照读)不阻塞写、也不加锁:每行保留多个历史版本,事务按自己的一致性视图读到对应版本。只有当前读(SELECT 语句末尾带 FOR UPDATE)才走锁。这也是 InnoDB 高并发的根基。
-- 查看当前隔离级别
SHOW VARIABLES LIKE 'transaction_isolation';
-- 设为读已提交
SET SESSION transaction_isolation = 'READ-COMMITTED';
锁冲突与死锁排查
死锁是两个事务互相等对方的锁。用 SHOW ENGINE INNODB STATUS 看最近死锁日志,定位是哪两条语句、按什么顺序加的锁。常见解法:让所有事务按相同顺序访问行,缩短事务、早点提交,减少长事务占锁。
RC 与 RR 怎么选
RR 是默认、能防幻读,但间隙锁范围大、并发略差;RC 没有间隙锁、并发性好,但会有幻读。互联网高并发业务常主动用 RC:接受幻读换更高吞吐,业务层面用唯一约束、乐观锁补正确性。
落地清单
- 更新语句必须走索引,否则行锁退化。
- 理解 RC 与 RR 的取舍,别盲目用默认值。
- 缩短事务、按固定顺序加锁防死锁。
- 用 MVCC 快照读避免读阻塞写。
- 死锁日志定期看,提前发现热点行。