一句话总结
InnoDB行锁基于索引实现,未命中索引会退化为表锁。MVCC通过Undo Log版本链 + Read View实现非阻塞读,RR级别下快照读通过MVCC避免幻读,当前读通过Next-Key Lock解决。
初级理解
行锁实现机制:
InnoDB的行锁是基于索引实现的,而非基于物理行。
如果SQL没有走索引,InnoDB将退化为表锁。
三种行锁类型:
• Record Lock(记录锁):锁定索引上的单条记录
• Gap Lock(间隙锁):锁定索引记录之间的间隙,防止其他事务插入新记录
• Next-Key Lock(临键锁):Record Lock + Gap Lock的组合,锁定记录及其前面的间隙
一句话总结:行锁基于索引,没索引变表锁;Next-Key Lock = 记录锁 + 间隙锁。
中级深入
MVCC原理:
MVCC(多版本并发控制)通过保存数据的历史版本来实现非阻塞读。
核心组件:
• Undo Log版本链:每次修改记录都会生成Undo Log,通过roll_pointer串联成版本链
• Read View(读视图):包含当前活跃事务ID列表,用于判断版本可见性
Read View生成时机:
• RR(可重复读):在事务第一次执行SELECT时生成Read View,后续查询复用该视图
• RC(读已提交):每次执行SELECT都会重新生成Read View
注意:MVCC只在RC和RR隔离级别下生效,RU和Serializable不使用MVCC。
高级拓展
RR级别下如何解决幻读:
• 快照读(普通SELECT):通过MVCC的Read View机制,始终读取事务开始时的数据快照
• 当前读(SELECT...FOR UPDATE / UPDATE / DELETE):通过Next-Key Lock锁住索引记录及间隙
InnoDB使用表锁的情况:
• SQL未命中索引:执行全表扫描时,InnoDB会锁住所有记录
• DDL操作:ALTER TABLE、CREATE INDEX等
• 显式加表锁:LOCK TABLES ...
• 外键检查:某些复杂的外键级联操作
面试加分项:能说出MVCC的两个核心组件、Read View在RR和RC下的区别、Next-Key Lock解决幻读的原理,说明你对InnoDB事务机制有深入理解。
实战场景
场景:行锁退化为表锁
-- 问题SQL:status列没有索引,行锁退化为表锁
BEGIN;
UPDATE users SET status = 'VIP' WHERE status = 'ACTIVE'; -- 全表锁!
-- 解决:给status列加索引
ALTER TABLE users ADD INDEX idx_status(status);
-- 现在只锁status='ACTIVE'的行
场景:MVCC读取一致性视图
-- 事务A
BEGIN;
SELECT * FROM users WHERE id = 1; -- 生成Read View,读到name='张三'
-- 事务B
BEGIN;
UPDATE users SET name = '李四' WHERE id = 1;
COMMIT;
-- 事务A再次查询(RR级别)
SELECT * FROM users WHERE id = 1; -- 仍然读到name='张三'(MVCC快照读)
面试模拟
Q:为什么InnoDB行锁要基于索引?
A:因为InnoDB的行锁是加在索引记录上的,不是加在物理行上的。如果SQL没有走索引,InnoDB就需要扫描整张表来匹配条件,相当于锁住了所有行,退化为表锁。所以确保UPDATE/DELETE的WHERE条件命中索引是避免锁升级的关键。
Q:RR和RC级别下MVCC有什么区别?
A:主要区别在于Read View的生成时机:RR级别在事务第一次SELECT时生成Read View,后续复用,保证同一事务内多次读取结果一致;RC级别每次SELECT都重新生成Read View,能读到其他事务已提交的最新数据。