如何强制Postgres使用特定索引?


111

如果Postgres坚持执行顺序扫描,该如何强制它使用索引?



1
+1我很想看看这个功能。就像其他答案所说的那样,这不仅仅是简单地禁用seq scan的问题:我们需要能够强制PG使用特定索引。这是因为实际上,统计信息可能是完全错误的,此时您需要使用不可靠/部分的解决方法。我同意,在简单的情况下,您应该首先检查索引和其他设置,但是对于大数据的可靠性和高级用途,我们需要这样做。
collimarco

MySQL和Oracle都有...不确定Postgres的计划器为何如此不可靠。
凯文·帕克

Answers:


103

假设您要查询许多数据库中常见的“索引提示”功能,PostgreSQL没有提供这种功能。这是PostgreSQL团队做出的明智决定。在这里可以找到关于为什么以及可以做什么的很好的概述。原因基本上是,这是一个性能黑客,随着数据的变化,它会在以后引起更多的问题,而PostgreSQL的优化器可以根据统计信息对计划进行重新评估。换句话说,今天可能是一个好的查询计划,可能永远不会成为一个好的查询计划,并且索引提示会一直强迫一个特定的查询计划。

您可以使用enable_seqscanenable_indexscan参数,这是一个非常钝的锤子,可用于测试。看到:

这些不适合正在进行的生产使用。如果您在查询计划选择方面遇到问题,则应查看有关跟踪查询性能问题的文档。不要只是设置enable_参数然后走开。

除非您有很好的理由使用索引,否则Postgres可能会做出正确的选择。为什么?

  • 对于小型表,顺序扫描更快。
  • 当数据类型不正确匹配时,Postgres不使用索引,您可能需要包括适当的转换。
  • 您的计划程序设置可能会引起问题。

另请参阅此旧新闻组帖子


4
同意,强迫postgres按自己的方式做通常意味着您做错了。9/10次的计划者将击败您能想到的一切。其他1次是因为您输入错误。
肯特·弗雷德里克

我认为这是检查您的索引持有人的真正运营商类的一个好主意。
metdos 2012年

2
我讨厌重提一个古老的问题,但是我经常在Postgres文档,讨论和这里看到,但是对于什么适合一张小桌子,是否有一个广义的概念?是5000行还是50000等?
waffl

1
@waffl您是否考虑过基准测试?创建一个带有索引和附带函数的简单表,用n行随机垃圾填充该表。然后开始查看查询计划中不同的n值。当您看到它开始使用索引时,您应该有一个大致的答案。如果PostgreSQL确定(基于统计信息)索引扫描也不会消除很多行,那么您还可以获得顺序扫描。因此,当您真正关心性能时,基准测试始终是一个好主意。作为附带的,偶然的猜测,我想说通常有几千个“小”。
2014年

9
凭借在Oracle,Teradata和MSSQL等平台上超过30年的经验,我发现PostgreSQL 10的优化器并不是特别聪明。即使使用最新的统计信息,它生成的执行计划也比强制执行特定方向的效率低。提供结构性提示以补偿这些问题将提供一种解决方案,以允许PostgreSQL在更多的细分市场中发展。恕我直言。
Guido Leenders

75

可能是使用的唯一有效理由

set enable_seqscan=false

是在编写查询并希望快速查看表中有大量数据时查询计划的实际情况。或者,当然,如果您只是由于数据集太小而需要快速确认查询未使用索引的话。


41
这个简短的答复实际上为测试目的提供了很好的提示
dwery 2014年

3
没有人在回答这个问题!
Ivailo Bardarov

@IvailoBardarov这些其他建议都在这里的原因是因为PostgreSQL没有此功能。这是开发人员根据其通常的用法以及所引起的长期问题做出的有意识的决定。
2014年

一个不错的测试技巧:运行set enable_seqscan=false,运行查询,然后快速运行set enable_seqscan=true以使postgresql返回其正确的行为(并且显然不在生产中执行此操作,而仅在开发中!)
Brian Hellekin 18/02/23

2
@BrianHellekin更好,SET SESSION enable_seqscan=false只影响自己
Izkata

19

有时PostgreSQL无法针对特定条件对索引进行最佳选择。例如,假设有一个包含几百万行的事务处理表,其中任意一天有数百行,并且该表具有四个索引:transaction_id,client_id,date和description。您要运行以下查询:

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description = 'Refund'
GROUP BY client_id

PostgreSQL可能选择使用索引transactions_description_idx而不是transactions_date_idx,这可能导致查询花费几分钟而不是不到一秒钟的时间。在这种情况下,您可以通过以下条件强制使用日期索引:

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description||'' = 'Refund'
GROUP BY client_id

3
好主意。但是,当我们使用此方法禁用当前索引使用时-Postgresql查询优化器会回退到下一个合适的索引。因此,不能保证优化程序会选择your_wanted_index,而是可以使Postgresql引擎仅执行序列/主键扫描。结论-没有100%可靠的方法强制对PostgreSql服务器使用某些索引。
Agnius Vasiliauskas

如果没有where条件但只有两个表或已联接并且Postgres无法获取索引该怎么办。
露娜·洛夫古德

@Surya以上适用于WHERE和JOIN ... ON条件
Ziggy Crueltyfree Zeitgeister 19/12/3

18

简短答案

当索引扫描的估计成本太高并且不能正确反映现实时,通常会发生此问题。您可能需要降低random_page_cost配置参数来解决此问题。从Postgres文档中

降低此值将使系统偏向于索引扫描;提高它会使索引扫描看起来相对更昂贵。

您可以检查一个较小的值是否实际上会使Postgres使用索引(但将其用于测试):

EXPLAIN <query>;              # Uses sequential scan
SET random_page_cost = 1;
EXPLAIN <query>;              # May use index scan now

您可以SET random_page_cost = DEFAULT;再次使用恢复默认值。

背景

索引扫描需要非顺序磁盘页读取。Postgres用于random_page_cost估计与顺序获取有关的此类非顺序获取的成本。默认值为4.0,因此假设与顺序读取相比,平均成本因子为4(考虑了缓存效果)。

但是,问题在于此默认值不适用于以下重要的实际场景:

1)固态驱动器

正如文档所承认的:

相对于顺序驱动(例如固态驱动器)而言,具有较低随机读取成本的存储可以用较低的值更好地建模random_page_cost

根据最后一点这张幻灯片从谈一谈PostgresConf 2018年,random_page_cost应设置之间的事情1.02.0固态驱动器。

2)缓存的数据

如果所需的索引数据已经缓存在RAM中,则索引扫描将始终比顺序扫描快得多。该文档说:

相应地,如果您的数据可能完全在高速缓存中,则减少数据丢失random_page_cost是适当的。

问题是,您当然无法轻松知道相关数据是否已被缓存。但是,如果经常查询特定索引,并且系统具有足够的RAM,则可能会缓存数据,因此random_page_cost应将其设置为较低的值。您必须尝试不同的值,然后看看哪种方法对您有用。

您可能还希望将pg_prewarm扩展用于显式数据缓存。



2
我什至必须设置random_page_cost = 0.1才能在Ubuntu 10.1上的大型(〜600M行表)上进行索引扫描。没有调整,seq扫描(尽管是并行的)要花费12分钟(请注意,执行了分析表!)。驱动器是SSD。调整之后,执行时间变为1秒。
Anatoly Alekseev,

你救了我的日子。我发疯了,试图弄清楚即使在两端运行了分析之后,在一台计算机上对同一数据库进行完全相同的查询如何花费30秒,而在另一台计算机上花费不到1秒……对于它可能涉及的人:命令' ALTER SYSTEM SET random_page_cost = x'全局设置新的默认值。
朱利安

10

这个问题本身是非常无效的。强制执行(例如,通过启用enable_seqscan = off)是个坏主意。检查它是否会更快会很有用,但是生产代码永远不要使用这种技巧。

取而代之的是-解释一下您的查询,阅读它,然后找出为什么PostgreSQL选择错误的计划(在您看来)。

网络上有一些工具可以帮助阅读解释分析输出-其中之一是我编写的explain.depesz.com

另一种选择是在freenode irc网络上加入#postgresql频道,并与那里的人聊天以帮助您-因为优化查询不是“问一个问题,得到一个快乐的答案”的问题。它更像是一次对话,需要检查很多事情,需要学习很多事情。


2

有一个技巧可以使postgres倾向于OFFSET 0在子查询中添加seqscan 的方法

当您只需要n个first / last元素时,这对于优化链接大型/大型表的请求非常方便。

假设您要查找的前/后20个元素涉及多个具有100k(或更多)条目的表,当要查找的数据位于前100个或1000个中时,没有点建立/链接所有数据上的所有查询条目。例如,在这种情况下,进行顺序扫描的速度要快10倍以上。

请参阅如何防止Postgres内联子查询?


好招 尽管一个好的优化程序当然应该优化偏移量0 :-)
Guido Leenders 18-10-19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.