如果要坚持进行顺序扫描,如何强制Postgres使用索引?
答案 0 :(得分:84)
假设您在询问许多数据库中常见的“索引提示”功能,PostgreSQL没有提供这样的功能。这是PostgreSQL团队做出的有意识的决定。可以找到一个很好的概述,为什么以及你可以做什么,here。原因基本上是它是一个性能黑客,随着数据的变化,往往会导致更多的问题,而PostgreSQL的优化器可以根据统计数据重新评估计划。换句话说,今天可能是一个好的查询计划可能不会是一个好的查询计划,索引提示会强制执行特定的查询计划。
作为一种非常钝的锤子,对测试很有用,您可以使用enable_seqscan
和enable_indexscan
参数。参见:
这些不适合正在进行的生产用途。如果您在选择查询计划时遇到问题,则应该看到the documentation for tracking down query performance issues。不要只设置enable_
参数并离开。
除非你有充分的理由使用索引,否则Postgres可能会做出正确的选择。为什么呢?
答案 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倍以上。
答案 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)
索引只能在特定情况下使用。
给定一个包含 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.
这对避免此类问题大有帮助。