146

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

4

9 回答 9

124

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

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

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

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

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

另请参阅此旧新闻组帖子

于 2008-11-21T19:04:03.803 回答
97

可能是使用的唯一正当理由

set enable_seqscan=false

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

于 2012-11-16T01:33:06.460 回答
37

TL;博士

运行以下三个命令,查看问题是否解决:

ANALYZE;
SET random_page_cost = 1.0;
SET effective_cache_size = 'X GB';    # replace X with total RAM size minus 2 GB

请继续阅读以了解更多详细信息和背景信息。

第 1 步:分析表

作为解决问题的简单第一次尝试,以ANALYZE;数据库超级用户身份运行命令以更新所有表统计信息。从文档中:

查询计划器使用这些统计信息来帮助确定最有效的查询执行计划。

第 2 步:设置正确的随机页面成本

索引扫描需要非顺序的磁盘页面获取。PostgreSQL 使用random_page_cost配置参数来估计这种非顺序提取相对于顺序提取的成本。从文档中:

减少这个值 [...] 将导致系统更喜欢索引扫描;提高它会使索引扫描看起来相对更昂贵。

默认值为4.0,因此假设与顺序提取相比,平均成本因子为 4,同时考虑到缓存效果。但是,如果您的数据库存储在SSD 驱动器上,那么您应该根据文档实际设置random_page_cost为:1.1

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

此外,如果索引大部分(甚至完全)缓存在 RAM 中,那么索引扫描将始终磁盘服务的顺序扫描快得多。然而,查询规划器不知道索引的哪些部分已经被缓存,因此可能会做出错误的决定。

如果您的数据库索引被频繁使用,并且系统有足够的 RAM,那么索引最终可能会被缓存。在这种情况下,random_page_cost可以将其设置为1.0,甚至设置为以下值,1.0以积极地倾向于使用索引扫描(尽管文档建议不要这样做)。您将不得不尝试不同的值,看看哪些对您有用。

作为旁注,您还可以考虑使用pg_prewarm扩展将索引显式缓存到 RAM 中。

你可以random_page_cost这样设置:

SET random_page_cost = 1.0;

步骤 3:设置正确的缓存大小

在具有 8 GB 或更多 GB RAM 的系统上,您应该将effective_cache_size配置参数设置为 PostgreSQL 通常可用于数据缓存的内存量。从文档中:

较高的值使其更有可能使用索引扫描,较低的值使其更有可能使用顺序扫描。

请注意,此参数不会更改 PostgreSQL 实际分配的内存量,而仅用于计算成本估算。一个合理的值(至少在专用数据库服务器上)是总 RAM 大小减去 2 GB。默认值为4 GB

你可以effective_cache_size这样设置:

SET effective_cache_size = '14 GB';   # e.g. on a dedicated server with 16 GB RAM

第 4 步:永久修复问题

您可能希望永久使用ALTER SYSTEM SET ...ALTER DATABASE db_name SET ...设置新的配置参数值(全局或每个数据库)。有关设置参数的详细信息,请参阅文档

第 5 步:其他资源

如果它仍然不起作用,那么您可能还想看看这个关于服务器调优的 PostgreSQL Wiki 页面

于 2018-10-16T10:31:16.600 回答
25

有时 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
于 2015-06-16T05:17:16.040 回答
11

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

相反 - 对您的查询进行解释分析,阅读它,并找出 PostgreSQL 选择错误(在您看来)计划的原因。

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

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

于 2009-07-09T17:58:04.820 回答
4

PostgreSQL 需要注意的一件事;您期望使用索引但未使用索引的地方是 VACUUM ANALYZE 表。

VACUUM ANALYZE schema.table;

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

于 2021-07-08T17:12:57.463 回答
2

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

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

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

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

于 2017-10-26T21:26:53.370 回答
1

索引只能在某些情况下使用。

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

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

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

例如,数据库可能会尝试使用索引 idx_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.

这将有助于避免此类问题。

于 2021-06-08T07:13:14.740 回答
0

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

我观察到的具体情况是使用 PostGISgin索引和ST_Within谓词,如下所示:

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)由 PostGIS 自动分解为,(restaurant.delivery_area ~ address.location) AND _st_contains(restaurant.delivery_area, address.location)因此添加第二个谓词restaurant.delivery_area ~ address.location是完全多余的。尽管如此,第二个谓词说服了计划者address.location在我需要的特定情况下使用空间索引,将运行时间提高了 8 倍。

于 2021-03-02T17:48:19.697 回答