每日问答mysql篇
1.InnoDB 为什么选用 B + 树做索引,而不用 B 树、哈希索引?
InnoDB 选用 B + 树:①非叶子节点只存索引键,一页存放更多索引,树矮,磁盘 IO 少;②叶子节点双向链表,非常适合范围查询、排序;③B 树叶子没有链表,范围查询性能差;hash 索引等值查询快,但不支持范围、排序,还有哈希冲突。
2.什么是聚簇索引、二级索引?什么叫回表?什么是覆盖索引,覆盖索引如何避免回表?
聚簇索引就是主键索引,叶子节点存放整行数据;二级索引叶子存索引列 + 主键。通过二级索引拿到主键,再去聚簇索引拿完整行就是回表。覆盖索引,查询字段全部在二级索引中,无需回表。
3.请讲联合索引最左前缀原则,并且说出几种索引失效的场景?
联合索引 (a,b,c),索引排序顺序 a→b→c。查询必须匹配最左边的起始列,才能用上索引。
例:where a=? 能用;where a=? and b=? 能用;where b=? 不能直接走联合索引,跳过最左 a,索引失效。
失效场景汇总:①索引列做函数、运算;②like 以 % 开头;③隐式类型转换;④not in、!=;⑤or 一侧无索引;⑥违背最左前缀。
4.MVCC 有哪几个核心组成?快照读、当前读分别对应哪些 SQL?
完整 MVCC 三大核心组成:
- undo log:保存数据历史版本,构建版本链
- 行隐藏字段 :
trx_id(最后修改事务 ID)、roll_pointer(指向 undo 上一个历史版本指针) - Read‑View 读视图:控制我能看到哪些版本的数据(RC/RR 最大区别就在 Read‑View 生成时机)
快照读:普通
select * from table,读取 undo 版本链历史快照,不加行锁。当前读:
select ... for update/select ... lock in share mode/update/delete,读取最新数据,会上锁。
💡短句总结:
MVCC 三要素:undo log、行隐藏字段 trx_id 与 roll_pointer、Read‑View。快照读普通 select;当前读是加锁查询以及 update、delete 语句。间隙锁属于锁体系,不属于 MVCC。
5.RC 读已提交 和 RR 可重复读,MVCC 的 Read‑View 生成时机有什么区别?RR 级别是怎么解决幻读的?
1.Read‑View 生成时机(RC vs RR)
- RC(读已提交):每执行一次普通 select,就生成一个新 Read‑View
每次查询都拿最新视图,所以能看到别的事务已经提交的数据,会出现不可重复读,同一个事务内两次 select 结果不一样。 - **RR(可重复读,InnoDB 默认):事务开启之后,**第一次执行快照读 select 的时候生成一次 Read‑View,整个事务复用这一个视图 **。
整个事务全程用同一个 Read‑View,所以同一个事务多次 select 读到的数据不变,实现可重复读。
记忆口诀:RC 每次查询新建视图;RR 事务第一次 select 生成,全程复用。
2.RR 怎么解决幻读(高频大坑)
幻读:同一个事务,两次当前读,中间别的事务插入新行,第二次读到多出的行。
分两层:
- 快照读(普通 select):靠 MVCC 的 Read‑View,读取历史版本,看不到别人新插入的数据,快照读没有幻读。
- 当前读(update/delete/for update):MVCC 管不住,会产生幻读。InnoDB 使用 Next‑Key 临键锁 (记录锁 + 间隙锁),锁住扫描到记录以及记录之间的间隙,禁止其他事务在间隙插入数据,解决当前读幻读。
⚠️:RR 不是单靠 MVCC 解决幻读;快照读靠 MVCC,当前读靠临键锁,两者配合才解决幻读。
极简口述答案
RC 隔离级别,每一次 select 都会生成新的 Read‑View ;RR 隔离级别,事务第一次快照读时生成 Read‑View,整个事务复用同一个视图 。
RR 解决幻读分为两部分:快照读依靠 MVCC 的 Read‑View 读取历史快照避免幻读;而 update、delete、for update 这类当前读,依靠 Next‑Key 临键锁锁住间隙,阻止其他事务插入新数据,解决当前读的幻读。
6.ACID 四个特性分别依靠 InnoDB 哪些机制来保证?redo log、undo log 各自作用是什么?什么是 WAL?
- ACID 四个特性,分开对应底层机制
- A 原子性:事务要么全成要么全失败 → undo log,失败回滚
- C 一致性:是最终业务结果,不是某一个组件直接实现,原子 + 隔离 + 持久共同保障
- I 隔离性:多个事务之间互不干扰 → MVCC + 锁体系(行锁、临键锁等)
- D 持久性:事务提交后数据永久生效,宕机不丢 → redo log
- redo log:记录数据页修改之后的物理日志,宕机恢复用。事务提交,redo log 刷盘,保证崩溃之后可以恢复已提交事务。
- undo log:保存数据修改前的历史版本,用于事务回滚 + MVCC 生成版本链,不是简单事务操作日志。
- WAL:Write‑Ahead‑Log 预写日志。修改数据页之前,先把 redo log 写入磁盘,再更新内存的数据页,晚一点再刷脏页到磁盘。不是先写缓存再落盘。
✅精简口述版:
A 原子性由 undo log 实现,事务失败可以回滚;D 持久性依靠 redo log;I 隔离性靠锁和 MVCC;C 一致性是业务最终结果,由 AID 共同保证。
redo log 记录数据页物理修改,用于崩溃恢复;undo log 保存修改前数据,用于回滚和 MVCC 版本链。
WAL 预写日志机制:修改内存数据页之前,先把 redo log 持久化磁盘,脏页可以后续慢慢刷盘。
7.Record 记录锁、Gap 间隙锁、Next‑Key 临键锁分别是什么?什么场景会产生间隙锁?
前提:只有 RR 可重复读隔离级别才会出现间隙锁、临键锁;RC 没有间隙锁,只有记录锁
- Record 记录锁(行锁)
锁住已存在的某一行真实记录。只锁这条数据,不让别的事务修改删除。 - Gap 间隙锁
锁住两条记录中间的空隙,不锁记录本身。目的:禁止其他事务往这个间隙插入新数据,防止幻读。
间隙可以是:索引最前面、两条记录中间、索引最后面之后。
- Next‑Key Lock 临键锁 = Record 锁 + Gap 间隙锁
InnoDB RR 下默认的行锁算法。既锁住当前这条记录,同时锁住这条记录前面的间隙,防止插入。
什么时候产生间隙锁
- RR 隔离级别;
- 使用当前读:update / delete / select ... for update;
- 查询条件走索引,条件命中不到真实存在的数据,就会产生间隙锁;
举例:表 id 有 1、5、10;执行
update set name=? where id=7,7 不存在,会锁住 5‑10 这个间隙,其他事务不能插入 id=6,7,8,9。
重点坑:RC 隔离级别没有间隙锁,所以 RC 无法解决当前读幻读。
精简短句
Record 记录锁:锁住真实存在的行记录。
Gap 间隙锁:锁住索引之间空隙,阻止插入;
Next‑Key 临键锁 = 记录锁 + 间隙锁,RR 下默认行锁。
间隙锁产生条件:RR 隔离级别,当前读,条件走索引,查询的数据不存在,锁住间隙防止插入,解决幻读。
8.死锁产生四个必要条件,线上如何排查 MySQL 死锁?
死锁 4 个必要条件(必须全部满足才会死锁)
- 互斥条件:资源同一时刻只能一个事务占用(行锁就是互斥)
- 不可剥夺:已经拿到锁的事务,别的事务不能强行抢走锁,只能自己释放
- 请求并保持:事务已经持有部分锁,同时还要去申请别的锁资源,不释放手上已有的
- 循环等待:两个 / 多个事务,互相持有对方想要的锁,形成环路等待。
记忆:互斥、不可剥夺、请求保持、循环等待,四个缺一不可;破坏任意一个就消除死锁。
MySQL 死锁排查
- 执行命令:
show engine innodb status;
输出结果里找到 LATEST DETECTED DEADLOCK 板块,里面记录最近一次死锁:两个事务分别执行什么 SQL、各自持有什么锁、等待什么锁。 - 看错误日志,也会打印死锁信息。
- 定位业务 SQL:调整加锁顺序,尽量统一获取锁的顺序,避免循环等待;减少大事务,事务尽快提交;RC 隔离级别消除间隙锁,降低死锁概率。
口述精简版:
死锁四条件:互斥、不可剥夺、请求并保持、循环等待。排查用
show engine innodb status,查看 LATEST DETECTED DEADLOCK,拿到死锁两条事务 SQL;解决手段统一加锁顺序、缩小事务、可改为 RC 减少间隙锁。
9.explain 执行计划中 type 字段,从最优到最差排序,ALL 代表什么?
🔔完整 type 优先级(从最优 → 最差,面试必须背顺序 )
system > const > eq_ref > ref > range > index > ALL
简单解释关键几项:
system:表只有一行数据,极少出现const:主键 / 唯一索引等值查询,最多匹配一行eq_ref:关联查询,主键、唯一索引匹配ref:普通索引等值匹配range:范围查询,> < between inindex:索引全扫描,扫全部索引叶子,不回表;比全表扫描 ALL 快,但数据量大依然慢ALL:全表扫描,扫描聚簇索引全部数据行,性能最差
业务调优目标:尽量达到
range/ref,杜绝 index、ALL。
最后留个问题那么用到了索引就一定快吗?