一、基础概念类
Q1:什么是事务?ACID 分别是什么?
事务是一组要么全部成功、要么全部失败的操作。
A 原子性:操作不可分割,靠 undo log 回滚。
C 一致性:数据从一个合法状态到另一个合法状态,是目的,靠其他三者共同保证。
I 隔离性:并发事务互不干扰,靠锁 + MVCC。
D 持久性:提交后永久生效,靠 redo log。
Q2:ACID 分别靠什么实现?
Q3:事务的隔离级别有哪些?分别解决什么问题?
MySQL InnoDB 默认 RR。
RR 下靠 MVCC + 间隙锁,很大程度上避免了幻读。
Q4:脏读、不可重复读、幻读的区别?
脏读:读到别的事务未提交的数据。
不可重复读:同一事务内两次读同一行,结果不同(别的事务改了并提交)。
幻读:同一事务内两次范围查询,第二次多出或少了几行(别的事务插入/删除并提交)。
Q5:RC 和 RR 的区别?
都用 MVCC,区别在 ReadView 生成时机:
RC:每次快照读都生成新的 ReadView,所以能读到别人已提交的新数据。
RR:只在第一次快照读时生成 ReadView,之后复用,所以可重复读。
RR 额外用间隙锁防当前读的幻读。
二、MVCC 类
Q6:什么是 MVCC?
多版本并发控制。通过保存数据的多个版本,让读不加锁、读写不冲突。
核心三要素:隐藏字段(trx_id、roll_pointer)、undo log 版本链、ReadView。
Q7:MVCC 的实现原理?
每行有隐藏列
DB_TRX_ID(最后修改的事务 ID)和DB_ROLL_PTR(指向 undo log 的指针)。每次修改,旧版本写入 undo log,形成版本链。
读时生成 ReadView,记录当前活跃事务 ID 列表。
沿版本链找第一个对当前 ReadView 可见的版本。
Q8:ReadView 的可见性判断规则?
设 m_ids 为活跃事务列表,min_trx_id 为最小值,max_trx_id 为下一个待分配 ID:
trx_id < min_trx_id:已提交,可见。trx_id >= max_trx_id:ReadView 之后才开启,不可见。min_trx_id <= trx_id < max_trx_id:在
m_ids中 → 活跃未提交,不可见;不在
m_ids中 → 已提交,可见。
Q9:当前读和快照读的区别?
快照读:普通
SELECT,走 MVCC,读历史版本,不加锁。当前读:
SELECT ... FOR UPDATE、LOCK IN SHARE MODE、UPDATE、DELETE、INSERT,读最新版本并加锁。
Q10:RR 下幻读完全解决了吗?
快照读:靠 MVCC,不会幻读。
当前读:靠间隙锁/临键锁,基本不会幻读。
但特殊场景(先快照读、再当前读)可能仍出现,所以严格说不是 100% 杜绝。
三、锁与并发类
Q11:InnoDB 有哪些锁?
按粒度:全局锁、表锁、行锁。
行锁类型:
记录锁:锁一条记录。
间隙锁:锁一个区间,防插入。
临键锁
命中记录:临键锁(Next-Key Lock),可能还包含一个额外的间隙锁。普通索引等值查询命中时,加的是 临键锁(Next-Key Lock),即"记录锁 + 前面的间隙锁"的组合,并且还会向后多锁一个间隙(下一个临键锁的间隙部分),以防止插入。
未命中:间隙锁。
插入意向锁:插入前加的间隙锁。
表级:意向共享锁、意向排他锁、MDL。
Q12:RR 下加锁规则?
唯一索引等值命中:记录锁。
唯一索引等值未命中:间隙锁。
普通索引等值:临键锁 + 间隙锁。
范围查询:临键锁。
没走索引:锁全表。
Q13:死锁怎么产生?如何排查和避免?
产生:两个事务互相持有对方需要的锁,循环等待。
排查:
SHOW ENGINE INNODB STATUS,看LATEST DETECTED DEADLOCK。避免:
按固定顺序访问资源;
小事务、快提交;
走索引,避免锁太多行;
设置
innodb_lock_wait_timeout。
Q14:乐观锁和悲观锁的区别?
悲观锁:先加锁再操作,
SELECT ... FOR UPDATE,适合冲突多。乐观锁:不加锁,提交时用版本号/CAS 校验,适合冲突少。
四、日志与持久化类
Q15:redo log、undo log、binlog 的区别?
Q16:什么是两阶段提交?为什么需要?
流程:写 redo log(prepare)→ 写 binlog → 提交 redo log(commit)。
目的:保证 redo log 和 binlog 逻辑一致,崩溃恢复时不会出现主从数据不一致。
如果不用两阶段提交,可能出现 redo 有、binlog 没有,或反过来。
Q17:崩溃恢复时怎么判断用 redo 还是 binlog?
redo 处于 prepare 且 binlog 完整 → 提交。
redo 处于 prepare 且 binlog 不完整 → 回滚。
redo 已 commit → 直接恢复。
Q18:innodb_flush_log_at_trx_commit 和 sync_binlog 怎么取舍?
=1:每次提交都刷盘,最安全,性能最低。=0/2:性能高,但崩溃可能丢最近事务。要求不丢:两个都设 1。
五、实战与设计类
Q19:长事务有什么危害?
占用 undo log,版本链变长,影响 MVCC 查询。
持有锁时间长,阻塞其他事务,易死锁。
占用连接和内存,可能撑大 redo。
主从延迟可能加大。
Q20:如何避免长事务?
控制事务粒度,快进快出。
避免在事务里做远程调用、批量操作。
设置
innodb_lock_wait_timeout、监控information_schema.innodb_trx。
Q21:大事务怎么处理?
拆成小批次提交。
用
LIMIT分批更新/删除。避免一次性
UPDATE全表。
Q22:RR 下 SELECT ... FOR UPDATE 没走索引会怎样?
会锁全表所有行和间隙,并发急剧下降。
所以当前读的 WHERE 一定要走索引。
Q23:MVCC 能解决写写冲突吗?
不能。MVCC 只解决读写冲突,写写冲突仍靠锁。
两个事务改同一行,后改的会阻塞等锁。
六、高频陷阱题
Q24:RR 下两次快照读之间别人插入并提交,会幻读吗?
不会。快照读复用第一次的 ReadView,看不到新插入的行。
Q25:RR 下先快照读,再当前读,会幻读吗?
可能。当前读读最新数据,会看到新插入的行,这就是“幻读”的边界场景。
Q26:SELECT 会加锁吗?
普通
SELECT是快照读,不加锁。SELECT ... FOR UPDATE/LOCK IN SHARE MODE是当前读,加锁。
Q27:事务提交后,undo log 立刻删除吗?
不一定。undo log 还要供 MVCC 读旧版本,等没有 ReadView 需要它时才清理。
Q28:为什么 RR 是 MySQL 默认隔离级别?
在主从复制
STATEMENT格式下,RC 可能导致主从不一致,RR 更安全。STATEMENT 格式下,binlog 记录的是原始 SQL 语句,从库重放这些语句。
在 RC 下,间隙锁基本被禁用,并发插入/更新更容易导致主库和从库执行同一语句时看到不同的数据快照,从而产生主从不一致。
经典例子:主库执行
DELETE FROM t WHERE a = 1和INSERT并发,binlog 按提交顺序记录,从库重放时因缺间隙锁,执行结果可能与主库不同。
在 RR 下,间隙锁能防止这类并发插入,使语句在主从上的执行效果一致,因此对 STATEMENT 复制更安全。
加上 MVCC + 间隙锁,RR 在一致性和并发间取得较好平衡。
Q29:START TRANSACTION 和 BEGIN 区别?
基本等价。
START TRANSACTION可加READ ONLY、WITH CONSISTENT SNAPSHOT等修饰。
Q30:事务的隔离级别怎么设置?
会话级:
SET SESSION TRANSACTION ISOLATION LEVEL ...全局:
SET GLOBAL TRANSACTION ISOLATION LEVEL ...启动参数:
transaction-isolation
评论