简介:这是一份面向后端开发求职者与在校学生的 MySQL 面试知识点总结文档,围绕数据库原理与索引机制展开,帮助读者系统梳理高频考点、补齐知识盲区。内容涵盖关系型与非关系型数据库的区别、一条 SQL 语句从连接器到执行器的完整执行流程、索引的使用原因与哈希表/有序数组/搜索树三种底层数据结构,并深入对比 MyISAM 与 InnoDB 的 B+ 树索引实现差异、聚簇索引与二级索引的叶子节点存储方式,以及覆盖索引、索引下推、change buffer、redo log 与 binlog 等进阶话题,还整理了模糊匹配、函数计算、隐式转换、OR 条件等导致索引失效的常见场景。资源包共 1 个 docx 文件,约 40KB,以问答形式组织,条理清晰便于速查背诵。目前已有 251 人学习下载,适合面试前集中复习或日常查漏补缺使用。
1. MySQL 面试题到底在考什么:从一条慢查询说起
很多人背 MySQL 面试题的方式是打开一份 PDF,从「什么是事务」开始往下刷,刷到「MVCC 原理」就卡住,最后记住的只有几个名词。但真实面试里,面试官往往不会直接问你「说说索引」,而是丢一条 SQL 过来:「这条查询为什么慢?你怎么优化?」——这才是 MySQL 面试题的核心考法:不是考你背了多少概念,而是考你能不能把概念落到一条具体的 SQL、一张具体的表、一次具体的执行计划上。
MySQL 面试题覆盖的范围其实很集中:索引与执行计划、事务与锁、日志与持久化、主从复制与高可用、分库分表与连接池。这些点看起来散,但底层是一条线串起来的——数据怎么存、怎么查、怎么保证不出错。你如果只背结论,面试官换个场景追问一句就露馅;你如果理解了这条线,大部分题都能自己推出来。这篇笔记就按这条线走一遍,把每类高频题拆成「原理是什么、怎么验证、参数怎么调、坑在哪」,让你不只是能答,还能在本地跑出来看。
2. 索引与执行计划:面试第一道坎怎么过
2.1 从 B+ 树到最左前缀:为什么你的索引没走
MySQL 面试题里出现频率最高的就是索引。面试官问「为什么加了索引还是慢」,八成是因为索引没走对。要理解这个,得先知道 InnoDB 的索引结构是 B+ 树——非叶子节点只存键值,叶子节点存数据并用链表串起来。这个结构决定了三件事:等值查询快、范围查询快、但前缀模糊查询(like '%xx')走不了索引。
更常考的是联合索引的最左前缀原则。假设你建了idx_a_b_c (a, b, c),那么where a=1、where a=1 and b=2、where a=1 and b=2 and c=3都能走索引,但where b=2、where b=2 and c=3走不了。原因在于 B+ 树是按 a 先排序、a 相同再按 b 排序、b 相同再按 c 排序的,你跳过 a 直接找 b,树没法定位。
这里有个容易被追问的点:where a=1 and c=3能不能走索引?答案是能走,但只用到 a 这一列,c 用不上。因为 a 确定后,c 在树里不是连续有序的。面试官如果追问「那怎么优化」,你可以说把 c 提到联合索引第二位,或者建覆盖索引。
验证这些结论最直接的方式是EXPLAIN。下面这条命令是必须会的:
-- 建一张测试表,模拟订单场景 CREATE TABLE t_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, status TINYINT NOT NULL DEFAULT 0, amount DECIMAL(10,2) NOT NULL, created_at DATETIME NOT NULL, KEY idx_user_status (user_id, status) ) ENGINE=InnoDB; -- 用 EXPLAIN 看执行计划 EXPLAIN SELECT * FROM t_order WHERE user_id = 1001 AND status = 1;执行后重点看几个字段:type最好是ref或range,出现ALL就是全表扫描;key显示实际用的索引;rows是预估扫描行数;Extra里出现Using filesort或Using temporary就要警惕。如果Extra是Using index,说明是覆盖索引,不用回表,这是最好的情况。
参数上有一个常被忽略的:optimizer_switch。MySQL 5.6 之后有索引下推(index condition pushdown),默认开启。它能把 where 条件下推到存储引擎层过滤,减少回表次数。你可以用SET optimizer_switch='index_condition_pushdown=off'关掉对比效果,面试时能说出这个细节会加分。
2.2 用 EXPLAIN 和慢查询日志定位问题:一套可复现的排查流程
光会看 EXPLAIN 还不够,面试官常问「线上怎么发现慢查询」。标准答案是慢查询日志加pt-query-digest或者performance_schema。我一般按这个流程走:
第一步,确认慢查询日志开着:
SHOW VARIABLES LIKE 'slow_query_log'; SHOW VARIABLES LIKE 'long_query_time';如果slow_query_log是 OFF,用SET GLOBAL slow_query_log = ON;打开,long_query_time默认 10 秒,线上一般调到 1 秒甚至 0.5 秒。注意这个参数是会话级的,改完只对新连接生效。
第二步,用mysqldumpslow或pt-query-digest聚合分析。mysqldumpslow -s t -t 10 slow.log能按执行时间排出前 10 条。这一步的目的是找出「哪类 SQL 最耗资源」,而不是逐条看。
第三步,对找出的 SQL 跑 EXPLAIN,看是否走索引、扫描行数是否合理。如果rows远大于实际返回行数,说明索引选择性差,考虑换索引或加联合索引。
第四步,用SHOW PROFILE看具体耗时在哪个阶段:
SET profiling = 1; SELECT * FROM t_order WHERE user_id = 1001 AND status = 1; SHOW PROFILES; SHOW PROFILE FOR QUERY 1;SHOW PROFILE会列出Sending data、Sorting result等阶段的耗时。如果Sending data占比高,通常是回表太多或返回数据量大;如果Sorting result高,说明有 filesort,需要优化排序字段的索引。
这里有个血泪经验:很多人看到type=ALL就急着加索引,但忽略了rows很小的情况。如果一张表只有几百行,全表扫描比走索引还快,因为走索引要回表。面试时能说出「小表全扫不一定慢」这个边界,说明你真跑过。
3. 事务与锁:MVCC 和间隙锁到底怎么答
3.1 一条 update 背后的锁:从行锁到间隙锁
事务和锁是 MySQL 面试题里最容易翻车的部分。面试官问「什么是 MVCC」,你背出「多版本并发控制,通过 undo log 和 read view 实现」只能算及格。真正拉开差距的是追问:「RR 隔离级别下,select ... for update加的是什么锁?」
InnoDB 的锁按粒度分行锁和表锁,行锁又分记录锁(Record Lock)、间隙锁(Gap Lock)、临键锁(Next-Key Lock)。在 RR 隔离级别下,select ... for update默认加的是临键锁,也就是记录锁加间隙锁,锁住记录本身和它前面的间隙。这样做的目的是防止幻读。
举个例子,表里 id 有 1、5、10 三条记录,你执行select * from t where id > 3 for update,锁住的是 (3,5]、(5,10]、(10,+∞) 这几个区间。这时候另一个事务插入 id=7 会被阻塞,插入 id=2 不会。这就是间隙锁的作用。
但间隙锁有个坑:它只在 RR 级别生效,RC 级别下没有间隙锁,只有记录锁。所以如果你的业务从 RC 切到 RR,可能会突然出现大量锁等待。面试时如果被问到「为什么线上锁等待突然增多」,先确认隔离级别有没有变。
验证锁等待可以用这两张表:
-- 会话 1 BEGIN; SELECT * FROM t_order WHERE user_id = 1001 FOR UPDATE; -- 会话 2(会阻塞) BEGIN; SELECT * FROM t_order WHERE user_id = 1001 FOR UPDATE; -- 另开会话查锁等待 SELECT * FROM performance_schema.data_lock_waits; SELECT * FROM information_schema.innodb_trx;innodb_trx里能看到当前运行的事务、锁等待时间、阻塞的 SQL。data_lock_waits是 MySQL 8.0 的表,5.7 用innodb_lock_waits。这一步在面试里能说出来,说明你真排查过线上问题。
3.2 MVCC 的 read view 怎么读:三个参数决定可见性
MVCC 的核心是 read view。每次快照读(普通 select)会生成一个 read view,里面有几个关键字段:m_ids(当前活跃事务 ID 列表)、min_trx_id(最小活跃事务 ID)、max_trx_id(下一个要分配的事务 ID)。判断某行版本是否可见的规则是:
- 如果行的
trx_id小于min_trx_id,说明这个版本在 read view 创建前就提交了,可见。 - 如果行的
trx_id大于等于max_trx_id,说明这个版本是 read view 创建后才开始的,不可见。 - 如果
trx_id在m_ids里,说明事务还活跃,不可见。 - 否则可见。
这套规则面试时能画出来最好,画不出来就用文字说清楚。常被追问的是「RR 和 RC 的 read view 有什么区别」——RR 只在第一次快照读时生成 read view,之后复用;RC 每次快照读都重新生成。这就是为什么 RR 能可重复读,RC 不能。
这里有个容易答错的点:MVCC 只解决快照读的并发,当前读(select ... for update、update、delete)还是要加锁。面试官如果问「MVCC 能解决幻读吗」,标准答案是「快照读下能,当前读下不能,需要间隙锁配合」。
4. 日志与持久化:redo log 和 binlog 的配合
4.1 两阶段提交:一条 update 到底写了几个文件
MySQL 面试题里关于日志的高频问题是「一条 update 语句的执行流程」。完整答案是:先写 undo log(用于回滚),再写 redo log(prepare 状态),再写 binlog,最后提交 redo log(commit 状态)。这就是两阶段提交。
为什么要两阶段提交?因为 redo log 和 binlog 是两套独立的日志,如果不协调,可能出现 redo log 写了但 binlog 没写,或者反过来。主从复制靠 binlog,崩溃恢复靠 redo log,两者不一致就会导致主从数据不一致或恢复后数据丢失。
redo log 是物理日志,记录「在某个数据页做了什么修改」,循环写,空间固定。binlog 是逻辑日志,记录「执行了什么 SQL」或「行变更」,追加写,空间不固定。面试时能说清这两个区别,基本就过关了。
参数上要关注innodb_flush_log_at_trx_commit和sync_binlog。前者控制 redo log 的刷盘策略:0 是每秒刷,1 是每次提交刷,2 是写到 OS cache 每秒刷。后者控制 binlog 刷盘:0 是交给系统,1 是每次提交刷。线上一般设innodb_flush_log_at_trx_commit=1和sync_binlog=1,保证不丢数据,但性能会降。如果业务能容忍少量丢失,可以设 2 和 0 换性能。
4.2 用 mysqlbinlog 验证主从数据一致性
面试官问「怎么验证主从一致」,很多人只会说SHOW SLAVE STATUS看Seconds_Behind_Master。但这个值不准,主库压力大时它会飘。更可靠的方式是用pt-table-checksum或者直接解析 binlog 对比。
一个可复现的验证方法是:在主库执行一批写操作,记录 binlog 位置,然后在从库用mysqlbinlog解析对应区间的日志,看是否都应用了。
# 在主库查看当前 binlog 位置 mysql -e "SHOW MASTER STATUS\G" # 执行一批写操作 mysql -e "INSERT INTO t_order (user_id, status, amount, created_at) VALUES (2001, 1, 99.00, NOW());" # 在从库查看复制状态 mysql -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master"Slave_IO_Running和Slave_SQL_Running都必须是 Yes,任何一个 No 都说明复制断了。Seconds_Behind_Master只能参考,真正判断延迟要看Relay_Log_Pos和Exec_Master_Log_Pos的差值。
如果复制断了,常见原因是主键冲突或从库写入。排查步骤是看Last_SQL_Error,然后决定是跳过这个事务(SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1)还是重建从库。跳过事务是后悔药,能不用就不用,因为会导致数据不一致。
5. 避坑与排查:MySQL 面试题里最容易答错的五个点
5.1 坑一:以为加了索引就一定走
现象:明明建了索引,EXPLAIN 显示type=ALL。
原因:索引列上用了函数、隐式类型转换、或者最左前缀没满足。比如where date(created_at) = '2026-01-01'用不了索引,因为对列做了函数运算;where user_id = '1001'如果 user_id 是 bigint,字符串会隐式转换,也可能不走索引。
解决:把函数移到等号右边,比如where created_at >= '2026-01-01' and created_at < '2026-01-02';类型保持一致,数字列就传数字。
5.2 坑二:RR 级别下以为没有幻读
现象:面试时答「RR 解决了幻读」,被追问「那为什么还能插入」。
原因:RR 只解决了快照读的幻读,当前读(for update)下如果没有间隙锁,还是能插入。而且间隙锁只在 RR 下生效,RC 下没有。
解决:答的时候区分快照读和当前读,说清间隙锁的作用范围。如果面试官继续追问「间隙锁有什么代价」,答「会增加锁冲突概率,可能死锁」。
5.3 坑三:把 redo log 和 binlog 搞混
现象:被问「崩溃恢复用哪个日志」,答成 binlog。
原因:没理解两套日志的分工。redo log 是 InnoDB 层的,负责崩溃恢复;binlog 是 Server 层的,负责主从复制和数据归档。
解决:记住「redo 管恢复,binlog 管复制」。再记一个细节:redo log 是循环写的,binlog 是追加写的。
5.4 坑四:连接池配得越大越好
现象:线上连接数一高就报Too many connections,于是把max_connections调到几千。
原因:连接数不是越大越好,每个连接占内存,而且上下文切换开销大。MySQL 默认max_connections=151,调到几千可能导致内存耗尽。
解决:先看Threads_connected和Threads_running,如果 running 远小于 connected,说明大量连接空闲,应该优化连接池配置而不是加连接数。连接池的maxPoolSize一般设 CPU 核数的 2 到 4 倍,配合connectionTimeout和idleTimeout。
5.5 坑五:分库分表后还按单表思路写 SQL
现象:分库分表后查询变慢,甚至查不到数据。
原因:分片键没选对,或者跨分片查询。比如按 user_id 分片,但查询条件只有 order_id,就要扫所有分片。
解决:分片键要选查询频率最高的字段,尽量让查询能定位到单个分片。跨分片查询用中间件聚合,或者建冗余表。面试时如果被问「分库分表后怎么分页」,答「先在各分片查再聚合,或者用二次查询法」。
6. 进阶技巧:用 performance_schema 定位锁等待和慢 SQL
面试里如果能把performance_schema用起来,基本能超过八成候选人。它比慢查询日志更实时,能看到当前正在执行的 SQL、锁等待、IO 情况。下面这套查询是我排查线上问题时最常用的:
-- 查看当前正在执行的 SQL(按耗时排序) SELECT t.PROCESSLIST_ID, t.PROCESSLIST_USER, t.PROCESSLIST_TIME, t.PROCESSLIST_STATE, t.PROCESSLIST_INFO FROM performance_schema.threads t WHERE t.PROCESSLIST_INFO IS NOT NULL ORDER BY t.PROCESSLIST_TIME DESC LIMIT 10; -- 查看锁等待关系 SELECT w.REQUESTING_ENGINE_TRANSACTION_ID AS waiting_trx, w.BLOCKING_ENGINE_TRANSACTION_ID AS blocking_trx, w.REQUESTING_THREAD_ID, w.BLOCKING_THREAD_ID FROM performance_schema.data_lock_waits w; -- 查看当前事务 SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, trx_query FROM information_schema.innodb_trx ORDER BY trx_started;第一段查当前活跃 SQL,PROCESSLIST_TIME是执行秒数,超过 5 秒就要关注。第二段查锁等待,能直接看出谁在等谁。第三段查事务,trx_state是 RUNNING 或 LOCK WAIT,trx_query是当前 SQL。
参数上要确认performance_schema是开的:SHOW VARIABLES LIKE 'performance_schema';默认 ON。如果 OFF,需要重启并加配置。另外performance_schema本身有内存开销,线上如果内存紧张,可以关掉部分 consumer。
一个具体技巧:如果发现锁等待,先看blocking_trx对应的线程 ID,用KILL杀掉阻塞源。但杀之前要确认这个事务是不是重要业务,别把正常的长事务误杀了。我一般会先看trx_started,如果跑了很久还没提交,大概率是代码里忘了 commit 或者事务范围太大。
最后说个我自己的习惯:每次面试前,我会在本地起一个 MySQL,把索引、事务、锁、日志这几个点各跑一遍,用 EXPLAIN 和 performance_schema 看实际结果。背下来的答案和跑出来的结果,在面试官追问时的底气完全不一样。希望帮到你。
本文还有配套的精品资源,点击获取