为啥 row_number() 比使用偏移快?

Posted

技术标签:

【中文标题】为啥 row_number() 比使用偏移快?【英文标题】:Why is row_number() faster than using offset?为什么 row_number() 比使用偏移快? 【发布时间】:2014-06-16 19:36:44 【问题描述】:

在 PostgreSQL 9.3 中尝试使用窗口函数时,我遇到了一个相当有趣的案例。与OFFSET vs. ROW_NUMBER() 上的答案直接矛盾的是,我发现窗口函数实际上 比OFFSET。

使用偏移量,大约需要 2500 毫秒:

select part_no, description
from inventory
order by part_no
limit 1000 offset 400000

使用 row_number(),大约需要 450 毫秒:

select *
from (select part_no, description, row_number() OVER () 
    from inventory
    order by part_no) AS ss
where row_number >= 400001
limit 1000

这个(新分析的)表有大约 450,000 行,part_no 被索引。 EXPLAIN 表示在 row_number() 情况下正在执行索引扫描,在 OFFSET 情况下正在执行顺序扫描。

我尝试了不同偏移量大小的 OFFSET、row_number() 组合以及索引和未索引排序顺序。所有时间都是几次运行的近似平均值(查询时间通常非常一致。)

            -------indexed-------    ------unindexed------
offset by   OFFSET   row_number()    OFFSET   row_number()
==========================================================
400000      2500ms          450ms     500ms          650ms
40000         80ms           60ms     850ms          650ms
4000          30ms           30ms     390ms          650ms

我想这里真正的问题是;在这两种情况下,查询规划器有什么不同,我怎样才能让它做出更好的选择(特别是在大偏移量+索引列的情况下)?

【问题讨论】:

【参考方案1】:

您的比较不完全有效。你需要使用:

row_number() OVER (<b>ORDER BY part_no</b>)

获得等效的结果。而ORDER BY 需要移动到外部查询。所以:

SELECT part_no, description
FROM  (
   SELECT part_no, description, row_number() OVER (ORDER BY part_no) AS rn
   FROM   inventory) AS ss
WHERE  rn > 400000
ORDER  BY rn
LIMIT  1000;

或者:

SELECT part_no, description
FROM  (
   SELECT part_no, description, row_number() OVER (ORDER BY part_no) AS rn
   FROM   inventory) AS ss
WHERE  rn BETWEEN 400000 AND 401000
ORDER  BY rn;

另外,你指的比较是4年,Postgres的版本还没有声明。我假设您正在使用最新的 9.3 进行测试?在过去的几年里有很多改进......

【讨论】:

我把 ORDER BY 移到了 OVER 子句中,似乎对性能影响不大。有趣的是,添加外部 ORDER BY 导致查询再次运行非常缓慢(>5s),但似乎根本没有改变结果的顺序。似乎外部 ORDER BY 的存在导致规划器显示两个排序步骤,其中前一种情况同时省略(尽管必须在 WindowAgg 中隐含一个。

以上是关于为啥 row_number() 比使用偏移快?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 UNION 比 OR 语句快 [重复]

为啥 MSMQ 比 WCF QueueService 快?

为啥 Presto 比 Spark SQL 快 [关闭]

为啥输入较小时插入排序比快速排序快?

为啥`arr.take(idx)`比`arr[idx]`快

为啥公共领域比属性快?