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

Cim*_*man 6 sql postgresql indexing query-optimization postgresql-performance

我在 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;
Run Code Online (Sandbox Code Playgroud)

索引:

"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)
Run Code Online (Sandbox Code Playgroud)

查询计划:

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
Run Code Online (Sandbox Code Playgroud)

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

Erw*_*ter 5

您聪明的解决方案

对于您的特定情况,部分唯一索引仅涵盖稀有值,因此 Postgres 不会(不能)使用常见NULL值的索引。

CREATE UNIQUE INDEX booking_substitute_confirmation_uni
ON booking (substitute_confirmation_token)
WHERE substitute_confirmation_token IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)

这是部分索引的教科书用例。字面上地!该手册有一个类似的示例以及与之完美匹配的建议:

最后,部分索引还可以用于覆盖系统的查询计划选择。此外,具有特殊分布的数据集可能会导致系统在实际上不应该使用索引的情况下使用索引。在这种情况下,可以设置索引,使其不可用于有问题的查询。通常,PostgreSQL 对索引的使用做出合理的选择(例如,它在检索通用值时避免使用它们,因此前面的示例实际上只节省了索引大小,不需要避免索引的使用),并且严重错误的计划选择会导致错误报告。

请记住,设置部分索引表明您至少知道查询计划者知道的一样多,特别是您知道索引何时可能有利可图。形成这些知识需要经验并了解 PostgreSQL 中索引的工作原理。在大多数情况下,部分索引相对于常规索引的优势很小。在某些情况下,它们会适得其反[...]

您评论道:

该表有几百万行,只有几千行不包含空值。

所以这是一个完美的用例。它甚至会加速对非空值的查询,因为substitute_confirmation_token索引现在小得多

回答问题

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

假掉落指数

可以在事务中删除索引,运行您的SELECT索引,然后使用ROLLBACK. 这很快,但请注意(引用手册):

普通DROP INDEX获取表上的排他锁,阻止其他访问,直到索引删除完成。

因此这不利于多用户环境中的常规使用。

BEGIN;
DROP INDEX big_user_id_created_at_idx;
SELECT ...;
ROLLBACK;  -- so the index is preserved after all
Run Code Online (Sandbox Code Playgroud)

看:

更详细的统计数据

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

ALTER TABLE booking ALTER COLUMN substitute_confirmation_token SET STATISTICS 1000;
Run Code Online (Sandbox Code Playgroud)

然后:ANALYZE booking;在您再次尝试查询之前。1000 是一个示例值。有关的: