改进PostgreSQL中的OFFSET性能

Jam*_*ber 38 database postgresql query-optimization

我有一个表,我在LIMIT和OFFSET之前进行ORDER BY,以便进行分页.

在ORDER BY列上添加索引会对性能产生巨大影响(与小LIMIT结合使用时).在一个500,000行表中,只要有一个小的LIMIT,我看到增加索引的10,000倍改进.

但是,索引对高OFFSET(即我的分页中的后续页面)没有影响.这是可以理解的:b树索引可以很容易地从头开始按顺序迭代但不能找到第n个项.

似乎有用的是计算的b树索引,但我不知道PostgreSQL中对这些的支持.还有其他解决方案吗?似乎优化大型OFFSET(特别是在分页用例中)并不是那么不寻常.

不幸的是,PostgreSQL手册简单地说"OFFSET子句跳过的行仍然必须在服务器内部计算;因此大的OFFSET可能效率低下."

Mik*_*nov 39

您可能需要计算索引.

让我们创建一个表:

create table sales(day date, amount real);
Run Code Online (Sandbox Code Playgroud)

并填写一些随机的东西:

insert into sales 
    select current_date + s.a as day, random()*100 as amount
    from generate_series(1,20);
Run Code Online (Sandbox Code Playgroud)

按天索引,这里没什么特别的:

create index sales_by_day on sales(day);
Run Code Online (Sandbox Code Playgroud)

创建行位置功能.还有其他方法,这是最简单的方法:

create or replace function sales_pos (date) returns bigint 
   as 'select count(day) from sales where day <= $1;' 
   language sql immutable;
Run Code Online (Sandbox Code Playgroud)

检查它是否有效(不要在大型​​数据集上调用它):

select sales_pos(day), day, amount from sales;

     sales_pos |    day     |  amount  
    -----------+------------+----------
             1 | 2011-07-08 |  41.6135
             2 | 2011-07-09 |  19.0663
             3 | 2011-07-10 |  12.3715
    ..................
Run Code Online (Sandbox Code Playgroud)

现在棘手的部分:添加另一个在sales_pos函数值上计算的索引:

create index sales_by_pos on sales using btree(sales_pos(day));
Run Code Online (Sandbox Code Playgroud)

这是你如何使用它.5是你的"抵消",10是"限制":

select * from sales where sales_pos(day) >= 5 and sales_pos(day) < 5+10;

        day     | amount  
    ------------+---------
     2011-07-12 | 94.3042
     2011-07-13 | 12.9532
     2011-07-14 | 74.7261
    ...............
Run Code Online (Sandbox Code Playgroud)

它很快,因为当你这样称它时,Postgres使用索引中的预先计算的值:

explain select * from sales 
  where sales_pos(day) >= 5 and sales_pos(day) < 5+10;

                                    QUERY PLAN                                
    --------------------------------------------------------------------------
     Index Scan using sales_by_pos on sales  (cost=0.50..8.77 rows=1 width=8)
       Index Cond: ((sales_pos(day) >= 5) AND (sales_pos(day) < 15))
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你.

  • @KonstantineRybnikov嗯..不,但你真的不需要重新计算索引,只要你严格按照日期的顺序插入条目,永远不要删除它们(这是一个好主意).在这种情况下,记录位置永远不会改变. (3认同)
  • 在 [select * from depesz blog: Pagination with fixed order](http://www.depesz.com/index.php/2011/05/20/pagination-with-fixed -命令/) (2认同)
  • 大.那么,现在每次在表中插入单个值时,它会为表中的每个项重新计算这个值吗? (2认同)

Fli*_*mzy 10

我对“计数的 b 树索引”一无所知,但我们在应用程序中为帮助解决此问题所做的一件事是将我们的查询分成两个,可能使用子查询。如果你已经这样做了,我很抱歉浪费你的时间。

SELECT *
FROM massive_table
WHERE id IN (
    SELECT id
    FROM massive_table
    WHERE ...
    LIMIT 50
    OFFSET 500000
);
Run Code Online (Sandbox Code Playgroud)

这里的优点是,虽然它仍然必须计算所有内容的正确顺序,但它不会对整行进行排序——仅对id列进行排序。

  • 根据 PostgreSQL 文档:“使用 LIMIT 时,使用 ORDER BY 子句将结果行限制为唯一的顺序非常重要。否则,您将得到查询行的不可预测的子集。您可能会要求第十到第二十行行,但第十到第二十行的顺序是什么?顺序未知,除非您指定 ORDER BY。” 请参阅 https://www.postgresql.org/docs/current/queries-limit.html (2认同)