用于对联合所有结果集执行偏移/限制的算法

Dmi*_*din 5 postgresql execution-plan database-internals postgresql-9.4

我有两个结果集:

rs1:

     id       name
   serial     text
________________
     1        Nick
....................
  1233112     Pete
Run Code Online (Sandbox Code Playgroud)

rs2:

     id       name
   serial     text
________________
  123121      Mike
....................
 221233112   Junior
Run Code Online (Sandbox Code Playgroud)

如果我们写一个查询:

SELECT *
FROM
(

    SELECT *
    FROM rs1

    UNION ALL

    SELECT *
    FROM rs2

) as rs
OFFSET 100000 LIMIT 10;
Run Code Online (Sandbox Code Playgroud)

查询结果是如何计算的?将使用什么算法?

我相信服务器执行惰性评估是指不加载整个联合,对其进行迭代,然后返回所需的结果。

如果您描述了其他 SQL 服务器中使用的算法作为补充,我将很高兴。

UPD:我对数据库内部结构很陌生,不知道是否可以向 sql-server 本身询问查询的执行计划。

这是一个查询计划:

"Limit  (cost=77.11..77.88 rows=10 width=522)"
"  ->  Append  (cost=0.00..140.18 rows=1818 width=522)"
"        ->  Seq Scan on tbl  (cost=0.00..70.09 rows=909 width=522)"
"        ->  Seq Scan on tbl tbl_1  (cost=0.00..70.09 rows=909 width=522)"
Run Code Online (Sandbox Code Playgroud)

带 order by id 的 saqme 查询:

"Limit  (cost=33.16..36.42 rows=10 width=522)"
"  ->  Merge Append  (cost=0.56..593.16 rows=1818 width=522)"
"        Sort Key: tbl.id"
"        ->  Index Scan Backward using pk_tabl on tbl (cost=0.28..285.21 rows=909 width=522)"
"        ->  Index Scan Backward using pk_tbl on tbl tbl_1  (cost=0.28..285.21 rows=909 width=522)"
Run Code Online (Sandbox Code Playgroud)

Dav*_*ett 3

何时应用限制子句OFFSET取决于查询,特别是结果的排序依据。

如果您按列排序,而这些列也用于过滤结果(WHERE例如在子句中)并已建立索引,则通常会提前应用限制。查询规划器将尝试仅查找与搜索条件匹配的第一行。否则,限制通常会在其他所有内容之后应用,因此服务器可能会将结果假脱机到临时存储中,然后排序并应用您的OFFSET/LIMIT过滤器。

对于UNION查询本身,它取决于查询规划器中的大脑,因此您可能必须查看其计划输出才能知道(我不是 postgres 专家),并且它可能再次取决于查询详细信息的其余部分。然而工作已经完成(这将影响性能),但输出将是相同的:限制将应用于完整的结果。

在您给出的示例中,UNION查询实际上提供了一个名为 的派生表rs。该限制肯定会应用于此处的整个结果,因此它在功能上与您所做的相同SELECT * FROM <some_table> OFFSET 100000 LIMIT 10。因为它们是相同的,您可以将示例重写为简单:

SELECT * FROM rs1
UNION ALL
SELECT * FROM rs2
OFFSET 100000 LIMIT 10;
Run Code Online (Sandbox Code Playgroud)

结果是一样的。

一个重要的注意事项:您没有在此处指定任何订购条款。这可能是因为您给出了一个最小的示例,所以如果我告诉您一些您已经知道的事情,请原谅我,但是如果没有排序子句,您应该假设输出顺序是任意的,可能是随机的,并且可能在运行之间发生变化相同的查询。有时这很好(您只想要数据,而不关心顺序),但是一旦您使用像OFFSET// LIMIT/ TOP... 这样的东西,您就会关心顺序,因为之后的查询OFFSET 10000 LIMIT 10可能是OFFSET 10010 LIMIT 10并且你希望它肯定需要按某种顺序排列十个不同的行(而不是理论上可能最终与上次相同的十个任意十行)。在实践中,输出的顺序通常比这更稳定,但并不总是如此,因此如果输出顺序的突然变化会导致不良行为,您应该始终提供排序子句。