Database 提高PostgreSQL中的偏移量性能

Database 提高PostgreSQL中的偏移量性能,database,postgresql,query-optimization,Database,Postgresql,Query Optimization,我有一张表,我在做一个订单,在一个限制和偏移量之前,为了分页 在ORDER BY列上添加索引会对性能产生巨大的影响(与小限制结合使用时)。在一个500000行的表上,我看到添加索引有10000x的改进,只要有一个小的限制 但是,索引对高偏移量(即分页中的后续页面)没有影响。这是可以理解的:b树索引可以很容易地从一开始就按顺序进行迭代,但不能找到第n项 看起来有帮助的是一个计数的b树索引,但我不知道PostgreSQL中是否支持这些索引。还有别的解决办法吗?对于大偏移量的优化(特别是在分页用例中)

我有一张表,我在做一个订单,在一个限制和偏移量之前,为了分页

在ORDER BY列上添加索引会对性能产生巨大的影响(与小限制结合使用时)。在一个500000行的表上,我看到添加索引有10000x的改进,只要有一个小的限制

但是,索引对高偏移量(即分页中的后续页面)没有影响。这是可以理解的:b树索引可以很容易地从一开始就按顺序进行迭代,但不能找到第n项

看起来有帮助的是一个计数的b树索引,但我不知道PostgreSQL中是否支持这些索引。还有别的解决办法吗?对于大偏移量的优化(特别是在分页用例中)似乎并不少见


不幸的是,PostgreSQL手册只是简单地说“被OFFSET子句跳过的行仍然必须在服务器内部计算;因此,较大的偏移量可能是低效的。”

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

SELECT *
FROM massive_table
WHERE id IN (
    SELECT id
    FROM massive_table
    WHERE ...
    LIMIT 50
    OFFSET 500000
);
这里的优点是,虽然它仍然需要计算所有内容的正确顺序,但它不会对整行进行排序——只对id列进行排序

这似乎是针对大型企业的优化 偏移量(特别是在分页中) 用例)并不是那么不寻常

这对我来说似乎有点不寻常。大多数人,大多数时候,似乎不会浏览太多的页面。这是我会支持的,但不会努力优化

但无论如何


由于应用程序代码知道已经看到了哪些有序值,因此应该能够通过在WHERE子句中排除这些值来减少结果集和偏移量。假设您对单个列进行排序,并按升序排序,您的应用程序代码可以在页面上存储最后一个值,然后以某种适当的方式将
和您已排序的列名>最后一个值添加到WHERE子句中。

您可能需要计算索引

让我们创建一个表:

create table sales(day date, amount real);
然后用一些随机的东西填充它:

insert into sales 
    select current_date + s.a as day, random()*100 as amount
    from generate_series(1,20);
按天编制索引,这里没有什么特别之处:

create index sales_by_day on sales(day);
创建行位置函数。还有其他方法,这是最简单的:

create or replace function sales_pos (date) returns bigint 
   as 'select count(day) from sales where day <= $1;' 
   language sql immutable;
现在,棘手的部分是:添加另一个根据sales_pos函数值计算的索引:

create index sales_by_pos on sales using btree(sales_pos(day));
下面是你如何使用它。5是您的“补偿”,10是“限制”:

从sales_pos(day)>=5且sales_pos(day)<5+10的sales中选择*;
日|金额
------------+---------
2011-07-12 | 94.3042
2011-07-13 | 12.9532
2011-07-14 | 74.7261
...............
它很快,因为当您这样调用它时,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))
解释从销售中选择*
其中销售额(日)>=5,销售额(日)<5+10;
查询计划
--------------------------------------------------------------------------
在销售中使用sales_by_pos进行索引扫描(成本=0.50..8.77行=1宽度=8)
指数条件:((销售额(日)>=5)和(销售额(日)<15))

希望能有帮助。

最近,我研究了一个类似这样的问题,并写了一篇关于如何面对这个问题的博客。很像,我希望对任何人都有帮助。 我使用部分附加的惰性列表方法。我将查询的限制和偏移或分页替换为手动分页。 在我的示例中,select返回1000万条记录,我获取它们并将它们插入“时态表”:

之后,我可以使用指定的顺序分页,而不必计算每一行:

select * from tmp_table where counterrow >= 9000000 and counterrow <= 9025000

select*from tmp_table,其中countrow>=9000000,countrow而不是使用偏移量,一个非常有效的技巧是使用临时表:

CREATE  TEMPORARY TABLE just_index AS
SELECT ROW_NUMBER() OVER (ORDER BY myID), myID
FROM mytable;
对于10000行,需要创建大约10秒。 然后,如果要使用选择或更新表,只需执行以下操作:

SELECT * FROM mytable INNER JOIN (SELECT just_index.myId FROM just_index WHERE row_number >= *your offset* LIMIT 1000000) indexes ON mytable.myID = indexes.myID
仅使用just_索引过滤mytable(在我的例子中)使用内部联接比使用WHERE myID in(选择…)更有效


这样,您就不必存储最后一个myId值,只需使用WHERE子句替换偏移量,该子句使用索引

它不一定知道它已经看到了什么,因为分页需要跳转到(比如)可能是特定于应用程序的第1000页。谷歌允许你向前跳转9页或向后跳转9页,但不允许你直接跳转到1000页。谷歌似乎还在URL中对起始项目编号进行编码,我想这可以用来减少结果集的大小和偏移量的大小。这种访问模式的一个常见例子是一个有数千篇帖子的论坛主题。用户跳转到偏移量0阅读原始帖子,然后一些大偏移量阅读最新回复,然后一些随机偏移量查看讨论中的兴趣点(如深度链接或对自己帖子的回复)。在@Tometzky中有一篇关于此技术的冗长且非常详细的博客帖子-非常好的主意!作为一种改进,我建议在分组列上使用窗口函数(仅9.0+版本)。太好了。所以,现在每次你们在表中插入一个值,它都会为表中的每一项重新计算这个值?@KonstantineRybnikov-Hmm。。不需要,但只要严格按照条目的日期顺序插入条目,并且从不删除条目,就不需要重新计算索引(无论如何,这是个好主意)。在这种情况下,记录位置永远不会改变。@MikeIvanov PostgreSql是否使用这种优化?(是否只对需要的部分进行了修改)当使用crosstab()函数时,这是一个非常好的解决方案。我的第一个查询(限制100,偏移量0)持续了14毫秒,但最后一个查询(限制100,偏移量14900)持续了将近3秒钟。用这个解决方案,我所有的
select * from tmp_table where counterrow >= 9000000 and counterrow <= 9025000
@Override
public E get(int index) {
  if (bufferParcial.size() <= (index - lastIndexRoulette))
  {
    lastIndexRoulette = index;
    bufferParcial.removeAll(bufferParcial);
    bufferParcial = new ArrayList<E>();
        bufferParcial.addAll(daoInterface.getBufferParcial());
    if (bufferParcial.isEmpty())
    {
        return null;
    }

  }
  return bufferParcial.get(index - lastIndexRoulette);<br>
}
CREATE  TEMPORARY TABLE just_index AS
SELECT ROW_NUMBER() OVER (ORDER BY myID), myID
FROM mytable;
SELECT * FROM mytable INNER JOIN (SELECT just_index.myId FROM just_index WHERE row_number >= *your offset* LIMIT 1000000) indexes ON mytable.myID = indexes.myID