PHPLEFT程序开发
MySQL数据库

MySQL 列表页变慢:从执行计划到联合索引

发布于 2026-11-10 更新于 2026-11-10
MySQL 列表页变慢:从执行计划到联合索引

网站列表页MySQL查询变慢十分常见,筛选、排序、分页组合很容易引发慢查询。本文讲解通过explain执行计划分析SQL,理解联合索引最左匹配原则,结合数据分布合理建索引,避免盲目加索引带来额外开销。

不要上来就新建索引,先拿到真实SQL

很多业务系统的列表页随着数据量上涨,查询速度慢慢变差,后台或者前端页面加载卡顿。遇到这种情况,不少开发者第一反应就是直接给where条件里的字段加上索引,但是加完之后发现速度提升有限,甚至写入性能下降。索引不是越多越好,盲目建索引会增加INSERT、UPDATE、DELETE的开销。

优化的第一步,是拿到业务真实执行的SQL语句,而不是根据代码猜想SQL。可以开启MySQL慢查询日志,抓取执行耗时较长的语句;也可以在框架中打印列表查询的完整SQL,注意保留筛选条件、排序字段、分页limit,这些都是影响索引生效的关键。同一个列表页,用户选择不同筛选项,会生成不一样的SQL,不能只拿一条测试数据的SQL作为优化依据。

拿到SQL之后,优先使用explain查看执行计划。重点关注type、key、rows、Extra这几个字段。type代表访问类型,从优到劣一般是system、const、eq_ref、ref、range、index、ALL,ALL就是全表扫描,也是列表页变慢最常见的根源。key代表实际用到的索引,rows是预估扫描行数,Extra里出现Using filesort、Using temporary,就代表出现了文件排序、临时表,是优化重点。

理解联合索引与最左匹配原则

列表页SQL大多是:where条件筛选 + order by排序 + limit分页,这种场景非常适合联合索引。联合索引由多个字段按顺序组成,遵循最左前缀匹配规则,索引顺序非常关键。例如经常按status状态筛选,再按create_time倒序,那么索引顺序优先(status, create_time),而不是反过来。

最左匹配的核心:索引从左往右依次匹配,中间不能断掉。如果索引是(a,b,c),where使用a、a+b、a+b+c都可以走索引;只写b或者c,一般无法命中这个联合索引。同时要注意,索引字段不能参与函数运算、隐式类型转换,比如字段是字符串,传入数字,会导致索引失效,变成全表扫描。

很多人容易混淆筛选字段和排序字段的顺序。原则上,等值筛选字段放前面,范围条件、排序字段放在后面。等值匹配完成之后,后面的字段天然有序,就可以避免Using filesort。如果排序字段不在索引末尾,就算where走了索引,排序阶段依旧需要额外文件排序,数据量大的时候耗时明显上升。

筛选、排序、分页组合场景的索引设计

举一个资讯列表的典型SQL:select * from article where status=1 and cate_id=5 order by create_time desc limit 10; 等值条件是status、cate_id,排序字段是create_time,那么合适的联合索引就是 (status,cate_id,create_time)。这条索引既可以完成等值过滤,同时数据已经按创建时间排好序,数据库不需要再额外排序,limit取前面少量行,效率很高。

如果筛选条件不固定,用户可以自由选择多个筛选项,这时很难用一条联合索引覆盖全部组合。这时不要为每一种筛选组合都新建索引,索引数量太多会拖累写入。优先覆盖高频、流量最大的查询,低频筛选条件允许扫描少量数据;或者考虑引入搜索引擎,把复杂多条件检索交给ES等组件。

分页也是一个大坑,limit offset很大的时候,比如limit 10000,10,MySQL需要先扫描前面一万条数据再丢弃,速度会越来越慢。可以改成主键书签分页,利用where id>上一页最大id的方式替代大offset分页,前提是排序字段稳定有序。

避开这些索引失效的常见坑

第一,select * 尽量少用,只查询业务需要的字段。如果索引包含查询的全部字段,就是覆盖索引,Extra会出现Using index,性能更好。第二,字段上使用函数,比如where date(create_time) = '2026-11-10',会让create_time索引失效,改成区间比较create_time between 起始和结束时间。

第三,字符集、类型不一致导致隐式转换,比如varchar类型的cate_code,查询时传入数字,索引失效。第四,or连接的条件,如果两边字段不在同一个索引,也容易不走索引,可以拆成union all或者调整索引。第五,区分度很低的字段单独建索引没有意义,比如只有0和1两个值的status,单独索引过滤效果差,适合放在联合索引前面配合其他字段使用。

建好索引之后,验证和持续观察

索引创建完成,不能直接判定优化成功,一定要重新explain核对执行计划,确认type、key、Extra符合预期,不再出现全表扫描和不必要的filesort。同时在测试环境模拟接近生产的数据量测试,少量数据的时候,全表扫描速度也很快,问题不会暴露。

上线之后持续观察慢查询日志,同一个列表页在不同筛选参数下表现不一样,有些参数组合依然会走坏计划。MySQL优化器会根据数据分布选择索引,数据倾斜的时候,比如某个分类下几十万条数据,另一个分类只有几十条,优化器选择的方案可能不一样。

数据库优化没有万能模板,联合索引的核心就是贴合真实业务SQL的结构。先抓SQL、看执行计划、理解数据分布,再设计索引,而不是凭经验批量加索引。列表页慢查询,大多都可以通过合理的联合索引设计得到明显改善。