防止在 Postgres 中为特定查询使用索引

Posted

技术标签:

【中文标题】防止在 Postgres 中为特定查询使用索引【英文标题】:Prevent usage of index for a particular query in Postgres 【发布时间】:2016-01-25 13:41:03 【问题描述】:

我在 Postgres 数据库中有一个缓慢的查询。使用explain analyze,我可以看到 Postgres 对两个不同的索引进行位图索引扫描,然后对两个结果集进行位图 AND。

删除其中一个索引会使评估速度提高十倍(位图索引扫描仍用于第一个索引)。但是,删除的索引在其他查询中很有用。

查询:

select
  booking_id
from
  booking
where
  substitute_confirmation_token is null
  and date_trunc('day', from_time) >= cast('01/25/2016 14:23:00.004' as date)
  and from_time >= '01/25/2016 14:23:00.004'
  and type = 'LESSON_SUBSTITUTE'
  and valid
order by
  booking_id;

索引:

"idx_booking_lesson_substitute_day" btree (date_trunc('day'::text, from_time)) WHERE valid AND type::text = 'LESSON_SUBSTITUTE'::text
"booking_substitute_confirmation_token_key" UNIQUE CONSTRAINT, btree (substitute_confirmation_token)

查询计划:

Sort  (cost=287.26..287.26 rows=1 width=8) (actual time=711.371..711.377 rows=44 loops=1)
  Sort Key: booking_id
  Sort Method: quicksort  Memory: 27kB
  Buffers: shared hit=8 read=7437 written=1
  ->  Bitmap Heap Scan on booking  (cost=275.25..287.25 rows=1 width=8) (actual time=711.255..711.294 rows=44 loops=1)
        Recheck Cond: ((date_trunc('day'::text, from_time) >= '2016-01-25'::date) AND valid AND ((type)::text = 'LESSON_SUBSTITUTE'::text) AND (substitute_confirmation_token IS NULL))
        Filter: (from_time >= '2016-01-25 14:23:00.004'::timestamp without time zone)
        Buffers: shared hit=5 read=7437 written=1
        ->  BitmapAnd  (cost=275.25..275.25 rows=3 width=0) (actual time=711.224..711.224 rows=0 loops=1)
              Buffers: shared hit=5 read=7433 written=1
              ->  Bitmap Index Scan on idx_booking_lesson_substitute_day  (cost=0.00..20.50 rows=594 width=0) (actual time=0.080..0.080 rows=72 loops=1)
                    Index Cond: (date_trunc('day'::text, from_time) >= '2016-01-25'::date)
                    Buffers: shared hit=5 read=1
              ->  Bitmap Index Scan on booking_substitute_confirmation_token_key  (cost=0.00..254.50 rows=13594 width=0) (actual time=711.102..711.102 rows=2718734 loops=1)
                    Index Cond: (substitute_confirmation_token IS NULL)
                    Buffers: shared read=7432 written=1
Total runtime: 711.436 ms

我可以防止在 Postgres 中为特定查询使用特定索引吗?

【问题讨论】:

显示查询,显示解释。您不能禁用索引,但您可以对查询进行足够的更改,使其不会使用索引。 @jakub-kania 我想知道是否可以为特定查询禁用索引。你已经给我答案了。无论如何,我更新了我的问题并添加了查询、索引描述和查询计划。 我认为booking_substitute_confirmation_token_key 是您要“禁用”的那个? @jakub-kania 是的。顺便提一句。我只是尝试用create unique index booking_substitute_confirmation_unique on booking(substitute_confirmation_token) where substitute_confirmation_token is not null 替换索引,这样可以将评估时间从 711 加快到 10 毫秒。 好吧,我最终用唯一索引替换了唯一约束(和相关的默认索引booking_substitute_confirmation_token_key),如我上面的评论所示。这对我来说似乎是一个完美的解决方案。此唯一索引不包括索引属性具有空值的行。该表有几百万行和几千行非空值。 【参考方案1】:

您的巧妙解决方案

您已经为您的特定情况找到了一个聪明的解决方案:仅涵盖稀有值的部分唯一索引,因此 Postgres 不会(不能)将索引用于常见的 NULL 值。

CREATE UNIQUE INDEX booking_substitute_confirmation_uni
ON booking (substitute_confirmation_token)
WHERE substitute_confirmation_token IS NOT NULL;

这是部分索引的教科书用例。 Literally! 该手册有一个类似的示例,并且这些完美匹配的建议可以与之搭配:

最后,部分索引也可以用来覆盖系统的 查询计划选择。此外,具有特殊分布的数据集可能 导致系统在确实不应该使用索引时使用它。在那里面 如果可以设置索引,使其不适用于 有问题的查询。通常,PostgreSQL 会做出合理的选择 索引使用(例如,它在检索公共值时避免使用它们,所以 前面的例子实际上只是节省了索引大小,它不是必需的 以避免使用索引),并且严重错误的计划选择是原因 获取错误报告。

请记住,设置部分索引表示您知道 至少与查询计划者知道的一样多,尤其是您知道的 当一个指数可能盈利时。形成这些知识需要 对 PostgreSQL 中的索引如何工作的经验和理解。在 大多数情况下,部分索引相对于常规索引的优势将 最小化。

您评论:The table has few millions of rows and just few thousands of rows with not null values,所以这是一个完美的用例。它甚至可以加快对substitute_confirmation_token 的非空值的查询,因为索引现在小了很多

回答问题

回答您最初的问题:不可能为特定查询“禁用”现有索引。你必须放弃它,但这太贵了。

假滴指数

可以在事务中删除索引,运行您的SELECT,然后使用ROLLBACK 而不是提交。这,但请注意 (per documentation):

一个普通的DROP INDEX 在表上获得排他锁,阻塞 可以完成索引删除之前的其他访问。

所以这对多用户环境没有好处。

BEGIN;
DROP INDEX big_user_id_created_at_idx;
SELECT ...;
ROLLBACK;  -- so the index is preserved after all

更详细的统计数据

不过,通常情况下,提高列的STATISTICS 目标就足够了,因此 Postgres 可以更可靠地识别常见值并避免为这些值建立索引。试试:

ALTER TABLE booking ALTER COLUMN substitute_confirmation_token SET STATISTICS 2000;

然后:ANALYZE booking;,然后再尝试查询。 2000 是一个示例值。相关:

Keep PostgreSQL from sometimes choosing a bad query plan

【讨论】:

以上是关于防止在 Postgres 中为特定查询使用索引的主要内容,如果未能解决你的问题,请参考以下文章

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

如何使用内部连接查询(Postgres)防止重复

为啥 postgres 没有在我的查询中使用索引

Postgres 在几乎每个查询中都使用 primary_key 索引

Postgres维护的正确顺序

如何使用 API 在 Elasticsearch Kibana 中为特定租户创建索引模式?