MySQL索引优化实战——从慢查询到索引调优

线上有个接口突然慢了,页面转圈等好几秒。一查日志,某条SQL查询耗时3.2秒。表里才200万行数据,按理说不该这么慢。EXPLAIN一拉,type列赫然写着ALL------全表扫描。索引建了但没走,或者压根就没建对。

这种事太常见了。下面聊聊怎么从慢查询定位到索引调优,把查询从秒级拉回毫秒级。

慢查询怎么抓

MySQL自带的慢查询日志是最直接的排查工具。先确认开了没有:

sql 复制代码
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';

没开的话,临时打开:

ini 复制代码
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

long_query_time设成1秒,超过1秒的查询都会被记录。线上建议设到0.1甚至更低,这样能抓到更多潜在问题。日志文件默认在/var/lib/mysql/hostname-slow.log

但光看日志文件不太直观,行又多又长。用mysqldumpslow工具汇总一下:

bash 复制代码
mysqldumpslow -s t -t 10 /var/lib/mysql/mysql-slow.log

-s t按总耗时排序,-t 10只看前10条。一拉就清楚哪几条SQL最该优化。

如果你的MySQL版本是8.4 LTS或最新的9.7 LTS(2026年4月发布的长期支持版),还可以用performance_schemaevents_statements_summary_by_digest视图,按SQL指纹聚合统计,比慢查询日志更精细。

EXPLAIN怎么看

抓到慢SQL后,EXPLAIN是第一诊断工具。在SQL前面加上EXPLAIN跑一下:

sql 复制代码
EXPLAIN SELECT * FROM orders WHERE user_id = 12345 ORDER BY created_at DESC;

几个关键列得会读:

type ------访问类型,从好到差大概是const > eq_ref > ref > range > index > ALL。看到ALL就是全表扫描,得加索引。index是全索引扫描,也不算好。理想状态是refrange

key ------实际用的索引。如果是NULL,说明没走索引。

rows------预估扫描行数。这个数字越接近实际返回行数越好。扫描10万行返回10行,那索引就有问题。

Extra ------额外信息。Using index是好事,说明覆盖索引了。Using filesort说明需要额外排序,Using temporary说明用了临时表,这两个出现就得注意。

key_len ------索引使用的字节数。复合索引里这个值能告诉你走了几个字段。如果建了(a, b, c)三个字段的联合索引,但key_len只覆盖了a,说明bc没用上。

索引类型选哪个

MySQL的InnoDB引擎默认用B+树索引。B+树的特点是数据只存在叶子节点,叶子节点之间用链表连起来,范围查询效率高。这是最常用的索引类型,日常说的"建索引"基本就是B+树。

哈希索引主要在Memory引擎上用,等值查询快但不能做范围查询。InnoDB内部有个自适应哈希索引的功能,会对热点数据自动建哈希索引,一般不用手动管。

全文索引用于文本搜索,FULLTEXT类型,适合做类似搜索引擎的功能。但现在大多数场景用Elasticsearch做全文检索了,MySQL的全文索引用得不多。

索引设计的几条原则

最左前缀原则 。复合索引(a, b, c)相当于建了(a)(a,b)(a,b,c)三个索引。查询条件必须从最左列开始才能走索引。WHERE a=1 AND b=2可以走,WHERE b=2 AND c=3走不了。

这条原则决定了复合索引的字段顺序很关键。一般把区分度高的放前面,比如user_idstatus区分度高得多。

覆盖索引。如果一个索引包含了查询需要的所有字段,就不用回表查数据了,直接从索引里拿。比如:

ini 复制代码
SELECT user_id, order_no, amount FROM orders WHERE user_id = 12345;

如果建的是(user_id, order_no, amount),这三个字段全在索引里,EXPLAINExtra列会显示Using index,省了回表的IO。这是个很有效的优化手段,特别是大表上。

索引下推(ICP) 。MySQL 5.6引入的特性,到现在依然是重要优化。假设有索引(a, b),查询WHERE a=1 AND b LIKE '%abc%'。没ICP的时候,引擎先用索引查到所有a=1的行,然后回表再过滤b。有了ICP,引擎在索引层面就能把b LIKE '%abc%'这个条件判断了,不符合的直接不回表,大幅减少IO。

EXPLAIN里看到Using index condition就是ICP在生效。MySQL 9.x和8.4 LTS都默认开启。

索引为什么失效

索引建了但不走,这是最常见的坑。几个典型场景:

函数操作WHERE YEAR(created_at) = 2026这种对列做了函数操作,索引直接失效。改成范围查询:

sql 复制代码
WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01'

隐式类型转换phone字段是VARCHAR类型,查询写WHERE phone = 13800001234,MySQL把字符串转成数字去匹配,索引失效。得加引号:WHERE phone = '13800001234'

OR条件WHERE a=1 OR b=2,如果ab没有共同的索引,MySQL可能放弃索引走全表扫描。解决办法是给两列分别建索引让MySQL走索引合并,或者改写成UNION

sql 复制代码
SELECT * FROM t WHERE a=1
UNION
SELECT * FROM t WHERE b=2

LIKE左模糊WHERE name LIKE '%张'左模糊走不了索引。LIKE '张%'可以。如果必须左模糊,考虑全文索引或者用搜索引擎。

不等于操作!=<>基本上走不了索引,MySQL觉得匹配的行太多,不如全表扫描。

复合索引顺序怎么排

这是索引设计里最纠结的部分。几条经验:

把等值查询的列放前面,范围查询的列放后面。因为范围查询后面的列用不上索引。比如WHERE user_id = 100 AND created_at > '2026-01-01',索引应该建(user_id, created_at)而不是反过来。

把排序字段也考虑进去。WHERE user_id = 100 ORDER BY created_at DESC,如果索引是(user_id, created_at),排序可以直接利用索引的有序性,Extra里不会出现Using filesort

字段区分度高的优先。status字段只有0和1两个值,区分度极低,放复合索引前面意义不大。user_id几万个不同值,放前面更好。

一个实际的优化案例

有个订单表2000万行,查询SELECT * FROM orders WHERE merchant_id = 88 AND status = 1 AND created_at > '2026-06-01' ORDER BY created_at DESC LIMIT 20,耗时4.5秒。

表上已有索引(merchant_id)(status)EXPLAIN一看,typeref,走的merchant_id索引,但rows预估扫描了80万行,ExtraUsing filesort。扫描量大是因为statuscreated_at的过滤在回表后做的,排序也没法用索引。

重新设计索引:(merchant_id, status, created_at)

等值列merchant_idstatus在前面,范围列created_at在最后,排序也是created_at,索引天然有序。建完索引后再查,type变成refrows降到230,Extra显示Using index condition,耗时降到8毫秒。

从4.5秒到8毫秒,就改了一个索引。但别高兴太早,建索引是有代价的------每次INSERT/UPDATE/DELETE都要维护索引,索引多了写入变慢,存储也变大。所以加索引之前评估一下这个查询的频率和写入压力,别为了一个偶尔跑一次的报表查询建一堆索引。

MySQL索引优化说到底就是搞清楚B+树怎么工作的、查询条件怎么跟索引匹配。工具链就那几个------慢查询日志、EXPLAIN、performance_schema。关键是养成习惯,上线前EXPLAIN一下,别等用户投诉了才发现全表扫描。

相关推荐
. . . . .1 小时前
服务端监控
后端
2601_962074811 小时前
大数据-264 实时数仓 - Canal MySQL的binlog研究 存储目录 变动信息 配置MySQL
大数据·数据库·mysql
凤山老林1 小时前
Spring Boot 集成 Spring Vault:集中式密钥管理与动态凭证轮换
spring boot·后端·spring·vault·集中式秘钥管理
雨夜之寂1 小时前
雨夜-现在有办法识别是不是ai文章么
后端·面试
2601_962071171 小时前
八. Spring Boot2 整合连接 Redis(超详细剖析)
spring boot·redis·后端
2601_962073811 小时前
大数据-240 离线数仓 - 广告业务 测试 ADS层数据加载 DataX数据导出到 MySQL
大数据·数据库·mysql
掘金者阿豪1 小时前
一次 413 Content Too Large 排查:真正的问题可能不在你的后端
后端
程序员贺加贝1 小时前
一次 SaaS ERP 主数据生命周期设计:Policy、PreCheck 与结构化阻断原因
java·后端·架构·saas
苏三说技术2 小时前
Redis已正式接入AI
后端