order by 在千万级表上变慢是因为未走索引导致 filesort;窗口函数如 row_number() 不走索引且易内存溢出;应优先用索引+limit、游标分页和合理索引设计优化。

ORDER BY 在千万级表上变慢,是因为没走索引
数据库对 ORDER BY 字段没有有效索引时,会强制触发 filesort,数据量一过百万,排序就从毫秒级跳到秒级甚至分钟级。不是 SQL 写得不够“高级”,是执行计划根本没用上索引。
实操建议:
- 检查
EXPLAIN输出里的type是否为index或range,Extra里不能有Using filesort - 复合索引要严格遵循「最左前缀」:比如
ORDER BY user_id, created_at DESC,索引必须是(user_id, created_at),反过来不行 - 如果排序字段含函数(如
ORDER BY UPPER(name)),普通索引无效,得建函数索引(MySQL 8.0+ 支持CREATE INDEX idx_name_upper ON t ((UPPER(name))))
ROW_NUMBER() 窗口函数在大数据量下内存爆掉
窗口函数本身不走索引,ROW_NUMBER() OVER (ORDER BY x) 会先把整个分区结果集拉进内存排序,千万行数据轻松吃光 sort_buffer_size,触发磁盘临时表甚至 OOM。
实操建议:
- 先用带索引的
WHERE过滤出目标子集,再套窗口函数——别在全表上开窗 - 避免
OVER ()全局无分区开窗;必须用时,确认sort_buffer_size足够(但别盲目调大,可能挤占其他连接内存) - PostgreSQL 可加
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW显式限制窗口范围,MySQL 8.0+ 不支持该优化,得靠应用层分页模拟
用索引覆盖 + LIMIT 做“伪窗口”替代全量排序
当业务只要前 N 条(比如分页第一页、排行榜 Top100),硬算全量 ROW_NUMBER() 是浪费。索引配合 LIMIT 能跳过绝大部分数据,速度差一个数量级。
实操建议:
- 确保
ORDER BY字段有索引,且LIMIT值不大(LIMIT 10快,LIMIT 1000000, 10仍会扫一百万行) - 深分页用游标法:记录上一页最大排序值,下一页查
WHERE sort_col > 'last_value' ORDER BY sort_col LIMIT 10,彻底避开OFFSET - 如果还要返回总行数,别用
COUNT(*)全表扫——改用近似统计(如 MySQL 的information_schema.TABLES中TABLE_ROWS)或业务可接受误差的采样估算
MySQL 8.0 窗口函数和索引能共存吗
能,但有限制。MySQL 8.0 的窗口函数执行阶段在索引扫描之后,所以 WHERE + ORDER BY + 窗口函数 这条链路中,只有 WHERE 和 ORDER BY 部分能用索引,窗口计算本身不加速。
实操建议:
-
ORDER BY字段必须出现在索引最左,否则窗口的排序步骤仍会触发 filesort - 联合索引中,等值条件字段(
WHERE a = ?)要放前面,排序字段(ORDER BY b)紧随其后,例如(a, b);如果写成(b, a),a = ?就无法利用索引下推 - 测试时用
EXPLAIN FORMAT=TREE看实际执行流程,确认 “Window aggregate” 节点输入是否来自 “Index range scan” 而非 “Table scan”
真正卡住性能的,往往不是窗口函数语法多难,而是默认把“排序”这件事全交给数据库去做。索引设计、过滤时机、结果集大小控制——这三个点漏掉任何一个,优化就只停留在语句层面。










