一、开场白:一条慢SQL引发的“血案”
上周半夜被运维同事的电话惊醒:“线上数据库CPU飙到100%,快看看!” 排查发现,一条看似普通的查询:
SELECT * FROM order WHERE user_id = 123 AND create_time > '2023-01-01'
竟然执行了30秒!明明给user_id建了索引,为什么还是全表扫描?
今天就来聊聊MySQL索引那些事儿:为什么精心设计的索引会失效?如何用Explain诊断慢SQL?纯干货,建议收藏!
二、索引失效?先搞懂MySQL怎么“查字典”
索引就像字典的目录,正常情况下MySQL会:
- 先查目录(索引)找到对应页码(数据位置)
- 再翻到对应页码找到内容(数据)
但如果索引失效,MySQL就会变成“逐字逐句翻字典”,也就是全表扫描,速度慢100倍!
三、索引失效的10大经典场景(附避坑指南)
1. 隐式转换:最容易踩的坑!
-- user_id是varchar类型
SELECT * FROM user WHERE user_id = 123;  -- 失效!
SELECT * FROM user WHERE user_id = '123'; -- 有效
原理:数字和字符串比较会触发类型转换,相当于在索引字段上做运算
2. 使用函数/表达式
SELECT * FROM order WHERE SUBSTR(create_time, 1, 7) = '2023-01'; -- 失效
SELECT * FROM order WHERE create_time >= '2023-01-01' AND create_time < '2023-02-01'; -- 有效
3. LIKE以%开头
SELECT * FROM user WHERE username LIKE '%小明'; -- 失效
SELECT * FROM user WHERE username LIKE '小明%'; -- 有效
4. OR连接非索引字段
SELECT * FROM product WHERE id = 123 OR price = 99; -- price无索引则整体失效
5. 违背最左匹配原则
-- 联合索引(a,b,c)
SELECT * FROM table WHERE b=2 AND c=3; -- 失效
SELECT * FROM table WHERE a=1 AND c=3; -- 只用到a索引
SELECT * FROM table WHERE a=1 AND b=2 AND c=3; -- 全索引生效
6. 使用NOT IN / NOT EXISTS
SELECT * FROM user WHERE id NOT IN (1,2,3); -- 可能失效
建议用LEFT JOIN替代:
SELECT a.* FROM user a LEFT JOIN blacklist b ON a.id = b.id WHERE b.id IS NULL;
7. 索引字段使用IS NULL / IS NOT NULL
SELECT * FROM user WHERE phone IS NULL; -- 可能失效
优化:业务允许的话用默认值代替NULL
8. 全表扫描更快的情况
当查询结果超过30%的数据时,MySQL会认为全表扫描比索引查询更快
9. 使用!=或<>运算符
SELECT * FROM product WHERE price != 100; -- 可能失效
10. 索引字段被更新频繁
写入频繁的字段建索引会导致更新性能下降
四、Explain执行计划:SQL诊断神器
学会看Explain,就能像医生一样诊断SQL问题!
1. 怎么用?
EXPLAIN SELECT * FROM order WHERE user_id = 123;
2. 关键字段解读
| 字段 | 含义 | 重要值 | 
|---|---|---|
| type | 访问类型 | ref(索引查找)、range(范围查找)、ALL(全表扫描) | 
| key | 使用的索引 | NULL表示未使用索引 | 
| rows | 预估行数 | 数值越小越好 | 
| Extra | 额外信息 | Using index(覆盖索引)、Using filesort(文件排序)、Using temporary(临时表) | 
3. 实战分析:从ALL到ref的优化
优化前:
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | order | ALL  | NULL          | NULL | NULL    | NULL | 100w | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
优化后:
+----+-------------+-------+------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key     | key_len | ref   | rows | Extra       |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-------------+
|  1 | SIMPLE      | order | ref  | idx_user_id   | idx_user_id | 4       | const | 100  | Using where |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-------------+
五、查询优化最佳实践
1. 索引设计三原则
- 高频查询字段优先建索引
- 避免过度索引(影响写入性能)
- 联合索引遵循最左匹配原则
2. SQL编写规范
- 避免SELECT *,只查需要的字段
- 控制JOIN表数量(建议不超过3张)
- 大表分页用主键自增ID:WHERE id > 1000 LIMIT 100
3. 定期维护
- 分析慢查询日志(slow_query_log)
- 定期优化表结构和索引
- 使用pt-index-usage工具分析索引使用情况
六、总结
索引失效就像“隐形的性能杀手”,稍不注意就会让系统变慢。记住:
- 写SQL时多思考:这个查询会走索引吗?
- 养成用Explain分析SQL的习惯
- 没有万能的优化方案,要结合业务场景
最后送大家一句口诀: “全值匹配我最爱,最左前缀要遵守;带头大哥不能死,中间兄弟不能断;索引列上不计算,范围之后全失效”
关注服务端技术精选,获取更多后端实战干货!
你遇到过哪些奇葩的SQL性能问题?欢迎在评论区分享你的故事!









