29、MySQL索引优化:索引类型选择、复合索引、索引统计信息、慢查询日志分析
索引优化这事儿,我做了十几年数据库,踩过的坑比走过的路还多。说实话,很多系统慢就慢在索引上——要么没建,要么建错了。今天咱们就把索引优化这摊子事彻底捋清楚。
一、索引类型选择:别一上来就B+树
MySQL里索引类型其实不少,但大多数人只知道B+树。我个人习惯先问自己三个问题:
- 数据重复度高不高? 高的话考虑哈希索引
- 是不是要范围查询? 是的话B+树是首选
- 字段是不是文本? 长文本记得用前缀索引
我记得有一次接手一个电商项目,订单表的status字段只有3种值,结果建了B+树索引。你想想看,这种低选择性的字段,B+树扫描起来跟全表扫描没啥区别。后来改成哈希索引,查询速度直接翻倍。
核心原则:索引不是越多越好,而是越准越好。高选择性、低重复度的字段才值得建索引。
二、复合索引:最左前缀法则
复合索引是优化利器,但很多人用错了。说白了,复合索引遵循最左前缀法则——查询条件必须从索引最左边开始匹配。
举个例子,假设我们有索引 (a, b, c):
-- 能用上索引
SELECT * FROM t WHERE a = 1;
SELECT * FROM t WHERE a = 1 AND b = 2;
SELECT * FROM t WHERE a = 1 AND b = 2 AND c = 3;
-- 用不上索引(跳过了a)
SELECT * FROM t WHERE b = 2;
SELECT * FROM t WHERE c = 3;
我在项目中遇到过最典型的坑:有人建了 (city, age, salary) 的复合索引,但查询条件经常是 WHERE age > 30。结果索引完全没用上,全表扫描跑得飞起。
我的建议:建复合索引时,把最常查询的字段放左边,把区分度高的字段也放左边。比如 (user_id, create_time) 就比 (create_time, user_id) 好用得多。
三、索引统计信息:优化器的眼睛
MySQL优化器选择索引,靠的就是统计信息。如果统计信息不准,优化器就会选错索引。我见过最离谱的一次,统计信息显示某个字段只有100个不同值,实际有100万——结果优化器选了全表扫描。
怎么更新统计信息?
-- 手动更新
ANALYZE TABLE orders;
-- 查看统计信息
SHOW INDEX FROM orders;
SHOW TABLE STATUS LIKE 'orders';
嗯,这里要注意:统计信息不是实时更新的。MySQL默认在表数据变化超过10%时才会自动更新。如果你的表频繁更新,建议定期手动执行 ANALYZE TABLE。
避坑指南:我曾经因为没更新统计信息,导致一个报表查询跑了30秒。执行完 ANALYZE TABLE 后,查询直接降到0.3秒。所以,定期维护统计信息很重要。
四、慢查询日志分析:找到罪魁祸首
慢查询日志是优化索引的第一手资料。开启方式很简单:
-- 查看是否开启
SHOW VARIABLES LIKE 'slow_query_log';
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2; -- 超过2秒的查询记录
SET GLOBAL log_queries_not_using_indexes = 'ON';
日志文件长这样:
# Time: 2024-01-15T10:30:00
# User@Host: root[root] @ localhost []
# Query_time: 5.234567 Lock_time: 0.000123
# Rows_sent: 1 Rows_examined: 1000000
SELECT * FROM orders WHERE status = 'pending' ORDER BY create_time DESC;
看到没?Rows_examined: 1000000 但只返回1行,这明显是索引没用好。我一般用 pt-query-digest 工具来分析慢查询日志,它能自动归类、排序,省不少事。
五、实战案例:一个慢查询的优化全过程
去年我优化过一个订单查询接口,原始SQL长这样:
SELECT * FROM orders
WHERE user_id = 12345
AND status = 'completed'
AND create_time > '2024-01-01'
ORDER BY create_time DESC;
慢查询日志显示执行时间8秒,扫描了50万行。我一看,表上只有一个 user_id 的单列索引。
优化方案:
- 建复合索引
(user_id, status, create_time) - 因为
status区分度不高,考虑把create_time放前面 - 最终索引:
(user_id, create_time, status)
优化后,查询时间从8秒降到0.05秒。你想想看,就改了个索引,性能差了160倍。
优化前后对比:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 执行时间 | 8.2秒 | 0.05秒 |
| 扫描行数 | 500,000 | 1,200 |
| 索引类型 | 单列索引 | 复合索引 |
六、索引优化知识体系
下面这张图是我自己总结的索引优化决策流程,每次做优化我都会对照着走一遍:
这张图我用了很多年,每次做索引优化都照着走一遍,基本不会漏掉关键步骤。
七、总结
索引优化说白了就三件事:选对类型、建好复合索引、维护统计信息。再加上慢查询日志这个工具,你就能精准定位问题。我见过太多人一上来就加索引,结果越加越慢。记住:少而精才是索引优化的真谛。
最后一个小技巧:每次建完索引,记得用 EXPLAIN 验证一下。我习惯把 EXPLAIN 的结果保存下来,方便以后对比。这样优化效果一目了然。