如何强制 Postgres 使用特定索引?
Posted
技术标签:
【中文标题】如何强制 Postgres 使用特定索引?【英文标题】:How do I force Postgres to use a particular index? 【发布时间】:2010-09-23 12:18:36 【问题描述】:当 Postgres 坚持执行顺序扫描时,我如何强制它使用索引?
【问题讨论】:
重复,见***.com/questions/14554302/… +1 我很想看到这个功能。正如其他答案所说,这不是简单地禁用 seq 扫描的问题:我们需要能够强制 PG 使用特定索引。这是因为实际上 stats 可以是completely wrong,此时您需要使用不可靠/部分解决方法。我同意在简单的情况下,您应该首先检查索引和其他设置,但是为了大数据的可靠性和高级用途,我们需要这样做。 mysql 和 Oracle 都有……不知道为什么 Postgres 的规划器如此不可靠。 【参考方案1】:假设您询问的是在许多数据库中发现的常见“索引提示”功能,PostgreSQL 不提供这样的功能。这是 PostgreSQL 团队有意识的决定。可以在here 找到一个很好的概述,了解为什么以及您可以做什么。原因基本上是它是一种性能黑客,随着数据的变化,它往往会导致更多的问题,而 PostgreSQL 的优化器可以根据统计数据重新评估计划。换句话说,今天可能是一个好的查询计划可能不会一直是一个好的查询计划,并且索引提示会一直强制执行特定的查询计划。
作为一个非常钝的锤子,对测试很有用,您可以使用enable_seqscan
和enable_indexscan
参数。见:
enable_
parameters
这些不适合正在进行的生产使用。如果您对查询计划选择有疑问,您应该看到the documentation for tracking down query performance issues。不要只是设置enable_
参数然后走开。
除非您有充分的理由使用索引,否则 Postgres 可能会做出正确的选择。为什么?
对于小型表,执行顺序扫描会更快。 当数据类型不正确匹配时,Postgres 不使用索引,您可能需要包含适当的类型转换。 您的规划器设置可能会导致问题。另见this old newsgroup post。
【讨论】:
同意,强迫 postgres 以你的方式去做通常意味着你做错了。 9/10 次,计划者会击败你能想到的任何事情。另外 1 次是因为你做错了。 我认为检查索引持有的真正运算符类是个好主意。 我讨厌重提一个老问题,但我经常在 Postgres 文档、讨论和这里看到,但是对于 small table 的条件是否有一个概括的概念?是 5000 行还是 50000 行之类的? @waffl 你考虑过基准测试吗?创建一个带有索引和附带函数的简单表,用 n 行随机垃圾填充它。然后开始查看 n 的不同值的查询计划。当您看到它开始使用索引时,您应该有一个大概的答案。如果 PostgreSQL 确定(基于统计数据)索引扫描也不会消除很多行,您还可以获得顺序扫描。因此,当您有真正的性能问题时,基准测试总是一个好主意。作为一个临时的轶事猜测,我会说几千通常是“小”。 在 Oracle、Teradata 和 MSSQL 等平台上拥有超过 30 年的经验,我发现 PostgreSQL 10 的优化器并不是特别聪明。即使使用最新的统计数据,它生成的执行计划效率也低于强制执行特定方向的计划。提供结构提示来弥补这些问题将提供一个解决方案,让 PostgreSQL 在更多的细分市场中成长。恕我直言。【参考方案2】:可能是使用的唯一正当理由
set enable_seqscan=false
当您正在编写查询并希望快速查看查询计划实际上是什么时,如果表中有大量数据。或者当然,如果您需要快速确认您的查询没有使用索引,仅仅是因为数据集太小。
【讨论】:
这个简短的回复实际上为测试目的提供了一个很好的提示 没人回答这个问题! @IvailoBardarov 所有这些其他建议都在这里的原因是因为 PostgreSQL 没有这个特性;这是开发人员根据它的典型使用方式和它导致的长期问题做出的有意识的决定。 @BrianHellekin 更好,SET SESSION enable_seqscan=false
只影响你自己
SESSION 是默认的,所以它相当于设置 enable_seqscan=false【参考方案3】:
TL;DR
运行以下三个命令,查看问题是否解决:
ANALYZE;
SET random_page_cost = 1.0;
SET effective_cache_size = 'X GB'; # replace X with total RAM size minus 2 GB
继续阅读以了解更多详细信息和背景信息。
第 1 步:分析表格
作为解决问题的简单第一次尝试,以数据库超级用户身份运行ANALYZE;
命令以更新所有表统计信息。来自documentation:
查询计划器使用这些统计信息来帮助确定最有效的查询执行计划。
第 2 步:设置正确的随机页面费用
索引扫描需要非顺序的磁盘页面获取。 PostgreSQL 使用random_page_cost
配置参数来估计这种非顺序提取相对于顺序提取的成本。来自documentation:
减少这个值 [...] 将导致系统更喜欢索引扫描;提高它会使索引扫描看起来相对更昂贵。
默认值为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 通常可用于数据缓存的内存量。来自documentation:
值越大,使用索引扫描的可能性越大,值越低,使用顺序扫描的可能性越大。
请注意,此参数不会更改 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 ...
永久设置新的配置参数值(全局或每个数据库)。参数设置详见documentation。
第 5 步:其他资源
如果还是不行,那么你可能还想看看this PostgreSQL Wiki page about server tuning。
【讨论】:
我什至不得不设置 random_page_cost = 0.1 以使索引扫描在 Ubuntu 的 Pg 10.1 中的大型(~600M 行表)上工作。如果没有调整,seq 扫描(尽管是并行的)需要 12 分钟(请注意,执行了分析表!)。驱动器是SSD。调整后,执行时间变为 1 秒。 你拯救了我的一天。我疯狂地试图弄清楚在同一数据库上完全相同的查询如何在一台机器上花费 30 秒而在另一台机器上花费不到 1 秒,即使在两端都运行了分析之后......它可能关心的人:命令' ALTER SYSTEM SET random_page_cost=x' 全局设置新的默认值。【参考方案4】:有时 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
【讨论】:
好主意。但是,当我们使用此方法禁用当前索引使用时 - postgresql 查询优化器会回退到下一个合适的索引。因此,不能保证优化器会选择your_wanted_index
,它可以让postgresql引擎只执行序列/主键扫描。结论 - 没有 100% 可靠的方法来强制 PostgreSql 服务器使用某些索引。
如果没有where
条件但是有两个表或者join,而Postgres无法获取索引怎么办。
@Surya 以上适用于 WHERE 和 JOIN ... ON 条件【参考方案5】:
这个问题本身是非常无效的。强制(例如通过 enable_seqscan=off)是非常糟糕的主意。检查它是否会更快可能很有用,但生产代码不应该使用这样的技巧。
相反 - 对您的查询进行解释分析,阅读它,并找出 PostgreSQL 选择错误(在您看来)计划的原因。
网络上有一些工具可以帮助阅读解释分析输出 - 其中之一是 explain.depesz.com - 由我编写。
另一种选择是加入freenode irc 网络上的#postgresql 频道,并与那里的人交谈以帮助您 - 因为优化查询不是“提出问题,得到答案,开心”的问题。这更像是一场对话,有很多东西要检查,很多东西要学习。
【讨论】:
【参考方案6】:使用 PostgreSQL 需要注意的一点;您期望使用索引但未使用索引的地方是 VACUUM ANALYZE 表。
VACUUM ANALYZE schema.table;
这会更新计划程序使用的统计信息,以确定执行查询的最有效方式。这可能会导致索引被使用。
【讨论】:
这是最好的答案,或者至少是在不使用索引时尝试的第一件事。【参考方案7】:有一个技巧可以让 postgres 更喜欢 seqscan 在子查询中添加 OFFSET 0
当您只需要 n 个第一个/最后一个元素时,这对于优化链接大/巨大表的请求非常方便。
假设您正在寻找第一个/最后 20 个元素,这些元素涉及具有 100k(或更多)条目的多个表,当您要查找的内容是第一个时,没有必要构建/链接所有数据的所有查询100 或 1000 个条目。例如,在这种情况下,执行顺序扫描的速度要快 10 倍以上。
见How can I prevent Postgres from inlining a subquery?
【讨论】:
不错的把戏。虽然一个好的优化器当然应该优化掉偏移量 0 :-)【参考方案8】:索引只能在特定情况下使用。
-
例如,值的类型适合列的类型。
在与值进行比较之前,您没有对列进行操作。
给定一个包含 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.
这将有助于避免此类问题。
【讨论】:
【参考方案9】:显然在某些情况下,可以通过重复两次类似条件来提示 Postgre 使用索引。
我观察到的具体情况是使用 PostGIS gin
索引和 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 倍。
【讨论】:
以上是关于如何强制 Postgres 使用特定索引?的主要内容,如果未能解决你的问题,请参考以下文章