如何强制 PostgreSQL 使用某个索引?

Posted

技术标签:

【中文标题】如何强制 PostgreSQL 使用某个索引?【英文标题】:How can I force PostgreSQL to use a certain index? 【发布时间】:2019-06-13 17:22:40 【问题描述】:

我的表 cache 上有两个 Postgres 索引,都在字段 dateconditionjsonb 列上。

第一个处理一个不可变函数,它接受文本字段并将其转换为date 类型。

第二个仅在text上创建。

所以,当我尝试第二个时,它会将我的 btree 索引转换为位图索引,并且不知何故比第一个慢,这需要另外两个步骤才能工作,但只使用索引扫描。

我有两个问题:为什么和如何?

为什么第一个只使用索引,而第二个由于某种原因使用位图?以及如何强制 PostgreSQL 在第二个索引上仅使用索引而不使用位图,因为我不想使用该函数。

如果有其他解决方案,请给我提示,因为我没有权限在服务器上安装软件包。

功能索引:

create index cache_ymd_index on cache (
   to_yyyymmdd_date(((data -> 'Info'::text) ->> 'Date'::text)::character varying),
   ((data -> 'Info'::text) ->> 'Condition'::text)
)  where (((data -> 'Info'::text) ->> 'Condition'::text) = '3'::text);

文字索引:

create index cache_data_index on cache (
   ((data -> 'Info'::text) ->> 'Date'::text),
   ((data -> 'Info'::text) ->> 'Condition'::text)
) where (((data -> 'Info'::text) ->> 'Condition'::text) = '3'::text);

函数本身:

create or replace function to_yyyymmdd_date(the_date character varying) returns date
    immutable language sql
as
$$
select to_date(the_date, 'YYYY-MM-DD')
$$;

分析函数索引的条件:

Index Scan using cache_ymd_index on cache  (cost=0.29..1422.43 rows=364 width=585) (actual time=0.065..66.842 rows=71634 loops=1)
  Index Cond: ((to_yyyymmdd_date((((data -> 'Info'::text) ->> 'Date'::text))::character varying) >= '2018-01-01'::date) AND (to_yyyymmdd_date((((data -> 'Info'::text) ->> 'Date'::text))::character varying) <= '2020-12-01'::date))
Planning Time: 0.917 ms
Execution Time: 70.464 ms

分析文本索引的条件:

Bitmap Heap Scan on cache  (cost=12.15..1387.51 rows=364 width=585)    (actual time=53.794..87.802 rows=71634 loops=1)
  Recheck Cond: ((((data -> 'Info'::text) ->> 'Date'::text) >= '2018-01-01'::text) AND (((data -> 'Info'::text) ->> 'Date'::text) <= '2020-12-01'::text) AND (((data -> 'Info'::text) ->> 'Condition'::text) = '3'::text))
  Heap Blocks: exact=16465
  ->  Bitmap Index Scan on cache_data_index  (cost=0.00..12.06 rows=364 width=0) (actual time=51.216..51.216 rows=71634 loops=1)
        Index Cond: ((((data -> 'Info'::text) ->> 'Date'::text) >= '2018-01-01'::text) AND (((data -> 'Info'::text) ->> 'Date'::text) <= '2020-12-01'::text))
Planning Time: 0.247 ms
Execution Time: 90.586 ms

【问题讨论】:

【参考方案1】:

“位图索引扫描”也是索引扫描。如果必须访问更大比例的表块,PostgreSQL 通常会选择这种方式,因为在这种情况下效率更高。

对于像你这样的索引范围扫描,有两种可能的解释:

    ANALYZE has run 在两个索引之间已经创建,因此 PostgreSQL 在一种情况下知道索引值的分布,但在另一种情况下不知道。

    要确定是否是这种情况,请运行

    ANALYZE cache;
    

    然后再次尝试这两个语句。也许现在的计划更相似了。

    这些语句在两个不同的表上运行,这些表包含相同的数据,但它们的物理排列方式不同,因此correlation 一方面是好的,另一方面是坏的。如果相关性接近 1 或 -1,则索引扫描变得更便宜。否则,位图索引扫描是最好的方法。

    由于你在两种情况下都表示是同一张表,所以可以排除这种解释。

索引的第二列是多余的;你应该省略它。 否则,您的两个索引应该工作差不多。

当然,如果表首先使用date 列定义,那么所有这些都会更好地工作...

【讨论】:

以上是关于如何强制 PostgreSQL 使用某个索引?的主要内容,如果未能解决你的问题,请参考以下文章

mysql 强制索引和禁止某个索引

mysql强制索引和禁止某个索引

mysql强制索引和禁止某个索引

SQL Server强制使用特定索引 并行度

如何强制合并连接?

PostgreSQL查看索引的使用情况