MySQL索引优化实战:从慢查询到执行计划,新手也能照着做
当MySQL查询变慢时,优化的核心思路是先用慢查询日志找到具体是哪条SQL慢,再用EXPLAIN看它有没有走索引,最后针对性地加索引或改SQL。
下面按这个顺序展开,命令和配置都可以直接复制到测试环境执行。
先确认环境并开启慢查询日志
这套流程适用于MySQL 5.7和8.0,操作前建议在测试库或从库先验证,避免直接在生产主库上改配置。
登录MySQL后,先查看当前状态:
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';
如果slow_query_log是OFF,可以临时开启(重启后失效):
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;
要永久生效,修改配置文件/etc/my.cnf(或/etc/mysql/my.cnf),在[mysqld]段落下添加:
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
改完重启MySQL:systemctl restart mysqld。注意:log_queries_not_using_indexes在并发高时会产生大量日志,
生产环境建议只在排查阶段临时打开。
从慢查询日志里找出要优化的SQL
日志文件默认在/var/log/mysql/slow.log,可以用mysqldumpslow快速汇总:
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
-s t表示按总耗时排序,-t 10表示只显示前10条。
找到耗时最高、出现次数最多的那条SQL,记下它的完整语句。
如果日志里SQL被截断,可以在MySQL里执行:
SHOW VARIABLES LIKE 'slow_query_log_file';
确认路径后,用tail -f实时观察新产生的慢查询。
用EXPLAIN判断索引是否生效
拿到慢SQL后,在前面加EXPLAIN执行:
EXPLAIN SELECT * FROM orders WHERE user_id = 100 AND status = 1;
重点看这几列:
- type:
ALL是全表扫描,最差;ref、range、eq_ref、const依次更好。 - key:实际使用的索引名。如果是
NULL,说明没走索引。 - rows:预估扫描行数,越小越好。
- Extra:出现
Using filesort或Using temporary通常意味着需要额外排序或临时表,是优化重点。
假设orders表有100万行,user_id和status各有单列索引,但type还是ALL,说明优化器没选索引,或者条件写法导致索引失效。
常见索引失效场景与修正写法
以下情况会让索引用不上,逐条对照检查:
- 在索引列上做运算或函数:
WHERE YEAR(created_at) = 2024改成WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'。 - 隐式类型转换:
user_id是varchar,却写WHERE user_id = 100,应写成WHERE user_id = '100'。 - 前导模糊匹配:
LIKE '%abc'无法走索引,LIKE 'abc%'可以。 - OR连接的条件中有一个列没索引,整个查询可能退化为全表扫描。
对于多条件查询,可以考虑联合索引。
例如:
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
联合索引要遵守最左前缀原则:WHERE user_id = 100能用,WHERE status = 1单独用不上。加索引前先确认字段区分度,
区分度太低的列(如性别)单独建索引意义不大。
优化后的验证与回滚准备
执行ALTER TABLE加索引后,再次运行EXPLAIN,确认type变成ref或range,key显示新建的索引名,rows明显下降。
然后用实际查询测速:
SELECT SQL_NO_CACHE COUNT(*) FROM orders WHERE user_id = 100 AND status = 1;
对比优化前后的耗时。
如果效果不理想,可以删除索引回滚:
ALTER TABLE orders DROP INDEX idx_user_status;
避坑提醒:大表加索引会锁表或占用大量IO,MySQL 5.7以上支持ALGORITHM=INPLACE,但仍建议在业务低峰期操作,并提前确认磁盘空间。
不要一次加太多索引,每个索引都会增加写入开销。
几个新手常问的点
索引是不是越多越好? 不是。
索引会占用磁盘,并让INSERT、UPDATE、DELETE变慢。
只给高频查询条件、排序和连接字段建索引。
为什么加了索引还是慢? 可能是数据量太大、回表次数多,或者查询返回了大部分数据,这时优化器会认为全表扫描更快。
可以尝试覆盖索引,把SELECT的列也放进索引里。
EXPLAIN的rows准吗? 它是估算值,基于统计信息。
如果偏差很大,可以执行ANALYZE TABLE 表名;更新统计信息后再看。
生产环境能直接改吗? 建议先在从库或测试库验证,确认SQL和索引效果后再上生产,并保留回滚用的DROP INDEX语句。