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

时间:2008-11-21 18:56:58

标签: sql postgresql indexing

如果要坚持进行顺序扫描,如何强制Postgres使用索引?

10 个答案:

答案 0 :(得分:84)

假设您在询问许多数据库中常见的“索引提示”功能,PostgreSQL没有提供这样的功能。这是PostgreSQL团队做出的有意识的决定。可以找到一个很好的概述,为什么以及你可以做什么,here。原因基本上是它是一个性能黑客,随着数据的变化,往往会导致更多的问题,而PostgreSQL的优化器可以根据统计数据重新评估计划。换句话说,今天可能是一个好的查询计划可能不会是一个好的查询计划,索引提示会强制执行特定的查询计划。

作为一种非常钝的锤子,对测试很有用,您可以使用enable_seqscanenable_indexscan参数。参见:

这些不适合正在进行的生产用途。如果您在选择查询计划时遇到问题,则应该看到the documentation for tracking down query performance issues。不要只设置enable_参数并离开。

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

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

另见this old newsgroup post

答案 1 :(得分:59)

可能是使用

的唯一正当理由
set enable_seqscan=false

是在您编写查询时,想要快速查看查询计划实际上是什么,表中有大量数据。或者当然,如果您因为数据集太小而需要快速确认您的查询未使用索引。

答案 2 :(得分:11)

有时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 :(得分:10)

问题本身就非常无效。强制(通过执行enable_seqscan = off)是非常糟糕的主意。检查它是否会更快可能会有用,但生产代码永远不应该使用这些技巧。

相反 - 请解释您的查询分析,阅读它,并找出PostgreSQL选择不良(在您看来)计划的原因。

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

另一个选择是加入freenode irc网络上的#postgresql频道,并与那里的人交谈以帮助你 - 因为优化查询不是“问一个问题,让答案快乐”的问题。它更像是一次谈话,需要检查很多事情,还有很多东西需要学习。

答案 4 :(得分:4)

简短回答

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

  

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

您可以检查一个较小的值是否实际上会使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较低的值来建模。

根据this slide在PostgresConf 2018上的演讲,对于固态硬盘,random_page_cost应该设置为2.0或更低。

2)大量缓存的数据

  

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

如果您知道索引已完全缓存到RAM中(您可能还想使用pg_prewarm扩展名),则random_page_cost甚至应设置为1.0


答案 5 :(得分:1)

有一个技巧可以推动postgres更喜欢在子查询中添加OFFSET 0的seqscan

当您只需要n个拳头/最后一个元素时,这对于优化连接大/大表的请求非常方便。

让我们假设您正在寻找包含100k(或更多)条目的多个表的第一个/最后20个元素,当您要查找的内容中没有任何点构建/链接所有数据的所有查询前100或1000个条目。例如,在这种情况下,进行顺序扫描的速度要快10倍以上。

请参阅How can I prevent Postgres from inlining a subquery?

答案 6 :(得分:1)

使用 PostgreSQL 需要注意的一件事;在您期望使用索引但未使用的情况下,对表进行 VACUUM ANALYZE。

VACUUM ANALYZE schema.table;

这会更新规划器使用的统计信息,以确定执行查询的最有效方式。这可能会导致索引被使用。

答案 7 :(得分:0)

EnterpriseDB的PostgresPlus Advanced Server产品支持Oracle提示语法,但该产品不是免费的。

答案 8 :(得分:0)

显然,在某些情况下,可以通过重复两次类似的条件来暗示 Postgre 使用索引。

我观察到的具体情况是使用 PostGIS CustomerRoute.post('/data1', (req, res) => { // var rc = req.headers.cookie; const { cookies } = req; console.log(cookies); if ('session_id' in cookies) { console.log('Session Id exists'); var points = JSON.parse(cookies); //console.log(cookies['id']); console.log(points); } res.apiSuccess(); 索引和 ST_Within 谓词,如下所示:

SwiftUI.Circle()

请注意,第一个谓词 gin 会被 PostGIS 自动分解为 select * from address natural join city natural join restaurant where st_within(address.location, restaurant.delivery_area) and restaurant.delivery_area ~ address.location ,因此添加第二个谓词 st_within(address.location, restaurant.delivery_area) 是完全多余的。尽管如此,第二个谓词说服规划器在 (restaurant.delivery_area ~ address.location) AND _st_contains(restaurant.delivery_area, address.location) 上使用空间索引,并且在我需要的特定情况下,将运行时间提高了 8 倍。

答案 9 :(得分:0)

索引只能在特定情况下使用。

  1. 例如,值的类型适合列的类型。
  2. 在与值进行比较之前,您没有对列进行操作。

给定一个包含 3 列的客户表,所有列都有 3 个索引。

create table customer(id numeric(10), age int, phone varchar(200))

数据库可能会尝试使用例如索引 idx_age 而不是电话号码。

你可以通过做age的操作来破坏索引age的使用:

 select * from customer where phone = '1235' and age+1 = 24 

(虽然你找的是23岁)

这当然是一个非常简单的例子,postgres 的智能可能足以做出正确的选择。但有时除了欺骗系统之外别无他法。

另一个例子是

select * from customer where phone = '1235' and age::varchar = '23'

但这可能比上面的选项成本更高。

很遗憾,您无法像在 MSSQL 或 Sybase 中那样将索引名称设置到查询中。

select * from customer (index idx_phone) where phone = '1235' and age = 23.

这对避免此类问题大有帮助。