多数情况下 DISTINCT ON 更快——它在扫描时就去重,无需额外排序或窗口计算;而 ROW_NUMBER() 需先生*序列再过滤,开销更大,但前提是索引覆盖 DISTINCT ON 和 ORDER BY 的列顺序。
多数情况下 DISTINCT ON 更快——它在扫描时就做去重,不需额外排序或窗口计算;而 ROW_NUMBER() 必须先生成完整序号列,再过滤,多一次逻辑读和内存开销。但这个结论有前提:查询能利用索引支持 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')时,除非该列也在索引前缀中,否则可能跳过索引当你要取“每组第 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 列完全匹配索引,且没有其他干扰条件(如函数包裹、隐式类型转换)。一旦触发不了这个优化,性能落差就实实在在落在慢查询日志里了。