为啥 Postgres 在使用覆盖索引时仍然进行位图堆扫描?

Posted

技术标签:

【中文标题】为啥 Postgres 在使用覆盖索引时仍然进行位图堆扫描?【英文标题】:Why does Postgres still do a Bitmap Heap Scan when a covering index is used?为什么 Postgres 在使用覆盖索引时仍然进行位图堆扫描? 【发布时间】:2020-07-10 12:48:45 【问题描述】:

表格如下所示:

CREATE TABLE "audit_log" (
  "id" int4 NOT NULL DEFAULT nextval('audit_log_id_seq'::regclass),
  "entity" varchar(50) COLLATE "public"."ci",
  "updated" timestamp(6) NOT NULL,
  "transaction_id" uuid,
  CONSTRAINT "PK_audit_log" PRIMARY KEY ("id")
);

它包含数百万行。

我尝试在这样的一列上添加索引:

CREATE INDEX "testing" ON "audit_log" USING btree (
  "entity" COLLATE "public"."ci" "pg_catalog"."text_ops" ASC NULLS LAST
);

然后对索引列和主键运行以下查询:

EXPLAIN ANALYZE SELECT entity, id FROM audit_log WHERE entity = 'abcd'

正如我所料,查询计划同时使用位图索引扫描(大概是为了查找“实体”列)和位图堆扫描(我假设是为了检索“id”列):

Gather  (cost=2640.10..260915.23 rows=87166 width=122) (actual time=2.828..3.764 rows=0 loops=1)
  Workers Planned: 2
  Workers Launched: 2
  ->  Parallel Bitmap Heap Scan on audit_log  (cost=1640.10..251198.63 rows=36319 width=122) (actual time=0.061..0.062 rows=0 loops=3)
        Recheck Cond: ((entity)::text = '1234'::text)
        ->  Bitmap Index Scan on testing  (cost=0.00..1618.31 rows=87166 width=0) (actual time=0.036..0.036 rows=0 loops=1)
              Index Cond: ((entity)::text = '1234'::text)

接下来我在索引中添加了一个 INCLUDE 列,以使其覆盖上述查询:

DROP INDEX testing

CREATE INDEX testing ON audit_log USING btree (
    "entity" COLLATE "public"."ci" "pg_catalog"."text_ops" ASC NULLS LAST
)
INCLUDE
(
  "id"
)

然后我重新运行我的查询,但它仍然执行位图堆扫描:

Gather  (cost=2964.10..261239.23 rows=87166 width=122) (actual time=2.711..3.570 rows=0 loops=1)
  Workers Planned: 2
  Workers Launched: 2
  ->  Parallel Bitmap Heap Scan on audit_log  (cost=1964.10..251522.63 rows=36319 width=122) (actual time=0.062..0.062 rows=0 loops=3)
        Recheck Cond: ((entity)::text = '1234'::text)
        ->  Bitmap Index Scan on testing  (cost=0.00..1942.31 rows=87166 width=0) (actual time=0.029..0.029 rows=0 loops=1)
              Index Cond: ((entity)::text = '1234'::text)

这是为什么呢?

【问题讨论】:

【参考方案1】:

PostgreSQL 使用称为可见性 的概念实现行版本控制。每个查询都知道它可以看到哪个版本的行。

现在可见性信息存储在表行中,而不是索引条目中,因此必须访问该表以测试该行是否可见。

因此,每次位图索引扫描都需要位图堆扫描。

为了克服这个不幸的特性,PostgreSQL 引入了可见性映射,这是一种数据结构,如果该块中的所有行对每个人都是可见的,则该数据结构存储表的每个 8kB 块。如果是这种情况,可以跳过查找表行。这仅适用于常规索引扫描,而不是位图索引扫描。

该可见性地图由VACUUM 维护。所以在表上运行VACUUM,那么你可能会在表上得到一个index only scan

如果仅此还不够,您可以尝试CLUSTER 按索引顺序重写表。

【讨论】:

以上是关于为啥 Postgres 在使用覆盖索引时仍然进行位图堆扫描?的主要内容,如果未能解决你的问题,请参考以下文章

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

为啥 oracle 表索引但仍然进行全表扫描?

Postgres ltree 不使用 Gist 索引 为啥?

为啥我的 Postgres SQL 查询不使用索引

索引扫描时 Postgres 不使用索引是更好的选择

如果我删除带有索引的 PG 表,该索引是不是仍然存在?