大表的高效分页

Dav*_*vid 10 postgresql performance index paging postgresql-10 query-performance

使用PostgreSQL 10.5。我正在尝试创建一个分页系统,用户可以在其中来回切换各种结果。

为了不使用OFFSET,我id在名为p(prevId)的参数中从上一页的最后一行传递了。然后我选择id高于p参数中传递的数字的前三行。(如本文所述

例如,如果id上一页的最后一行是 5,我会选择前 3 行的 anid大于 5:

SELECT 
  id, 
  firstname, 
  lastname 
FROM 
  people 
WHERE 
  firstname = 'John'
  AND id > 5 
ORDER BY 
  ID ASC 
LIMIT 
  3;
Run Code Online (Sandbox Code Playgroud)

这很好用,而且时机也不错:

Limit  (cost=0.00..3.37 rows=3 width=17) (actual time=0.046..0.117 rows=3 loops=1)
   ->  Seq Scan on people  (cost=0.00..4494.15 rows=4000 width=17) (actual time=0.044..0.114 rows=3 loops=1)
         Filter: ((id > 5) AND (firstname = 'John'::text))
         Rows Removed by Filter: 384
 Planning time: 0.148 ms
 Execution time: 0.147 ms
Run Code Online (Sandbox Code Playgroud)

虽然,另一方面,如果用户想返回上一页,事情看起来有点不同:

首先,我会传递id第一行的 ,然后在它前面加上减号以指示我应该选择id小于(正)p参数的行。即,如果id第一行的 为 6,则p参数将为-6。同样,我的查询如下所示:

SELECT 
  * 
FROM 
  (
    SELECT 
      id, 
      firstname, 
      lastname 
    FROM 
      people 
    WHERE 
      firstname = 'John' 
      AND id < 6 
    ORDER BY 
      id DESC 
    LIMIT 
      3
  ) as d 
ORDER BY 
  id ASC;
Run Code Online (Sandbox Code Playgroud)

在上面的查询中,我首先选择id小于 6的最后 3 行,然后反转它们,以便以与开头描述的第一个查询相同的方式呈现它们。

这可以正常工作,但是由于数据库几乎遍历了我所有的行,因此性能受到影响:

Sort  (cost=4252.75..4252.76 rows=1 width=17) (actual time=194.464..194.464 rows=0 loops=1)
   Sort Key: people.id
   Sort Method: quicksort  Memory: 25kB
   ->  Limit  (cost=4252.73..4252.73 rows=1 width=17) (actual time=194.460..194.460 rows=0 loops=1)
         ->  Sort  (cost=4252.73..4252.73 rows=1 width=17) (actual time=194.459..194.459 rows=0 loops=1)
               Sort Key: people.id DESC
               Sort Method: quicksort  Memory: 25kB
               ->  Gather  (cost=1000.00..4252.72 rows=1 width=17) (actual time=194.448..212.010 rows=0 loops=1)
                     Workers Planned: 1
                     Workers Launched: 1
                     ->  Parallel Seq Scan on people  (cost=0.00..3252.62 rows=1 width=17) (actual time=18.132..18.132 rows=0 loops=2)
                           Filter: ((id < 13) AND (firstname = 'John'::text))
                           Rows Removed by Filter: 100505
Planning time: 0.116 ms
Execution time: 212.057 ms
Run Code Online (Sandbox Code Playgroud)

话虽如此,我很感激您花时间阅读到这里,我的问题是,如何使分页更有效率?

Erw*_*ter 11

性能的关键是匹配以下形式的多列索引

CREATE UNIQUE INDEX ON people (firstname, id);
Run Code Online (Sandbox Code Playgroud)

UNIQUE,因为没有它,排序顺序可能不明确,并且您可以从对等方获得任意结果。

A UNIQUEorPRIMARY KEY约束也起作用。

虽然像您的示例中那样检查第一列是否相等(或按与查询相同的方向排序),但此索引适用于向上和向下分页,尽管它更适合向上分页。

索引就位后(并在ANALYZE表上运行后),您将不会再看到顺序扫描(除非您的表很小)。数据库不再“遍历几乎所有行”。

阅读您链接到的Markus Winand的精美演示文稿

如果您想要跨多个分页firstname,请使用 ROW 值。向下翻页示例:

SELECT *
FROM  (
   SELECT id, firstname, lastname 
   FROM   people
   WHERE  (firstname, id) < ('John', 6)  -- ROW values
   ORDER  BY firstname DESC, id DESC 
   LIMIT  3
   ) d 
ORDER BY firstname, id;
Run Code Online (Sandbox Code Playgroud)

有关的:

如果SELECT列表只增加了lastname像你的榜样,你可能会尝试该列添加到索引中得到仅索引扫描的吧:

CREATE UNIQUE INDEX ON people (firstname, id, lastname);
Run Code Online (Sandbox Code Playgroud)

按此顺序索引表达式。

即将推出的Postgres 11允许对INCLUDE列进行索引,这导致索引大小更小,性能更好,适用于更多情况。喜欢:

CREATE UNIQUE INDEX ON people (firstname, id) INCLUDE (lastname);
Run Code Online (Sandbox Code Playgroud)