📌 今日关键词: 执行计划、Explain、SQL优化、索引失效、性能诊断
大家好呀!我是数据库小学妹👋
我们之前学过索引,也分享过SQL优化十大技巧。但是在使用索引的过程我常遇到这些问题:
我怎么知道索引有没有生效?为什么加了索引还是慢?这条查询到底慢在哪一步?
每次遇到慢查询,我都像无头苍蝇一样乱试:加索引、改SQL、拆表……运气好能蒙对,运气不好折腾半天。
后来同事告诉我:光建索引不行,你得学会看SQL的“体检报告”,我们今天要学的就是数据库的“诊断报告”——执行计划。把SQL丢进去,它就会告诉你数据库打算怎么查、用没用索引、扫了多少行。精准定位查询瓶颈,不再瞎优化!
一、什么是执行计划?
执行计划是数据库为了执行你的SQL,自己制定的“作战方案”。它会告诉你:
- 用什么顺序访问表
- 用哪个索引(或者不用)
- 预计要扫描多少行
- 需不需要临时表、文件排序等
如何查看? 很简单,在SELECT前面加上 EXPLAIN 就行了:
EXPLAIN SELECT * FROM users WHERE age > 18;
执行后,MySQL会返回一张表格,每一行代表一个“步骤”。看懂这张表,你就知道SQL慢在哪了。
💡 EXPLAIN 不会真正执行SQL,只是分析查询计划,所以放心用,不会改数据。
二、执行计划结果中6个关键字段
虽然 EXPLAIN 返回的字段很多,但我们新手只需要关注6个关键字段,就能解决90%的问题:
⭐ id:执行顺序的“优先级”
- 含义: 查询中操作表的顺序标识。
- 怎么看: 数字越大,越先执行;数字相同,从上往下执行。
- 避坑: 如果你在写复杂的子查询,id 能帮你判断哪个子查询是“老大”。
⭐select_type:查询的“身份”
- SIMPLE:简单的查询(没有子查询或UNION)。
- PRIMARY:最外层的查询。
- SUBQUERY:子查询中的第一个 SELECT。
- 理解它: 知道自己写的是简单查询还是复杂嵌套,有助于分析性能。
⭐table:正在操作的“对象”
- 含义: 这一行操作的是哪张表。
- 技巧: 在多表关联时,这里会显示表名,配合 type 字段看关联效率。
⭐type:访问类型的“含金量”(最重要!)
这是判断SQL性能好坏的金标准。从最好到最差:
- system/const:完美! 通过主键或唯一索引直接命中,速度极快。
- eq_ref:优秀! 通常出现在多表关联的主键匹配中。
- ref:良好! 使用了非唯一索引进行等值查询。
- range:凑合! 使用了索引进行范围查询(如 BETWEEN, IN)。
- index:警告! 索引全扫描。虽然走了索引,但还是把索引树遍历了一遍。
- ALL:危险! 全表扫描!这是性能杀手,必须优化。
小学妹口诀: 能 const 就不 range,千万别出现 ALL!
⭐ possible_keys & key:索引的“博弈”
- possible_keys:理论上可能用到的索引。
- key:实际上真正用到的索引。
- 避坑: 如果 possible_keys 有值,但 key 是 NULL,说明索引失效了! 这就是你需要排查的地方。
⭐rows:预计扫描的“行数”
- 含义: 数据库预估要扫描多少行才能找到数据。
- 目标: 这个数字越小越好。如果是百万级的表,这里显示几万,那肯定有问题。
三、实战:用Explain诊断“慢SQL”
场景: 我们有一张orders表(订单表),数据量很大。我想查用户ID为100的所有订单。
- 建表示例
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
amount DECIMAL(10,2),
create_time DATETIME
);
-- 假设我们忘了给 user_id 加索引
- 慢SQL诊断
EXPLAIN SELECT * FROM orders WHERE user_id = 100;
报告分析:
🚨 诊断结果:
- type: ALL:全表扫描!数据库正在一行一行地翻找。
- key: NULL:没有用到任何索引。
- rows: 1000000:扫描了100万行!这太慢了。
- 优化方案 给 user_id 加个索引,再跑一次 EXPLAIN:
CREATE INDEX idx_user_id ON orders(user_id);
EXPLAIN SELECT * FROM orders WHERE user_id = 100;
优化后报告:
✅ 优化结果:
- type: ref:使用了索引!
- key: idx_user_id:命中了我们刚建的索引。
- rows: 5:只扫描了5行数据!🚀 这就是 EXPLAIN 的魔力!
四、新手避坑指南
💢对字段使用了函数:
- 错误: WHERE YEAR(create_time) = 2023
- 原因: 数据库无法直接用索引,必须把所有时间拿出来算一遍年份。
- 修正: WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'
💢最左前缀原则破坏:
- 场景: 你建了联合索引 (a, b, c),但查询条件只写了 WHERE b = 1 AND c = 2。
- 结果: 索引失效(除非是覆盖索引)。
- 修正: 查询条件必须包含最左边的字段 a。
💢隐式类型转换:
- 错误: user_id 是 INT 类型,但你写了 WHERE user_id = '100'(加了引号)。
- 原因: 数据库会把数字转成字符串比较,导致索引失效。
- 修正: 保持类型一致,WHERE user_id = 100。
五、今日学习心得
今天的内容总结成三句话:
- EXPLAIN 是慢查询的“CT机”,用之前不要乱加索引
- 重点关注 type(ALL要优化)、key(是否为NULL)、Extra(有无临时表/文件排序)
- 优化后一定重新 EXPLAIN,验证是否生效
👋 我是数据库小学妹一个用设计师思维学数据库的转行人。我们一起,把复杂的技术变得简单有趣!💕
本文示例基于 MySQL 8.0。EXPLAIN 在不同版本中细节有差异,但核心思想通用。