row_number()配合partition by取最新记录的核心是:先按分组字段分区,再按时间字段desc排序使最新记录排首位,最后在子查询中筛选rn=1;必须建联合索引提升性能,且where不可直接引用窗口函数。

ROW_NUMBER() 配合 PARTITION BY 实现分组取最新
核心思路是:先按分组字段 PARTITION BY,再按时间/序号字段 ORDER BY DESC 排序,最后筛选 ROW_NUMBER() = 1 的行。关键不是“取最大”,而是“排第一”——因为最新记录未必对应最大 ID,但一定在降序排序后占首位。
常见错误是漏写 ORDER BY 或写反方向(比如用 ASC 却想取最新),导致结果随机或取到最老一条。
-
PARTITION BY必须明确指定分组依据,比如user_id、category_id,不能留空或错用聚合字段 - 排序字段推荐用带时区的
created_at或updated_at,避免仅依赖id(自增 ID 在分布式写入下不严格保序) - 如果存在毫秒级并发写入,需确认数据库对
timestamp的精度支持(例如 MySQL 5.6 默认只到秒,需显式声明datetime(3))
WHERE 子句里不能直接引用 ROW_NUMBER()
这是最常踩的坑:SELECT *, ROW_NUMBER() OVER (...) AS rn FROM t WHERE rn = 1 会报错,因为 WHERE 执行顺序在窗口函数之前。必须把窗口计算放在子查询或 CTE 中。
正确写法是两层结构:
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY updated_at DESC, id DESC
) AS rn
FROM orders
) t
WHERE t.rn = 1;
注意:ORDER BY 里加 id DESC 是为时间相同时保确定性——否则同秒更新的多条记录,ROW_NUMBER() 分配可能因执行计划变化而波动。
MySQL 8.0+ 和 PostgreSQL 行为一致,但旧版 MySQL 不支持
MySQL 5.7 及更早版本不支持窗口函数,强行运行会提示 ERROR 1064 (42000): You have an error in your SQL syntax。此时只能用关联子查询或 GROUP BY + MAX() 配合 JOIN 模拟,但逻辑更重、性能更差。
PostgreSQL 和 SQL Server 对 ROW_NUMBER() 支持成熟,但要注意:SQL Server 的 datetime 类型精度只有 3.33ms,若业务要求毫秒级准确,应改用 datetime2(3)。
- SQLite 从 3.25.0 开始支持窗口函数,但需确认实际部署版本
- Oracle 用户可直接用,但传统写法偏好
ROWNUM,注意ROWNUM是物理读序,不能替代ROW_NUMBER() OVER
性能敏感时要给排序字段建联合索引
当表数据量超过十万行,没索引会导致全表扫描 + 文件排序(Using filesort),响应明显变慢。最优索引应覆盖 PARTITION BY 和 ORDER BY 字段。
例如按 user_id 分组、按 updated_at DESC 取最新,建索引:
CREATE INDEX idx_user_updated ON orders (user_id, updated_at DESC);
注意:MySQL 8.0+ 支持降序索引,但 5.7 只能建 (user_id, updated_at),然后靠优化器倒排扫描;PostgreSQL 则对 DESC 索引效果更好。
如果查询还带其他过滤条件(如 status = 'active'),索引字段顺序要权衡——通常把等值条件放前,分组字段次之,排序字段在最后。










