MySQL索引调优50问:从B+树到慢查询排查全解析

📅 发布时间:2026/8/30 16:50:45
MySQL索引调优50问:从B+树到慢查询排查全解析 2026 年数据库方向的面试里MySQL 索引调优依然是最高频的考察点。面试官通常不会直接问“索引是什么”而是从一条慢查询开始连续追问 B 树的存储结构、索引为什么失效、Explain 输出代表什么、深分页如何优化甚至把一份死锁日志放在面前让你找根因。这些问题看似零散背后其实是一条完整的分析链路先理解索引的数据结构再判断 SQL 是否能命中索引然后用执行计划验证最后在生产环境用慢查询日志和锁日志复盘。下面把面试中反复出现的题目整理成 50 问按六个主题展开。每个问题都尽量给出原理和可操作结论而不是只背结论。后半部分会给出生产环境慢查询排查清单方便直接对照使用。1. 索引基础先理解 B 树为什么快1.1 索引的底层结构与聚簇、二级索引Q1MySQL 索引到底是什么索引是一种独立的存储结构用来减少扫描行数并帮助存储引擎快速定位记录。InnoDB 的 B 树索引中非叶子节点存储键值和指向子节点的指针叶子节点存储主键或整行数据。没有索引时查询只能全表扫描每一行都要判断有索引时可以按索引顺序快速定位磁盘 IO 次数明显下降。Q2为什么 InnoDB 选择 B 树而不是哈希、红黑树或普通 B 树B 树有三个核心特点只有叶子节点存数据非叶子节点可以放更多键树高通常只有 3 到 4 层几千万行数据也只需要几次磁盘 IO。叶子节点通过链表连接范围查询非常高效。所有查询都要到叶子节点单次查询成本稳定。哈希索引适合等值查询但不支持范围查询红黑树树高更高磁盘 IO 更多普通 B 树的非叶子节点也保存数据单节点能存储的索引项变少范围查询也需要中序遍历不如 B 树的链表结构方便。Q3聚簇索引和二级索引有什么区别InnoDB 使用主键作为聚簇索引叶子节点保存完整行记录。二级索引的叶子节点保存索引列和主键值。查询时先扫描二级索引找到主键再到聚簇索引取完整行这个步骤叫回表。MyISAM 没有聚簇索引索引与数据分离索引叶子保存的是行地址。Q4回表一定发生吗不一定。如果查询所需字段全部在二级索引中优化器可以直接扫描二级索引返回结果这就是覆盖索引。例如联合索引(user_id, status)查询SELECT status FROM t WHERE user_id 123时不需要回表。如果SELECT *而二级索引没有包含所有列通常就需要回表。1.2 索引类型与建索引的基本判断Q5MySQL 索引有哪些类型按用途分普通索引、唯一索引、主键索引、全文索引、空间索引。按底层结构分B 树索引、Hash 索引、R-tree 索引。InnoDB 对外主要使用 B 树也会在内存中维护自适应哈希索引来加速等值访问。Memory 引擎默认使用 Hash 索引适合精确匹配不适合范围查询。Q6唯一索引和普通索引怎么选普通索引允许重复写入时可以利用 change buffer 延迟合并二级索引写放大更小。唯一索引插入时必须立即检查唯一性change buffer 对唯一索引的优化效果有限。如果业务确实要求某个字段不能重复必须加唯一索引如果只是加速查询不要为了“看起来规范”而滥用唯一索引。Q7覆盖索引有什么价值覆盖索引可以减少一次回表减少大量随机 IO。对高频查询来说可以把where列、order by列和select需要的列组合成联合索引。代价是索引存储空间变大、写入维护成本增加所以要优先覆盖核心查询不要试图让所有查询都走覆盖索引。Q8如何判断一条 SQL 应该建什么索引先提取查询里的where、join、order by、group by、distinct字段。优先考虑等值条件列把区分度高的列放在联合索引前面范围条件列放到后面最后用EXPLAIN验证key、rows和Extra。区分度太低的列比如“状态”字段大部分值都是已完成除非过滤后行数极少否则建立索引收益有限。2. 联合索引与最左前缀面试连环问的重灾区2.1 联合索引存储方式和最左前缀Q9联合索引(a,b,c)的存储结构是什么联合索引仍然是一棵 B 树排序规则是先按a排序a相同再按b排序b相同再按c排序。所以查询条件必须能利用这种前缀顺序。如果跳过a直接使用b条件无法通过联合索引快速定位只能扫描大量索引项。Q10最左前缀原则具体指什么最左前缀原则指的是联合索引必须从最左侧列开始连续匹配。查询条件包含a或者ab或者abc才有机会走到索引。如果条件是b或c单独出现通常无法使用如果条件包含a和c可能走索引但a参与定位c只能通过索引条件下推或回表后过滤。Q11范围查询为什么会让后面的列失效假设索引是(a,b,c)查询条件是a1 and b10 and c1。B 树按a,b,c排序a等值后b10是一个范围在这个范围内c的顺序不再全局有序因此c1不能继续用于索引定位。这是“后续列无法继续定位”不是整个索引全部失效。回答时不要把这个概念说错。Q12a1 and b2 and c3能命中索引吗能优化吗a1可以用到索引前缀b2可以用范围条件c3通常无法继续参与索引定位。优化方式是调整联合索引顺序把等值条件列放在前面范围条件列放最后例如建立(a,c,b)。这样a和c都能用于等值定位b只用于范围扫描。2.2 联合索引的列顺序设计Q13联合索引能用于 ORDER BY 和 GROUP BY 吗可以但条件比较严格。order by的字段必须与联合索引的最左前缀一致排序方向一致并且中间没有范围条件破坏顺序。例如索引(user_id, login_time)查询WHERE user_id1 ORDER BY login_time DESC可以避免 filesort如果查询WHERE login_time 2026-01-01 ORDER BY user_id通常无法利用索引排序。group by类似匹配索引前缀可以减少临时表但要以Extra是否出现Using temporary为准。Q14联合索引的列顺序怎么确定基本规则是等值条件列优先区分度高的放前面然后放范围条件列最后考虑排序需求。但也要看业务查询频率。如果a区分度低但几乎每次都出现b区分度高但偶尔出现索引(b,a)能单独服务b的查询比(a,b)更灵活。最终顺序要结合统计信息和真实查询记录确认不要只凭经验。3. 索引失效不要只背结论要会用 Explain 验证3.1 最常见的 6 个失效场景Q15varchar 字段查询条件不带引号为什么会导致索引失效常见的坑是WHERE phone 13800001111其中phone是 varchar查询参数是数字。MySQL 会进行隐式类型转换把字符串列转成数字再比较索引列上发生函数式转换B 树无法正常定位通常表现为扫描行数暴增。解决办法是让参数带上引号WHERE phone 13800001111。Q16在索引列上使用函数索引一定失效吗在传统 B 树索引下WHERE DATE(create_time) 2026-01-01会导致索引列被函数改写无法从根节点定位。MySQL 5.7 开始支持函数索引可以写成ALTER TABLE t ADD INDEX idx_create_date ((DATE(create_time)))。另一种更稳妥的写法是范围查询SELECT * FROM t WHERE create_time 2026-01-01 00:00:00 AND create_time 2026-01-02 00:00:00;这种写法既保留索引又能命中更精准的范围。Q17LIKE %abc一定不走索引吗LIKE %abc因为不确定以什么字符开头B 树无法定位起始位置通常不走索引。LIKE abc%有明确前缀可以走索引。如果业务确实需要前导模糊查询可以结合全文索引或覆盖索引评估但不要直接认为LIKE永远不能走索引。Q18OR 一定会导致索引失效吗不一定。如果 OR 两侧的列都有可用索引优化器可能使用 index_merge分别扫描索引再合并。如果其中一侧没有索引通常全表扫描更划算。遇到 OR 不能直接背结论先用EXPLAIN看type和key。也可以把 OR 改写成 UNION ALL但要对比执行计划是否真的更优。Q19NOT IN 一定走不了索引吗不绝对。NOT IN、NOT LIKE是否走索引取决于优化器对返回行数的估算。如果返回行数占比低可能走索引如果占比高全表扫描更合理。面试时不要回答“NOT IN 一定失效”而要说要看执行计划中的rows和filtered。也可以改写为LEFT JOIN ... IS NULL或NOT EXISTS但同样需要验证。Q20为什么优化器有时候会不选索引常见原因有统计信息过期、索引区分度太低、优化器估算了大量回表成本或者查询写法触发隐式转换和函数。处理顺序是先ANALYZE TABLE t;更新统计信息再用EXPLAIN查看执行计划必要时在测试环境用FORCE INDEX对比响应时间。生产环境不建议直接加 hint要找到根本原因。3.2 索引失效场景速查表场景示例是否可能失效原因建议隐式转换varchar 列 数字通常失效索引列上发生类型转换查询参数加引号函数包裹列DATE(create_time) ...通常失效索引列被函数改写使用范围查询或函数索引前导模糊LIKE %abc通常失效无法定位前缀使用 abc% 或覆盖索引OR 一侧无索引a1 OR b2可能失效无法合并索引结果两侧建索引或改 UNION ALLNOT INid NOT IN (...)不绝对优化器估算全表更优用 EXPLAIN 看 rows统计信息过期索引未生效不绝对行数估算不准执行 ANALYZE TABLE跳过联合索引前列WHERE b1通常失效无法利用最左前缀建匹配索引或评估 Skip Scan3.3 判断是否命中的正确姿势不要只看“索引有没有建”。查询是否走索引要看EXPLAIN输出的possible_keys、key、key_len、rows和Extra。possible_keys只表示候选索引真正使用的是keykey_len可以判断联合索引实际用到了哪几列rows越低代表扫描越少Extra会提示回表、排序和临时表问题。EXPLAIN SELECT id, order_no, amount FROM t_order WHERE order_no 2026001;执行计划里typeref或range通常说明索引生效typeALL说明全表扫描需要进一步排查。4