多数情况下 DISTINCT ON 更快——它在扫描时就去重,无需额外排序或窗口计算;而 ROW_NUMBER() 需先生成全序列再过滤,开销更大,但前提是索引覆盖 DISTINCT ON 和 ORDER BY 的列顺序。

PostgreSQL 里 DISTINCT ON 和 ROW_NUMBER() 哪个更快?
多数情况下 DISTINCT ON 更快——它在扫描时就做去重,不需额外排序或窗口计算;而 ROW_NUMBER() 必须先生成完整序号列,再过滤,多一次逻辑读和内存开销。但这个结论有前提:查询能利用索引支持 DISTINCT ON 的排序字段。
DISTINCT ON 能用上索引的关键条件
DISTINCT ON 的性能优势依赖索引能否覆盖其排序逻辑。它等价于“对每组取排序后第一行”,所以 PostgreSQL 会尝试用索引跳过重复扫描。
- 必须有索引以
DISTINCT ON (col1)的列开头,且后续包含ORDER BY col1, col2中的列(顺序一致) - 例如:
DISTINCT ON (user_id) ORDER BY user_id, created_at DESC需要索引ON posts (user_id, created_at DESC) - 如果
ORDER BY和DISTINCT ON列顺序不一致(如DISTINCT ON (a) ORDER BY b),就无法用索引加速,退化为全表扫描 + 排序 - 复合条件中带非前导列过滤(如
WHERE status = 'active')时,除非该列也在索引前缀中,否则可能跳过索引
ROW_NUMBER() 在什么场景下反而更合适?
当你要取“每组第 N 行”(不只是第一行),或需要基于复杂条件动态决定排序优先级时,ROW_NUMBER() 是唯一选择;DISTINCT ON 只能固定取第一行。
- 需要分页:比如“每个用户最新两条帖子”,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)+WHERE rn - 排序逻辑无法静态表达:比如按“有图片优先、再按时间”,需在
ORDER BY里写CASE WHEN image_url IS NOT NULL THEN 0 ELSE 1 END, created_at DESC,这种DISTINCT ON也能写,但可读性和维护性差 - 需要后续对序号做计算:比如只取奇数行、或跨组累计计数,
ROW_NUMBER()提供的是稳定可复用的数值,DISTINCT ON没有中间值 - 注意:若仅为了取第一行却用了
ROW_NUMBER(),且没加合适的索引,执行计划常出现WindowAgg+Sort,比Unique+Index Scan多出 2–5 倍执行时间
实测性能差异的典型表现
在千万级 posts 表(含 user_id 索引)上测试“每个用户最新一篇”:
EXPLAIN ANALYZE SELECT DISTINCT ON (user_id) * FROM posts ORDER BY user_id, created_at DESC;
走 Index Scan Backward using idx_user_created,耗时约 120ms;
EXPLAIN ANALYZE SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn FROM posts ) t WHERE rn = 1;
走 WindowAgg → Sort → Seq Scan,耗时约 480ms(无索引优化时可能超 2s)。
真正容易被忽略的是:即使你写了 ROW_NUMBER(),只要外层只取 rn = 1,PostgreSQL 9.6+ 其实能自动优化成类似 DISTINCT ON 的执行路径——但前提是 PARTITION BY 和 ORDER BY 列完全匹配索引,且没有其他干扰条件(如函数包裹、隐式类型转换)。一旦触发不了这个优化,性能落差就实实在在落在慢查询日志里了。











