窗口函数中order by必触发排序,无索引时超内存阈值即落盘;有效索引须为partition by+order by复合顺序;过滤需在窗口前下推;多窗口共用同一window定义方可复用排序。

窗口函数 ORDER BY 没走索引就必然落盘
只要 OVER 子句里写了 ORDER BY,数据库就必须排序;没索引支撑时,MySQL 8.0+ 或 PostgreSQL 会先尝试内存排序,一旦超出 sort_buffer_size(MySQL)或 work_mem(PostgreSQL),立刻写入磁盘临时表。你在执行计划里看到 Sort 节点带 Warning: Operator used tempdb to spill data 或 Materialize,就是已经落盘了。
- 检查方式:运行
EXPLAIN ANALYZE,重点看Extra是否含Using filesort,或Plans中是否出现Window Spool/Materialize - 不是“偶尔慢”,而是每次查询都稳定落盘——只要输入数据量超过内存阈值,就会触发
- 字符串字段要注意校对规则:
utf8mb4_0900_as_cs和utf8mb4_general_ci排序行为不同,可能导致优化器弃用已有索引
复合索引必须严格匹配 PARTITION BY + ORDER BY 顺序
只给 ORDER BY 字段建单列索引没用,数据库无法在分完区后复用该索引做区内排序。真正有效的索引,第一列必须是 PARTITION BY 字段,第二列是 ORDER BY 字段,且方向一致。
- 错误写法:
CREATE INDEX idx_created ON orders(created_at DESC)—— 即使查询是PARTITION BY user_id ORDER BY created_at DESC,也无效 - 正确写法:
CREATE INDEX idx_user_created_desc ON orders(user_id, created_at DESC) - MySQL 8.0+ 要求
DESC显式写在索引定义里,仅靠ORDER BY created_at DESC语句不会自动匹配升序索引 - PostgreSQL 同理:索引需为
(user_id, created_at),且表数据物理顺序必须与该索引一致,否则仍会触发Materialize
WHERE 必须放在窗口之前,不能塞进 CTE 或子查询里再开窗
窗口函数无法下推过滤条件。如果你写成 WITH w AS (SELECT *, ROW_NUMBER() OVER (...) rn FROM orders) SELECT * FROM w WHERE rn ,引擎会先对全表计算 row number,再裁剪——哪怕原始表有 5000 万行,而你只关心最近 1 天的 2 万条记录,那 4998 万行的排序全是白干。
- 正确姿势:
SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) FROM orders WHERE created_at >= '2026-08-02' - 如果过滤条件涉及表达式(如
WHERE DATE(created_at) = '2026-08-02'),函数会导致索引失效,改用范围写法:created_at >= '2026-08-02' AND created_at - 高频过滤字段可加入索引前缀,例如:
CREATE INDEX idx_status_user_created ON orders(status, user_id, created_at DESC)
多个窗口共用同一 OVER 子句才能复用排序
现代引擎(PostgreSQL 13+、SQL Server 2016+、MySQL 8.0.22+)能识别完全一致的 OVER 子句并复用一次排序结果。但“看似相同”不等于“被识别为相同”——细微差异都会导致重复排序。
- 安全写法:
ROW_NUMBER() OVER w, AVG(amount) OVER w, LAG(amount) OVER w,配合WINDOW w AS (PARTITION BY user_id ORDER BY created_at DESC) - 危险写法:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)和AVG(amount) OVER (PARTITION BY user_id ORDER BY created_at DESC ASC)—— 后者语法合法,但部分版本不认为它等价于前者 - 避免混用排序方向:
ORDER BY created_at DESC和ORDER BY created_at ASC共存,即使在同一查询中,也会强制两次排序 - 验证方法:执行计划中应只有一个
Sort或WindowAgg节点,而不是每个窗口函数都带一个
实际生产中最容易被忽略的,是把 ORDER BY 当成可选装饰——它根本不是语法糖,而是排序指令。哪怕只用 ROW_NUMBER(),只要写了 ORDER BY,就得承担对应排序成本;而漏掉索引,就等于默认选择磁盘排序。










