📖 本篇简介:EXPLAIN 是 SQL 优化的"显微镜",也是面试中考察实战能力的核心。本篇基于 MySQL 8.0,逐列讲解 EXPLAIN 输出(id、select_type、table、type、possible_keys、key、key_len、rows、filtered、Extra),重点剖析 type 列的 system/const/ref/range/index/ALL 性能等级、Extra 字段常见值(Using index、Using where、Using temporary、Using filesort、Using join buffer)的含义与优化方向。学完后你能拿到一条慢 SQL 就知道"走没走索引、扫了多少行、要不要 filesort",并能讲清楚 EXPLAIN ANALYZE 的真实执行成本。
目录
- 题目 1:EXPLAIN 能告诉你什么,什么时候用它
- 题目 2:EXPLAIN 各列含义,重点看哪几列
- 题目 3:type 列有哪些取值,性能排序如何
- 题目 4:const 与 system 的区别,什么时候出现
- 题目 5:ref 与 eq_ref 的区别
- 题目 6:range 索引扫描的常见场景
- 题目 7:index 与 ALL 的区别,如何避免全表扫描
- 题目 8:key_len 怎么算,有什么用
- 题目 9:rows 与 filtered 列的含义,准确度如何
- 题目 10:Extra 列 Using index 是什么意思
- 题目 11:Using where 一定不好吗
- 题目 12:Using temporary 是什么,怎么优化
- 题目 13:Using filesort 是什么,怎么优化
- 题目 14:Using join buffer 是什么意思
- 题目 15:possible_keys 为 NULL 但 key 有值是怎么回事
- 题目 16:key 列显示的索引不是期望的,为什么
- 题目 17:id 列相同与不同代表什么
- 题目 18:select_type 有哪些常见值
- 题目 19:EXPLAIN ANALYZE 与传统 EXPLAIN 区别
- 题目 20:常见坑与注意事项
- 速查表:EXPLAIN 列速记
- 复习清单
- 练习题
题目 1:EXPLAIN 能告诉你什么,什么时候用它
考点
- 执行计划的作用
- 不实际执行 SQL(ANALYZE 除外)
- 优化 SQL 的第一步
参考答案
1. EXPLAIN:输出 MySQL 优化器为这条 SQL 选择的执行计划,包括访问哪些表、用哪个索引、扫描多少行、是否排序/临时表。
2. 特点:普通 EXPLAIN 不真正执行 SQL,只输出"估计"的计划;EXPLAIN ANALYZE(8.0.18+)会真正执行并输出真实耗时与行数。
3. 什么时候用:
- SQL 上线前评审;
- 慢查询日志抓出来的 SQL;
- 业务突然变慢时排查。
4. 基本用法:
-- 本示例基于 MySQL 8.0
EXPLAIN SELECT * FROM emp WHERE dept = '研发';
EXPLAIN FORMAT=JSON SELECT ...; -- 更详细的 JSON 格式
EXPLAIN ANALYZE SELECT ...; -- 8.0.18+,真实执行并输出耗时
追问
- 追问:EXPLAIN 的 rows 是精确值吗?应对:是优化器基于索引统计的估算值,与真实值可能差几倍,要结合 EXPLAIN ANALYZE 看真实行。
- 追问:EXPLAIN 会锁表吗?应对:普通 EXPLAIN 不会;EXPLAIN ANALYZE 会真正执行,对写 SQL 要小心。
题目 2:EXPLAIN 各列含义,重点看哪几列
考点
- 12 列含义
- 重点关注 type/key/rows/Extra
- 输出格式
参考答案
| 列 | 含义 |
|----|------|
| id | 查询序号,越大越先执行 |
| select_type | 查询类型(SIMPLE/PRIMARY/SUBQUERY 等) |
| table | 访问的表名 |
| type | 访问类型(性能等级,重点看) |
| possible_keys | 可能用到的索引 |
| key | 实际用到的索引 |
| key_len | 使用的索引长度 |
| ref | 索引比较的来源 |
| rows | 预估扫描行数 |
| filtered | 过滤后的百分比 |
| Extra | 额外信息(重点看) |
重点关注:type(访问类型)、key(是否走索引)、rows(扫描行数)、Extra(是否临时表/排序)。
追问
- 追问:FORMAT=JSON 比默认好在哪?应对:JSON 包含更详细的 cost_info、读取字节数、子查询信息,适合深度分析。
- 追问:EXPLAIN 能看优化器选择理由吗?应对:8.0 可用
EXPLAIN FORMAT=TREE或OPTIMIZER_TRACE。
题目 3:type 列有哪些取值,性能排序如何
考点
- 性能从好到差排序
- 常见的几种类型
- 生产至少要到 range
参考答案
从最好到最差:
| type | 含义 |
|------|------|
| system | 表只有一行(系统表) |
| const | 主键/唯一索引等值匹配,最多一行 |
| eq_ref | JOIN 时主键/唯一索引被引用,一行匹配 |
| ref | 普通索引等值匹配,可能多行 |
| range | 索引范围扫描(>、<、BETWEEN、IN) |
| index | 扫描整个索引树(比 ALL 好一点) |
| ALL | 全表扫描(最差,要优化) |
1. 生产标准:至少要到 range,最好是 ref 以上;出现 ALL 必须优化。
2. index vs ALL:index 是扫描整个索引(覆盖索引时可能比 ALL 快),ALL 是扫描整表数据。
3. 还有 ref_or_null(ref 后再查 NULL)、index_merge(多个索引合并)等少见值。
追问
- 追问:index_merge 好吗?应对:一般,说明优化器没用好联合索引;能建联合索引代替就建。
- 追问:type=index 一定比 ALL 快吗?应对:覆盖索引时 index 不用回表,可能比 ALL 快;非覆盖索引时 index 也要回表,不一定快。
题目 4:const 与 system 的区别,什么时候出现
考点
- const:主键/唯一索引等值匹配
- system:const 的特例,表只有一行
- 性能最高
参考答案
1. system:表只有一行,是 const 的特例,基本只出现在系统表或衍生表。
2. const:通过主键或唯一索引做等值查询,最多匹配一行,优化器把它当常量。
-- emp 主键 id = 1,至多一行,type=const
EXPLAIN SELECT * FROM emp WHERE id = 1;
-- uk_name 是唯一索引
EXPLAIN SELECT * FROM emp WHERE name = '张三';
3. 要求:WHERE 条件必须是主键/唯一索引的所有列都做等值匹配。
4. 性能极好,一次定位,几微秒。
追问
- 追问:唯一索引列中有 NULL 会怎样?应对:MySQL 唯一索引允许多个 NULL,等值查 NULL 走不到 const,走 ref。
- 追问:联合唯一索引只匹配一部分列呢?应对:不算 const,算 ref 或 range。
题目 5:ref 与 eq_ref 的区别
考点
- ref:单表普通索引等值
- eq_ref:JOIN 时主键/唯一索引被引用
- 性能差异
参考答案
1. ref:单表查询中普通索引(非唯一)做等值匹配,可能匹配多行。
2. eq_ref:JOIN 时,被驱动表用主键或唯一索引等值关联,对驱动表每一行,被驱动表只匹配一行。
3. eq_ref 是 JOIN 中最好的访问类型。
-- dept_id 普通索引,type=ref
EXPLAIN SELECT * FROM emp WHERE dept_id = 5;
-- JOIN:dept.id 是主键,emp.dept_id 关联 dept.id,dept 表 type=eq_ref
EXPLAIN SELECT e.name, d.name FROM emp e JOIN dept d ON e.dept_id = d.id;
追问
- 追问:eq_ref 和 const 区别?应对:const 是单表主键等值(最多一行),eq_ref 是 JOIN 中被驱动表主键等值(对驱动表每行一行)。
- 追问:JOIN 用普通索引关联会怎样?应对:被驱动表 type=ref,性能比 eq_ref 差。
题目 6:range 索引扫描的常见场景
考点
- >、<、BETWEEN、IN、LIKE 前缀
- 性能可接受
- 必须走索引
参考答案
1. 常见触发条件:
WHERE age > 20WHERE id BETWEEN 100 AND 200WHERE dept_id IN (1,2,3)WHERE name LIKE '张%'(前缀匹配)
2. 关键:列上必须有索引,否则 range 也走不到,直接 ALL。
3. IN 多了可能变 ALL:IN 列表太长,优化器觉得全表扫描更便宜,会放弃索引。
-- id 主键,type=range
EXPLAIN SELECT * FROM emp WHERE id BETWEEN 100 AND 200;
-- name 索引,前缀匹配,type=range
EXPLAIN SELECT * FROM emp WHERE name LIKE '张%';
追问
- 追问:LIKE '%张' 走 range 吗?应对:不走,因为前缀模糊,索引失效,变 ALL。
- 追问:IN (1,2,3,4,...,1000) 走索引吗?应对:数据量大时优化器可能放弃索引,要拆成多条或改写法。
题目 7:index 与 ALL 的区别,如何避免全表扫描
考点
- index 扫索引树
- ALL 扫数据行
- 避免 ALL 的方法
参考答案
1. index:扫描整棵索引树,不回表(覆盖索引)时比 ALL 快;要回表时与 ALL 差不多。
2. ALL:全表扫描,逐行读取数据页,最差。
3. 避免 ALL:
- WHERE 条件加合适索引;
- 避免索引列上用函数/运算;
- 避免前导模糊 LIKE '%xx';
- 避免隐式类型转换。
-- 全表扫描:name 没索引或写法让索引失效
EXPLAIN SELECT * FROM emp WHERE LEFT(name,1)='张'; -- 函数包列,索引失效
-- 走索引
EXPLAIN SELECT * FROM emp WHERE name LIKE '张%';
追问
- 追问:小表 ALL 比索引还快?应对:是,MySQL 优化器对小表(几百分页内)直接全表扫更便宜,走索引反而多一次回表。
- 追问:怎么判断要不要建索引?应对:看 WHERE/JOIN/ORDER BY 中高频出现的列。
题目 8:key_len 怎么算,有什么用
考点
- 验证联合索引用了几列
- 长度计算规则
- 字符集影响
参考答案
1. key_len:显示 MySQL 实际使用了索引的前多少字节,用来判断联合索引用到了第几列。
2. 计算规则:
- 变长字段(VARCHAR):长度 × 字节数 + 2(变长头);
- 定长字段(INT/DATE):固定字节数;
- 允许 NULL 多 1 字节;
- 字符集:utf8mb4 每字符 4 字节。
3. 举例:联合索引 idx(a VARCHAR(10), b INT),utf8mb4,a 允许 NULL:
- 只用 a:key_len = 10×4 + 2 + 1 = 43
- a + b:key_len = 43 + 4 = 47
-- 联合索引 idx_dept_job_sal(dept, job, salary)
-- 只查 dept='研发':key_len 显示 dept 部分的长度
EXPLAIN SELECT * FROM emp WHERE dept='研发';
-- 查 dept='研发' AND job='工程师':key_len 增大,说明用了前两列
EXPLAIN SELECT * FROM emp WHERE dept='研发' AND job='工程师';
追问
- 追问:key_len 越小越好吗?应对:不是,key_len 大说明索引用得多,但也要看是否真的需要。
- 追问:怎么知道索引是否完整用上?应对:对照 key_len 与索引定义列的理论长度。
题目 9:rows 与 filtered 列的含义,准确度如何
考点
- rows 预估扫描行数
- filtered 过滤百分比
- 与真实值差距
参考答案
1. rows:优化器预估这次查询要扫描的行数,基于索引统计信息。
2. filtered:预估扫描后经过 WHERE 过滤剩下的行百分比(0~100)。
3. 实际产出行数 ≈ rows × filtered / 100。
4. 准确度:统计信息过期时 rows 与真实值可能差几倍到几十倍;ANALYZE TABLE 刷新统计信息后会准一些。
5. JOIN 中:多行 rows 相乘就是嵌套循环的总比较次数,是 JOIN 优化的关键指标。
追问
- 追问:rows 大但实际很快为什么?应对:覆盖索引不回表、或者 LIMIT 提前终止,rows 是预估值不准。
- 追问:怎么刷新统计信息?应对:
ANALYZE TABLE tbl_name;。
题目 10:Extra 列 Using index 是什么意思
考点
- 覆盖索引
- 不回表
- 性能好
参考答案
1. Using index:MySQL 只需要扫描索引就能拿到所有需要的列,不需要回表,即"覆盖索引"。
2. 好处:减少随机 I/O,性能好。
3. 触发条件:SELECT 的列都在索引中(包括 WHERE、JOIN、ORDER BY 用到的列)。
4. 示例:
-- 索引 idx_dept_sal(dept, salary)
-- SELECT 只要 dept, salary:覆盖索引,Using index
EXPLAIN SELECT dept, salary FROM emp WHERE dept='研发';
-- SELECT name:不在索引中,要回表,不会出现 Using index
EXPLAIN SELECT dept, salary, name FROM emp WHERE dept='研发';
追问
- 追问:Using index condition 是什么?应对:8.0 索引下推 ICP,与 Using index 不同,是在索引层先过滤一部分条件再回表。
- 追问:怎么设计覆盖索引?应对:把高频 SELECT 列都放进联合索引,但别贪多,索引有维护成本。
题目 11:Using where 一定不好吗
考点
- Server 层过滤
- 与索引条件下推区别
- 正常出现
参考答案
1. Using where:存储引擎返回行后,Server 层再用 WHERE 条件过滤。
2. 不一定不好:如果 WHERE 条件能走索引,返回的行已经是过滤后的,Using where 只是额外兜底。
3. 不好的情况:type=ALL 且 Extra=Using where,说明全表扫描后还要 Server 层过滤,要加索引。
4. 与 ICP 区别:Using index condition 是 5.6+ 的索引下推,把过滤条件下推到存储引擎层,比 Using where 高效。
追问
- 追问:Using where 和 Using index condition 哪个好?应对:后者更好,过滤下推到引擎层,减少回表次数。
- 追问:什么情况下必然出现 Using where?应对:WHERE 条件无法完全走索引时。
题目 12:Using temporary 是什么,怎么优化
考点
- 内部临时表
- 常见于 GROUP BY/DISTINCT
- 优化方向
参考答案
1. Using temporary:MySQL 为了完成查询建了内部临时表,常见于 GROUP BY、DISTINCT、UNION、子查询。
2. 内存/磁盘:临时表先在内存(tmp_table_size),超了落盘,性能骤降。
3. 优化方向:
- 让 GROUP BY 列利用索引顺序,避免临时表;
- 缩小结果集(提前 WHERE);
- 适当调大 tmp_table_size。
-- GROUP BY 列与索引顺序不一致,容易出现 Using temporary
EXPLAIN SELECT dept, COUNT(*) FROM emp GROUP BY YEAR(hire_date);
-- hire_date 上加索引并改成直接按 hire_date 分组(应用层再聚合年)可优化
追问
- 追问:临时表在内存还是磁盘怎么看?应对:状态变量
Created_tmp_tables(内存)vsCreated_tmp_disk_tables(磁盘)。 - 追问:临时表一定慢吗?应对:小数据量无所谓;大数据量落盘就慢。
题目 13:Using filesort 是什么,怎么优化
考点
- 额外排序操作
- 不一定是磁盘文件
- 用索引消除
参考答案
1. Using filesort:MySQL 无法利用索引顺序完成 ORDER BY,需要额外排序(内存中 sort_buffer 或磁盘)。
2. 名字误导:filesort 不一定写磁盘,小结果集在内存排序。
3. 优化方向:
- 让 ORDER BY 列与联合索引列顺序一致;
- 排序方向与索引一致(8.0 支持降序索引);
- 减小排序结果集(提前 WHERE + LIMIT)。
-- 索引 idx_dept_sal(dept, salary)
-- 下面这条 ORDER BY 能利用索引,不 filesort
EXPLAIN SELECT * FROM emp WHERE dept='研发' ORDER BY salary;
-- ORDER BY hire_date 与索引不一致,filesort
EXPLAIN SELECT * FROM emp WHERE dept='研发' ORDER BY hire_date;
追问
- 追问:filesort 用什么内存?应对:sort_buffer_size,每个线程独享,别开太大。
- 追问:LIMIT 10 能缓解吗?应对:能,排序只取前 10 行,用优先队列,比全排序快。
题目 14:Using join buffer 是什么意思
考点
- BNL/BKA 算法
- 被驱动表没索引
- 加索引解决
参考答案
1. Using join buffer:JOIN 时被驱动表没索引,MySQL 把驱动表读到 join_buffer 里,再批量扫描被驱动表做匹配(Block Nested Loop)。
2. 性能差:说明 JOIN 缺少索引,要在被驱动表的关联列上建索引。
3. 优化:给被驱动表的 JOIN 列建索引,type 就会从 ALL 变成 ref/eq_ref。
-- emp.dept_id 没索引,JOIN dept 时 dept 端出现 Using join buffer
EXPLAIN SELECT e.name, d.name FROM emp e JOIN dept d ON e.dept_id = d.id;
-- 优化:CREATE INDEX idx_dept_id ON emp(dept_id);
追问
- 追问:join_buffer_size 调大有用吗?应对:缓解但不能根治,根本是加索引。
- 追问:BKA 是什么?应对:Batched Key Access,带索引的批量访问,比 BNL 好。
题目 15:possible_keys 为 NULL 但 key 有值是怎么回事
考点
- 覆盖索引扫描
- 优化器选了不在 possible_keys 中的索引
- 常见于 COUNT(*)/MIN/MAX
参考答案
1. 原因:优化器发现某个索引(通常是覆盖索引)能直接完成查询,虽然它不在 WHERE 的候选索引中。
2. 典型场景:SELECT MIN(idx_col) FROM t,优化器直接扫索引最左页,possible_keys=NULL 但 key=idx_col。
3. 不是错误:说明优化器做了"聪明"的选择。
-- possible_keys=NULL, key=idx_hire_date
EXPLAIN SELECT MIN(hire_date) FROM emp;
追问
- 追问:possible_keys=NULL 且 key=NULL 才是真没索引。应对:对,那才是要加索引的信号。
- 追问:怎么看优化器为什么这么选?应对:用
OPTIMIZER_TRACE。
题目 16:key 列显示的索引不是期望的,为什么
考点
- 优化器基于成本选择
- 统计信息不准
- 索引选错
参考答案
1. 原因:
- 统计信息过期,优化器估算行数偏差;
- 有多个可选索引,优化器选了它认为成本低的;
- 索引列区分度低,优化器觉得走索引不如全表扫。
2. 解决:
ANALYZE TABLE刷新统计信息;- 用
FORCE INDEX(idx_name)强制指定索引(应急); - 删掉无用索引,减少优化器困惑。
-- 强制走指定索引
EXPLAIN SELECT * FROM emp FORCE INDEX(idx_dept) WHERE dept='研发';
-- 忽略某个索引
EXPLAIN SELECT * FROM emp IGNORE INDEX(idx_old) WHERE ...;
追问
- 追问:FORCE INDEX 有什么风险?应对:数据分布变化后强制索引可能反而变慢,要定期评估。
- 追问:怎么删多余索引?应对:
sys.schema_unused_indexes视图查长期未用的索引。
题目 17:id 列相同与不同代表什么
考点
- id 越大越先执行
- id 相同从上到下执行
- 子查询/UNION 的执行顺序
参考答案
1. id 不同:id 越大越先执行(子查询先于外层)。
2. id 相同:从上到下顺序执行(JOIN 表的顺序)。
3. id 为 NULL:UNION 的临时表行,最后执行。
-- 子查询:id=2 的子查询先执行,id=1 的外层后执行
EXPLAIN SELECT * FROM emp WHERE dept_id IN (SELECT id FROM dept WHERE name='研发');
追问
- 追问:JOIN 表顺序是优化器定的吗?应对:是,优化器基于成本选驱动表;STRAIGHT_JOIN 可强制顺序。
- 追问:子查询优化器会改写成 JOIN 吗?应对:8.0 对半连接做了大量优化,IN 子查询常被改写成 JOIN。
题目 18:select_type 有哪些常见值
考点
- SIMPLE/PRIMARY/SUBQUERY/DERIVED/UNION
- 各值含义
- 性能提示
参考答案
| select_type | 含义 |
|-------------|------|
| SIMPLE | 简单查询,无子查询/UNION |
| PRIMARY | 外层查询 |
| SUBQUERY | 子查询(非 FROM 中) |
| DERIVED | FROM 中的派生表 |
| UNION | UNION 中第二个及之后的查询 |
| UNION RESULT | UNION 结果临时表 |
| DEPENDENT SUBQUERY | 相关子查询(外层引用) |
1. DEPENDENT SUBQUERY 要警惕:相关子查询对外层每行都执行一次,性能差。
2. DERIVED 多了:派生表物化成本高,8.0 有条件下推优化。
追问
- 追问:DEPENDENT SUBQUERY 怎么优化?应对:改写成 JOIN,让优化器一次扫描。
- 追问:DERIVED 8.0 有什么优化?应对:8.0 支持 derived_condition_pushdown,把外层条件下推到派生表。
题目 19:EXPLAIN ANALYZE 与传统 EXPLAIN 区别
考点
- 真实执行 vs 预估
- 输出实际行数与耗时
- 8.0.18+ 支持
参考答案
1. 传统 EXPLAIN:只输出优化器估计的计划,不真正执行。
2. EXPLAIN ANALYZE:8.0.18+,真正执行 SQL,输出每一步的实际行数、实际耗时、循环次数。
3. 优势:能对比"预估 rows"与"实际 rows",发现统计信息不准的问题。
4. 注意:会真正执行 SQL,对写操作或大查询要小心。
-- 输出每一步的 actual rows、time
EXPLAIN ANALYZE
SELECT e.name, d.name FROM emp e JOIN dept d ON e.dept_id = d.id;
追问
- 追问:EXPLAIN ANALYZE 能用于 UPDATE 吗?应对:能,但会真正执行 UPDATE,生产环境慎用。
- 追问:输出格式是什么?应对:树状文本,类似
-> Nested loop inner join (cost=...)(actual time=... rows=... loops=...)。
题目 20:常见坑与注意事项
考点
- EXPLAIN 使用高频坑
- 预估值与真实值差异
- 版本差异
参考答案
1. 坑 1:把 rows 当真值,其实是估算,差异可能很大。
2. 坑 2:type=ALL 不一定立刻加索引,小表全表扫反而快。
3. 坑 3:filesort 名字吓人,小结果集在内存排,不慢。
4. 坑 4:Using temporary 不代表磁盘临时表,内存里也可能。
5. 坑 5:FORCE INDEX 应急用,别长期依赖。
6. 坑 6:EXPLAIN 在不同数据量下结论不同,要在生产数据量级下测。
7. 坑 7:统计信息过期导致走错索引,定期 ANALYZE TABLE。
8. 坑 8:EXPLAIN ANALYZE 会真正执行,写 SQL 生产慎用。
追问
- 追问:怎么看索引统计信息是否过期?应对:
SHOW TABLE STATUS的 Update_time 与数据变化频率对比。 - 追问:EXPLAIN 输出可以导出吗?应对:
EXPLAIN FORMAT=JSON输出 JSON,可脚本解析。
速查表:EXPLAIN 列速记
| type 值 | 性能 | 出现条件 |
|---------|------|----------|
| system | 极好 | 表只有一行 |
| const | 极好 | 主键/唯一索引等值 |
| eq_ref | 很好 | JOIN 被驱动表主键/唯一 |
| ref | 好 | 普通索引等值 |
| range | 可接受 | 索引范围扫描 |
| index | 较差 | 全索引扫描 |
| ALL | 差 | 全表扫描 |
| Extra 值 | 含义 | 是否要优化 |
|----------|------|-----------|
| Using index | 覆盖索引,不回表 | 否,好事 |
| Using where | Server 层过滤 | 视情况 |
| Using index condition | 索引下推 ICP | 否,好事 |
| Using temporary | 用了临时表 | 视数据量 |
| Using filesort | 额外排序 | 视情况 |
| Using join buffer | JOIN 无索引 | 是,加索引 |
| Start temporary / End temporary | 半连接去重临时表 | 注意 |
复习清单
| 题目 | 核心结论 | 易错点 |
|------|----------|--------|
| 1 | EXPLAIN 看执行计划,ANALYZE 真实执行 | rows 当真值 |
| 2 | 重点看 type/key/rows/Extra | 列太多不知道看啥 |
| 3 | type 从 const 到 ALL 性能递减 | ALL 就立刻加索引 |
| 4 | const 是主键/唯一等值 | 唯一索引多 NULL 不走 const |
| 5 | eq_ref 是 JOIN 最优访问类型 | ref 与 eq_ref 混淆 |
| 6 | range 是索引范围扫描 | LIKE '%xx' 不走 range |
| 7 | index 扫索引,ALL 扫数据 | 小表 ALL 可能更快 |
| 8 | key_len 判断联合索引用了几列 | 字符集字节数算错 |
| 9 | rows × filtered 是产出行数 | 统计信息过期不刷新 |
| 10 | Using index = 覆盖索引 | 与 Using index condition 混淆 |
| 11 | Using where 不一定差 | 看到就想优化 |
| 12 | Using temporary 常见于 GROUP BY | 临时表落盘不监控 |
| 13 | Using filesort 不一定磁盘 | 以为 filesort 必慢 |
| 14 | Using join buffer 说明缺 JOIN 索引 | 只调 join_buffer_size |
| 15 | possible_keys=NULL 但 key 有值是覆盖扫描 | 以为是错误 |
| 16 | 优化器选错索引用 FORCE INDEX 应急 | 长期依赖 FORCE |
| 17 | id 越大越先执行 | UNION 临时表 id 为 NULL |
| 18 | DEPENDENT SUBQUERY 要改 JOIN | 相关子查询性能差 |
| 19 | EXPLAIN ANALYZE 真实执行 | 生产环境乱用 |
| 20 | 统计信息、小表、FORCE 三大坑 | 不在生产数据量下测 |
练习题
1. 题目:EXPLAIN 输出 type=ALL、Extra=Using where,说明什么?怎么优化?
- 参考答案:全表扫描 + Server 层过滤;要在 WHERE 列上建索引,让 type 升到 range/ref。
2. 题目:联合索引 idx(a,b,c),WHERE 只用 a=1 AND c=3,key_len 体现用了几列?
- 参考答案:最左前缀原则下只用到 a 列,key_len 等于 a 列长度;b 没用到,c 用不上。
3. 题目:Extra=Using filesort 一定意味着磁盘排序慢吗?
- 参考答案:不一定,小结果集在 sort_buffer 内存排序;大数据量才落盘。
4. 题目:怎么判断临时表是在内存还是磁盘?
- 参考答案:对比状态变量
Created_tmp_tables与Created_tmp_disk_tables,后者大说明落盘多。
5. 题目:EXPLAIN ANALYZE 与 EXPLAIN 最大的区别是什么?
- 参考答案:ANALYZE 真正执行 SQL,输出实际行数与耗时;普通 EXPLAIN 只输出优化器估计。
下一篇预告:第 14 章 SQL优化面试精讲
文章回复
0 条公开回复