为啥 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 在使用覆盖索引时仍然进行位图堆扫描?的主要内容,如果未能解决你的问题,请参考以下文章