MySQL 8.0.13+ 仅支持 PERCENTILE_CONT/PERCENTILE_DISC 作为窗口函数(需 OVER),不支持聚合语法;其底层仍需全量排序,无法避免 O(n) 扫描,性能瓶颈仍在索引覆盖与 WHERE 剪枝。

MySQL 8.0+ 直接用 PERCENTILE_CONT 还是别试了
MySQL 8.0.13 起确实引入了 PERCENTILE_CONT 和 PERCENTILE_DISC,但它们**仅在窗口函数上下文中支持,且必须配合 OVER() 使用,不支持直接聚合**。更关键的是:这些函数底层仍需对排序字段全量扫描 + 排序,EXPLAIN 显示 type = ALL 或 type = index(走索引但仍是全索引扫描),无法跳过中间行。所以想靠它“自动优化中位数”来规避全表扫描,行不通。
ORDER BY ... LIMIT 分页法为什么在大数据量下依然慢
常见写法如:SELECT val FROM t ORDER BY val LIMIT N,1(N为中位位置)看似只取一行,但 MySQL 必须先完成整个 ORDER BY 排序,才能确定第 N 行是谁。如果 val 列没索引,就是全表扫描 + 文件排序;即使有索引,也要遍历约一半的索引节点 —— 对千万级表,这仍是 O(n/2) 时间复杂度。尤其当 LIMIT 5000000,1 时,优化器无法跳过前 500 万条索引记录。
- 确保
ORDER BY字段有单列索引或联合索引的最左前缀 - 避免
SELECT *,只查需要的列,减少回表开销 - 若表有主键且业务允许近似中位数,可改用采样估算(见下一条)
用 COUNT(*) + 索引覆盖 + 两次查询逼近中位数
这是目前在严格中位数语义 + 可控性能之间最实用的折中方案。核心是把“找第 k 小”拆成两步:先算总数确认中位位置,再用带索引的范围查询定位目标值。前提是目标列有索引,且查询能走 index 或 range 类型。
- 第一步:
SELECT COUNT(*) FROM t—— 如果表有近实时统计(如INFORMATION_SCHEMA.TABLES中的TABLE_ROWS),可跳过这步,但注意该值是估算值 - 第二步:假设总数为 1000001(奇数),中位是第 500001 小的值,执行:
SELECT val FROM t WHERE val IN (SELECT val FROM t ORDER BY val LIMIT 1 OFFSET 500000);但这个子查询仍会触发全排序 - 更稳的做法:用两个独立查询分别取第 500000 和 500001 小的值,再平均。例如:
(SELECT val FROM t ORDER BY val LIMIT 1 OFFSET 499999) UNION ALL (SELECT val FROM t ORDER BY val LIMIT 1 OFFSET 500000),并确保val上有索引
为什么 WHERE 条件里加范围过滤能真正降低扫描量
真正避开全表扫描的关键,不是“怎么算中位数”,而是“能不能提前剪枝”。比如你知道中位数大概率落在 [100, 500] 区间,就可以先加 WHERE val BETWEEN 100 AND 500,再在这个子集上排序取中位。只要这个范围能过滤掉 90% 的数据,后续排序成本就大幅下降。
- 这个范围不能拍脑袋定,建议基于历史查询结果或直方图统计(MySQL 8.0 支持
ANALYZE TABLE生成直方图) - 如果业务能接受误差 ±5%,用
SELECT APPROX_COUNT_DISTINCT配合采样查询更快,但 MySQL 原生不提供APPROX_MEDIAN,得自己实现随机抽样 - 注意:任何在
WHERE中对索引列使用函数(如WHERE ABS(val) > 100)都会让索引失效,回到全表扫描
中位数计算本身没有银弹。所有“避免全表扫描”的努力,最终都落在两点:索引是否精准覆盖排序字段、WHERE 条件能否有效收缩数据集。别迷信函数名,EXPLAIN 里看到 rows 是百万级,就说明还没绕开瓶颈。


















