1. 为什么EXPLAIN是SQL优化的第一课
上周排查一个订单查询超时问题,发现团队里三年经验的开发居然不会看EXPLAIN的执行计划。这就像修车师傅不看故障码直接拆发动机——不是不能修,但大概率会白忙活。今天我们就用真实的生产案例,拆解这个MySQL优化中最基础也最重要的诊断工具。
EXPLAIN输出的12个字段就像SQL的体检报告单。type列告诉你查询是全身检查(ALL)还是定点抽查(const),rows列显示预估要翻多少病历本,Extra里的"Using filesort"相当于查出高血压需要特别注意。去年我们一个核心接口从2秒优化到200毫秒,第一步就是通过EXPLAIN发现它在错误地全表扫描用户地址表。
2. EXPLAIN结果深度解析
2.1 执行计划中的关键指标
以这个慢查询为例:
EXPLAIN SELECT * FROM orders WHERE user_id IN (SELECT id FROM users WHERE vip_level > 3) ORDER BY create_time DESC;输出结果中这几个字段最值得关注:
| 字段 | 示例值 | 健康指标 | 故障表现 |
|---|---|---|---|
| type | ALL | 至少达到range级别 | 全表扫描如同翻遍仓库找货 |
| key | NULL | 必须显示使用的索引名 | 没走索引就像没带导航出门 |
| rows | 28432 | 超过1000就要警惕 | 检查室堆满待查病历 |
| Extra | Using where | 出现filesort临时表就危险 | 体检发现多项异常指标 |
2.2 索引失效的七种经典场景
- 隐式类型转换:
WHERE user_id = '10086'(user_id是int) - 索引列运算:
WHERE YEAR(create_time) = 2023 - 前导模糊查询:
WHERE product_name LIKE '%手机' - OR条件未全覆盖:
WHERE a=1 OR b=2(只有a有索引) - 最左前缀缺失:联合索引(a,b,c)但条件只有
WHERE b=1 - 范围查询阻断:
WHERE a>1 AND b=2(a范围查询后b不走索引) - 函数转换:
WHERE SUBSTR(order_no,1,3) = 'ABC'
上周遇到的真实案例:WHERE status=1 AND create_time>='2023-01-01'查询缓慢,明明有(status,create_time)联合索引,EXPLAIN却显示全表扫描。原来是因为status字段在表设计时是tinyint,但应用代码传了字符串'1'。
3. 系统化优化方法论
3.1 优化五步法实战
抓取慢查询:
-- 开启慢日志(生产环境慎用) SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1;EXPLAIN诊断:
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE...;索引手术方案:
- 添加缺失索引
- 重建低效索引
- 调整联合索引顺序
SQL重构技巧:
- 拆分复杂查询
- 改用JOIN替代子查询
- 避免SELECT *
验证效果:
FLUSH STATUS; SELECT ...; SHOW STATUS LIKE 'Handler%';
3.2 索引优化器原理
MySQL选择索引的过程就像网约车派单系统:
- 统计信息收集(乘客分布热力图)
- 成本计算(距离×路况)
- 执行计划生成(最优路线)
这个月我们通过ANALYZE TABLE更新统计信息,解决了一个持续两周的索引失效问题。优化器误判扫描行数,以为全表扫描比走索引更快,更新统计信息后立即恢复正常。
4. 高级优化场景处理
4.1 分页查询优化
错误的写法:
SELECT * FROM orders ORDER BY create_time DESC LIMIT 10000, 20;优化方案:
SELECT * FROM orders INNER JOIN ( SELECT id FROM orders ORDER BY create_time DESC LIMIT 10000, 20 ) AS tmp USING(id);原理:先通过覆盖索引获取主键,再回表查询,减少随机IO。实测10万数据量下,查询时间从1.8秒降到0.02秒。
4.2 大数据量统计优化
某电商平台要统计年度销售额:
-- 原始方案 SELECT COUNT(*) FROM orders WHERE create_time BETWEEN...; -- 优化方案 SELECT TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_NAME='orders';当误差允许5%以内时,直接查询统计信息比COUNT(*)快三个数量级。我们在大促实时看板中采用这种方案,数据库负载下降70%。
5. 性能优化陷阱指南
5.1 索引越多越好?
最近接手的一个系统建了47个索引,插入性能只有正常值的1/10。每个索引就像书架的目录页——增加查询效率但降低写入速度。我们通过这些原则精简到19个:
- 删除三个月未使用的索引
- 合并可以共用的联合索引
- 区分冷热数据不同策略
5.2 缓存能解决一切?
曾有个团队给所有慢查询加Redis缓存,结果:
- 缓存击穿导致数据库崩溃
- 数据不一致引发客诉
- 内存占用超80%影响其他服务
现在我们采用分层优化策略:
- 先优化SQL到200ms内
- 再考虑本地缓存
- 最后才是分布式缓存
某商品详情页经过这三步优化,TP99从3秒降到80毫秒,缓存命中率反而从60%提升到92%。