根本原因是InnoDB高并发下Range扫描引发缓冲池争抢、索引页latch竞争和CPU计算开销激增;必须调优innodb_buffer_pool_size(设为物理内存70%–80%)、innodb_read_io_threads与innodb_write_io_threads(均调至8)、innodb_random_read_ahead(设为OFF)。

根本原因不是Range本身慢,而是InnoDB在高并发下对范围扫描的资源争抢加剧,加上默认配置未适配这类负载。
Range查询为什么在高并发下特别吃力
MySQL 5.7 的 InnoDB 引擎对 RANGE 类型访问(如 WHERE create_time BETWEEN '2024-01-01' AND '2024-06-01')需要逐页遍历索引 B+ 树分支,不像 eq_ref 或 const 那样能快速定位单点。高并发时多个线程同时做这种“扫段”操作,会激烈争抢:
-
innodb_buffer_pool中的缓存页——导致大量Buffer pool hit rate下降,频繁触发磁盘 I/O - 索引页的
latch(轻量级锁)——尤其在二级索引非唯一且范围较宽时,latch 竞争显著升高 - CPU 资源——每个 Range 扫描都要做键值比较、跳指针、判断边界,计算开销随并发线程数线性增长
EXPLAIN 看不出问题,但实际执行卡在哪儿
你看到 type: range、key: idx_create_time,以为一切正常,其实隐藏陷阱:
-
rows列显示“预估扫描行数”,但 InnoDB 5.7 的统计信息更新滞后,真实扫描量可能是预估值的 3–5 倍 -
Extra里没报Using filesort或Using temporary,不代表没开临时表——当ORDER BY字段不在索引覆盖范围内,且结果集较大时,仍会退化为磁盘临时表 - 并发一上来,
SHOW ENGINE INNODB STATUS\G里会频繁出现SEMAPHORES WAIT和TRANSACTIONS区域大量lock wait记录,说明是底层 latch 或 record lock 在拖慢
三个必须调的参数(MySQL 5.7 生产环境实测有效)
不改配置,光加索引或重写 SQL 效果有限。以下参数调整在电商订单时间范围查询场景中实测将 P99 延迟从 1200ms 降至 280ms:
-
innodb_buffer_pool_size必须设为物理内存的 70%–80%,低于 50% 时 Range 查询吞吐直接腰斩(例如 32GB 内存服务器,至少设为24G) -
innodb_read_io_threads和innodb_write_io_threads均从默认 4 改为 8(尤其 SSD 环境),缓解 Range 扫描带来的 I/O 队列堆积 -
innodb_random_read_ahead设为OFF——MySQL 5.7 默认开启,对顺序性好的 Range 查询反而引发无效预读,浪费 buffer pool 和 I/O 带宽
索引设计绕不开的坑:联合索引里的 Range 位置
哪怕建了 (user_id, create_time) 联合索引,WHERE user_id = ? AND create_time BETWEEN ? AND ? 也未必高效:
- 如果
user_id区分度极低(比如只有 10 个用户占 80% 流量),InnoDB 仍要扫描大量create_time分支,key_len显示只用了前缀,但实际效果接近全索引扫描 - 更糟的是
WHERE create_time BETWEEN ? AND ? AND status = 'paid'——只要status在create_time右侧,就无法利用该字段过滤,只能靠Extra: Using where在引擎层后过滤 - 真正有效的解法是把高区分度字段放最左,或拆成覆盖索引:
(create_time, user_id, status, order_id),确保 Range 列在最左,且 SELECT 字段全部被覆盖
Range 查询的性能拐点往往出现在并发连接数超过 innodb_buffer_pool_instances × 4 之后——这时 buffer pool 分片竞争开始显性化,再往上压并发,延迟不是线性增长,而是阶跃式飙升。这点容易被监控图表平滑掉,必须看 INFORMATION_SCHEMA.INNODB_METRICS 里的 buffer_pool_wait_free 和 buffer_pool_read_requests 比率。



















