对值为 NULL 的布尔值进行查询时出现意外的 Seq 扫描

Sim*_*tti 11 postgresql index postgresql-8.3 postgresql-9.1

我有一个名为auto_reviewwhere column type is的数据库列boolean。该字段有一个索引,使用 ActiveRecord ORM 创建。

CREATE INDEX index_table_on_auto_renew ON table USING btree (auto_renew);
Run Code Online (Sandbox Code Playgroud)

当我查询布尔值的字段时,PG 按预期使用索引。

EXPLAIN for: SELECT "table".* FROM "table"  WHERE "table"."auto_renew" = 'f'
                                          QUERY PLAN
----------------------------------------------------------------------------------------------
 Bitmap Heap Scan on table  (cost=51.65..826.50 rows=28039 width=186)
   Filter: (NOT auto_renew)
   ->  Bitmap Index Scan on index_domains_on_auto_renew  (cost=0.00..44.64 rows=2185 width=0)
         Index Cond: (auto_renew = false)
(4 rows)
Run Code Online (Sandbox Code Playgroud)

当值为 时NULL,使用顺序扫描。

EXPLAIN for: SELECT "table".* FROM "table"  WHERE "table"."auto_renew" IS NULL
                           QUERY PLAN
----------------------------------------------------------------
 Seq Scan on table  (cost=0.00..1094.01 rows=25854 width=186)
   Filter: (auto_renew IS NULL)
(2 rows)
Run Code Online (Sandbox Code Playgroud)

我很想知道这个选择背后的原因。

Erw*_*ter 20

通常,col IS NULL是(默认)b 树索引搜索的可能候选者。手册

此外,索引列上的IS NULLIS NOT NULL条件可以与 B 树索引一起使用。

要获得证据,请禁用顺序扫描(仅在测试会话中!):

SET enable_seqscan = OFF;
Run Code Online (Sandbox Code Playgroud)

在这里引用手册

enable_seqscan (boolean)

启用或禁用查询计划程序对顺序扫描计划类型的使用。完全抑制顺序扫描是不可能的,但是如果有其他方法可用,关闭此变量会阻止计划者使用一种方法。默认为开启。

然后再试一次:

EXPLAIN ANALYZE SELECT * FROM tbl WHERE auto_renew IS NULL;
Run Code Online (Sandbox Code Playgroud)

这可能会导致位图索引扫描变慢比表上的顺序扫描。

重置或关闭会话(设置为会话本地)。

RESET enable_seqscan;
Run Code Online (Sandbox Code Playgroud)

boolean列上的索引仅在某些情况下有用。规划器仅在期望更快时才使用索引。计算基于您的成本设置和收集的统计数据ANALYZE。如果表中有相当大的部分符合您的条件(大约 5% 或更多,这取决于),通常进行全表扫描会更快。

这使得列中的稀有boolean成为普通索引的唯一有用候选者。并且为此创建(更专业的)部分索引通常更有效- 如果查询条件匹配,维护成本更低,更小,更快并且更容易使用。

如果您有很多查询查找行,auto_renew IS NULL并且这种NULL情况不是很常见(和/或您需要某种排序顺序),那么此索引将有助于快速查找/排序这些行:

CREATE INDEX index_tbl_tbl_id_auto_renew_null ON tbl (tbl_id)
WHERE auto_renew IS NULL;
Run Code Online (Sandbox Code Playgroud)

部分索引的条件必须在WHERE查询的子句中或多或少准确地重复,以使查询规划器意识到索引是适用的。

索引列 ( tbl_id) 是任意选择。重要的部分是WHERE条款。此特定索引对于带有ORDER BY tbl_id或附加过滤器或连接的查询最有效tbl_id。您可以将其设为多列索引。布尔列与其他列结合使用时通常更有用。

旁白:ORM 是经常无法从 RDBMS 中充分发挥潜力的拐杖。