在具有现有数据的表上创建时未使用 PostgreSQL 部分索引

Jak*_*sel 7 postgresql index statistics execution-plan

在 PostgreSQL 9.3 中,我试图在一个很少使用的(占总记录的 0.00001%)布尔列上创建一个有效的索引。为此,我在 SO 上发现了这篇文章: https //stackoverflow.com/a/12026593/808921

我正在尝试利用 Erwin Brandstetter 推荐的 PostgreSQL 的“部分索引”功能。我已经有一个包含几百万条记录的表,我想将索引添加到该表中,如下所示:

CREATE INDEX schema_defs_deprovision ON schema_defs (deprovision) 
WHERE deprovision = 0;
Run Code Online (Sandbox Code Playgroud)

(绝大多数记录都会有 deprovision = 1

问题是,当我尝试将此索引与最简单的查询一起使用时,PostgreSQL 就好像它不存在一样:

explain select * from schema_defs where deprovision = 0;

Seq Scan on schema_defs (cost=0.00..1.05 rows=1 width=278)
Filter: (deprovision = 0)
Run Code Online (Sandbox Code Playgroud)

真正奇怪的是,我发现如果这个索引是在表中有数据之前创建的,那么它确实可以正常工作。不相信我?以下是一些证明这一点的 SQL Fiddle 条目:

插入后创建的部分索引(索引不起作用)

插入前创建的部分索引(索引正常工作)

在这两个中,只需展开“查看执行计划”链接即可查看我在说什么。

所以,我的问题是 - 我必须做什么才能让 PostgreSQL 在创建索引之前开始在其中包含数据的表上使用部分索引?

顺便说一句,我也是 SQL Fiddle 的开发人员,这个问题与我正在为此进行的一项新开发工作有关。

Erw*_*ter 9

ANALYZE添加索引后运行。并确保该列deprovision 具有统计信息。如何验证?

基本统计pg_class

SELECT relname, relkind, reltuples, relpages
FROM   pg_class
WHERE  oid = 'schema_defs'::regclass;
Run Code Online (Sandbox Code Playgroud)

pg_stats( pg_statistics) 中每列的数据直方图:

SELECT attname, inherited, n_distinct
     , array_to_string(most_common_vals, E'\n') AS most_common_vals
FROM   pg_stats
WHERE  tablename = 'schema_defs'
AND    attname = 'deprovision';
Run Code Online (Sandbox Code Playgroud)

手册:

PostgreSQL 查询计划器依靠有关表内容的统计信息来生成良好的查询计划。这些统计信息由ANALYZE命令收集,可以由命令本身调用,也可以作为VACUUM. 拥有合理准确的统计数据很重要,否则计划的错误选择可能会降低数据库性能。

如果启用了 autovacuum 守护程序,则ANALYZE 只要表的内容发生充分变化,就会自动发出命令。但是,管理员可能更喜欢依赖手动调度的 ANALYZE操作,尤其是在已知表上的更新活动不会影响“感兴趣”列的统计信息的情况下。守护进程ANALYZE严格按照插入或更新的行数进行调度;它不知道这是否会导致有意义的统计变化。

在您的情况下,仅分析一列就可以完成这项工作:

ANALYZE table_name (deprovision);
Run Code Online (Sandbox Code Playgroud)

在此期间,在 column 上设置索引是没有意义的deprovision。给定谓词,WHERE deprovision = 0它不携带附加信息。您也可以使用常量表达式:

CREATE INDEX schema_defs_deprovision ON schema_defs ((true)) 
WHERE deprovision = 0;
Run Code Online (Sandbox Code Playgroud)

只是一个概念证明。这将不再有用。在这种特殊情况下,您根本不需要索引,但您必须至少提供一个列或表达式。因此,请使用主键(因为它不会更改并且无论如何都已编入索引,因此不会引入更多限制/开销成本)或任何其他可能对查询有用的小列(<= 8 字节)。

CREATE INDEX schema_defs_deprovision ON schema_defs (id) 
WHERE deprovision = 0;
Run Code Online (Sandbox Code Playgroud)

sqlfiddle.com

演示小提琴具有误导性

插入前创建的部分索引(索引正常工作)

您的演示表只有 4 行。Postgres应该使用索引。类似的问题,正好相反。Postgres 在创建后不会立即对表进行统计——直到第一次运行ANALYZE. 然后它知道只有 4 行并且不会再接触索引。
那么为什么它在你的第二个演示中正常工作呢?手册:

出于效率原因,reltuples并且relpages不会即时更新,因此它们通常包含有些过时的值。它们由VACUUMANALYZE和一些 DDL 命令更新,例如 CREATE INDEX.

大胆强调我的。如果插入行创建索引,pg_class则更新那些基本统计信息。但只有那些,而不是 中的详细统计数据pg_statistic

inpg_statistic中的条目由ANALYZEVACUUM ANALYZE 命令更新,并且即使在新更新时也始终是近似值。

为了让 Postgres 使用部分索引(特别是它的原始形式对其他任何东西都没有用),您还需要数据直方图来pg_statistic通知查询规划器,这deprovision = 0实际上是一种罕见的情况,因此使用索引是需要付出代价的。

Autovacuum负责解决这个问题。它调度VACUUMANALYZE自动。但是在写入表和下一次ANALYZE运行之间有一个时间范围(取决于设置和负载)。如果您在创建表或更改表后立即运行查询,这些最新的更改还不会反映在统计信息中。没关系,如果这不会以相关的方式改变统计数据。如果确实如此,例如在创建大INSERT表之后或立即创建表之后,请ANALYZE手动运行以获得正确的查询计划。

请注意,autovacuum 根本不涵盖临时表。如果需要,您总是需要ANALYZE手动运行这些:

我不知道您如何配置 autovacuum 以及是否/何时运行 ANALYZE手动。但我过去注意到 sqlfiddle 可能会由于缺少/过时的统计数据而产生误导。

我会非常感兴趣如何ANALYZE在 sqlfiddle 的幕后处理。最好不要做任何特别的事情,但欢迎提供一些信息。也许每个可用的 RDBMS 版本都有一个基本网页?

演示

我创建了一个SQL小提琴展示的效果CREATE INDEXANALYZE对各种统计数据。

效果(至少)在我的第一次运行中显示出来。在以后的运行中可能无法重现,您必须创建一个新模式并再次运行。

首先,我们在 中没有看到基本统计数据pg_class

relname      reltuples  relpages
schema_defs  0          0
Run Code Online (Sandbox Code Playgroud)

也不对任何条目deprovisionpg_statistics的所有(无结果)。
Postgres 不知道表中有什么并且默认使用索引 - 这是一个糟糕的选择!

之后CREATE INDEX,我们看到了基本的统计数据,但在 中仍然没有数据直方图pg_statistics

ANALYZE我们看到两者之后。

有了适当的统计信息,Postgres 现在使用顺序扫描(不错的选择,即使有索引 - 如此少的行会更昂贵)。