You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MySQL 8.0分页查询优化器索引选择不合理问题咨询

MySQL 8.0分页查询优化器索引选择异常问题

问题背景

业务库中有存量数据量较大的表PendingExpense,近期开发分页查询GET接口时遇到性能异常:MySQL 8.0.23优化器未按预期走过滤字段的联合索引,反而优先为ORDER BY子句做DATE字段的全索引反向扫描,再逐行执行WHERE条件过滤,最终查询耗时长达数秒。
业务逻辑中通过companyId、loginCredentialId两个字段联合定位数据所属用户。

表结构与现有索引

create table PendingExpense
(
    ID                        bigint   auto_increment primary key,
    LOGINCREDENTIALID         int      null,
    COMPANYID                 int      null,
    DATE                      datetime null,
    -- ... 其他无关字段:金额、状态、类型、国家、商户信息等
)

-- 现有索引
create index IN_PendingExpense_COMPANYID_ASC_LOGINCREDENTIALID_ASC
    on PendingExpense (COMPANYID, LOGINCREDENTIALID);

create index IN_PendingExpense_LOGINCREDENTIALID_ASC
    on PendingExpense (LOGINCREDENTIALID);

create index IN_PendingExpense_Date
    on PendingExpense (DATE);

对比测试结果

两条查询逻辑完全一致,仅存在是否加索引提示的差异,执行计划与耗时对比如下:

无索引提示的查询(耗时5.5秒)

查询SQL:

explain analyze select id from PendingExpense
where COMPANYID = 1641 and LOGINCREDENTIALID = 2451
order by date DESC, id DESC
limit 101;

执行计划输出:

-> Limit: 101 row(s)  (cost=2356102.00 rows=101) (actual time=2292.676..4474.843 rows=101 loops=1)
    -> Filter: ((PendingExpense.LOGINCREDENTIALID = 2451) and (PendingExpense.COMPANYID = 1641))  (cost=2356102.00 rows=105) (actual time=2292.675..4474.818 rows=101 loops=1)
        -> Index scan on PendingExpense using IN_PendintExpense_Date (reverse)  (cost=2356102.00 rows=5660) (actual time=0.088..4371.774 rows=1491859 loops=1)

该执行路径下优化器选择反向扫描DATE索引,累计扫描149万余行数据后才过滤出符合条件的101条结果。

强制走联合索引的查询(耗时0.184秒)

查询SQL:

explain analyze select id from PendingExpense use index (IN_PendingExpense_COMPANYID_ASC_LOGINCREDENTIALID_ASC)
where COMPANYID = 1641 and LOGINCREDENTIALID = 2451
order by date desc, id desc
limit 101;

执行计划输出:

-> Limit: 101 row(s)  (cost=9722.30 rows=101) (actual time=38.255..38.267 rows=101 loops=1)
    -> Sort: PendingExpense.`DATE` DESC, PendingExpense.ID DESC, limit input to 101 row(s) per chunk  (cost=9722.30 rows=27778) (actual time=38.254..38.259 rows=101 loops=1)
        -> Index lookup on PendingExpense using IN_PendingExpense_COMPANYID_ASC_LOGINCREDENTIALID_ASC (COMPANYID=1641, LOGINCREDENTIALID=2451)  (actual time=0.046..35.410 rows=14170 loops=1)

该执行路径下仅通过联合索引定位到14170条符合过滤条件的记录,排序后取前101条,性能提升近30倍。

核心疑问

  • 过滤字段COMPANYID、LOGINCREDENTIALID已经存在可用的联合索引,MySQL优化器为什么会选择全量扫描DATE索引再逐行过滤的低效方案?
  • 出于代码可维护性考虑,不希望在业务SQL中硬编码索引提示,有什么其他优化方案可以让优化器自动选择正确的执行路径?

内容的提问来源于stack exchange,提问作者J. LaF

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 06:01:20