【问题标题】:PostgreSQL does not use a partial indexPostgreSQL 不使用部分索引
【发布时间】:2014-11-19 17:46:53
【问题描述】:

我在 PostgreSQL 9.2 中有一个表,它有一个 text 列。我们称之为text_col。此列中的值相当独特(最多可能包含 5-6 个重复项)。该表有大约 500 万行。这些行中大约有一半包含text_colnull 值。当我执行以下查询时,我期望 1-5 行。在大多数情况下 (>80%) 我只希望有 1 行。

查询

explain analyze SELECT col1,col2.. colN
FROM table 
WHERE text_col = 'my_value';

btree 索引存在于 text_col。查询计划器从不使用此索引,我不知道为什么。这是查询的输出。

规划师

Seq Scan on two (cost=0.000..459573.080 rows=93 width=339) (actual time=1392.864..3196.283 rows=2 loops=1)
Filter: (victor = 'foxtrot'::text)
Rows Removed by Filter: 4077384

我添加了另一个部分索引来尝试过滤掉那些不为空但没有帮助的值(有或没有text_pattern_ops。我不需要text_pattern_ops,考虑到没有LIKE条件在我的查询,但它们也匹配相等)。

CREATE INDEX name_idx
  ON table
  USING btree
  (text_col COLLATE pg_catalog."default" text_pattern_ops)
  WHERE text_col IS NOT NULL;

使用set enable_seqscan = off; 禁用序列扫描会使规划器仍然选择seqscan 而不是index_scan。总之……

  1. 此查询返回的行数很少。
  2. 鉴于非空行相当独特,对文本的索引扫描应该更快。
  3. 清空和分析表并不能帮助优化器选择索引。

我的问题

  1. 为什么数据库选择顺序扫描而不是索引扫描?
  2. 当表中的文本列应检查其相等条件时,我是否可以遵循任何最佳做法?
  3. 如何减少此查询所用的时间?

[编辑 - 更多信息]

  1. 索引扫描是在我的本地数据库中提取的,该数据库包含生产中可用数据的大约 10%。

【问题讨论】:

  • 我认为排序规则不匹配。尝试指定一个:SELECT col1,col2.. colN FROM table WHERE text_col = 'my_value' COLLATE pg_catalog."default"
  • 我尝试指定COLLATE pg_catalog."default",结果是序列扫描。
  • 表结构(CREATE TABLE 语句)?我 a) 建立了一个测试表,b) 用 500 万行、一半 NULL 和一半随机字符串填充它,c) 在 text_col 上创建了一个部分索引,d) 分析了表,并且 e) 在 0.075 毫秒内找到了一个匹配的行。 PostgreSQL 9.3 使用了仅索引扫描。
  • 我使用 Postgresql 9.2.4。我发现它会选择序列扫描同样令人费解。
  • 因为您一直在尝试禁用规划器方法。 show enable_indexscan; 能得到什么?你不是不小心禁用的吗?

标签: sql database performance postgresql indexing


【解决方案1】:

partial index 是一个好主意,可以排除您显然不需要的表的一半行。更简单:

CREATE INDEX name_idx ON table (text_col)
WHERE text_col IS NOT NULL;

确保在创建索引后运行ANALYZE table。 (如果您不手动执行,Autovacuum 会在一段时间后自动执行此操作,但如果您在创建后立即进行测试,您的测试将失败。)

然后,为了让查询计划者相信可以使用特定的部分索引,请在查询中重复 WHERE 条件 - 即使它看起来完全多余:

SELECT col1,col2, .. colN
FROM   table 
WHERE  text_col = 'my_value'
AND   text_col IS NOT NULL;  -- repeat condition

瞧。

Per documentation:

但是,请记住谓词必须匹配条件 在应该从索引中受益的查询中使用。成为 准确地说,只有当系统可以在查询中使用部分索引 认识到查询的WHERE 条件在数学上暗示 索引的谓词。 PostgreSQL 没有复杂的 可以识别数学等价的定理证明器 以不同形式书写的表达方式。 (不仅是这样的 一般定理证明器极难创建,它将 可能太慢而无法真正使用。)系统可以识别 简单的不等式含义,例如“x WHERE 条件或索引将不会被识别为可用。 匹配发生在查询计划时,而不是运行时。作为一个 结果,参数化查询子句不适用于部分索引。

至于参数化查询:再次,将部分索引的(冗余)谓词添加为附加的常量WHERE 条件,它就可以正常工作了。


Postgres 9.6 中的一项重要更新大大提高了index-only scans 的机会(这可以降低查询成本,并且查询规划器将更容易选择此类查询计划)。相关:

【讨论】:

  • 我试过这个并按照建议运行语句,只替换表/索引和列名。它仍然会进行序列扫描。我在运行查询之前分析了表格。
  • @DeepakBala:这很奇怪。我正在使用数百个部分索引,它们的工作方式与宣传的一样。
  • 我也很难过。我决定在 SO 上发帖,认为我遗漏了一些明显的东西。如果有什么我可以忽略的,请告诉我。如果我能尽快找到答案,我会发布答案。
  • 感谢您的帮助。我设法获得了索引扫描并发布了我自己的答案。我会继续接受您的回答,因为您帮助确认我使用部分索引的方法没有偏离轨道。
  • 您的 pg9.6 链接的底部正是我来这里寻找的内容:"... WHERE 子句指的是成功,它作为索引的结果列不可用。尽管如此,仅索引扫描是可能的,因为计划不需要在运行时重新检查 WHERE 子句的那部分:在索引中找到的所有条目都必须成功 = true,因此不需要在计划中明确检查。 9.6 及更高版本将识别此类情况并允许生成仅索引扫描,但旧版本不会。”
【解决方案2】:

我想通了。在仔细查看analyze 帮助构建的pg_stats 视图后,我在documentation 上看到了这段摘录。

相关性

物理行排序和逻辑之间的统计相关性 列值的排序。范围从 -1 到 +1。当。。。的时候 值接近 -1 或 +1,将估计列上的索引扫描 由于随机性的减少,比接近零时便宜 访问磁盘。 (如果列数据类型为空,则此列为空 没有

在我的本地机器上,相关编号是 0.97,而在生产中是 0.05。因此,规划器估计更容易依次遍历所有这些行,而不是每次都查找索引并潜入磁盘块上的随机访问。这是我用来查看相关数的查询。

select * from pg_stats where tablename = 'table_name' and attname = 'text_col';

此表还对其行执行了一些更新。行的avg_width 估计为20 个字节。如果文本列的更新值很大,它可能会超过平均值,并且还会导致更新速度变慢。我的猜测是物理和逻辑顺序随着每次更新而变慢。为了解决这个问题,我执行了以下查询。

ALTER TABLE table_name SET (FILLFACTOR = 80);
VACUUM FULL table_name;
REINDEX TABLE table_name;
ANALYZE table_name;

我的想法是我可以给每个磁盘块一个 20% 的缓冲区和vacuum full 表来回收丢失的空间并保持物理和逻辑顺序。在我这样做之后,查询会获取索引。

查询

explain analyze SELECT col1,col2... colN
FROM table_name 
WHERE text_col is not null 
AND 
text_col = 'my_value';

部分索引扫描 - 1.5ms

Index Scan using tango on two (cost=0.000..165.290 rows=40 width=339) (actual time=0.083..0.086 rows=1 loops=1)
Index Cond: ((victor five NOT NULL) AND (victor = 'delta'::text))

排除 NULL 条件会使用位图堆扫描获取另一个索引。

完整索引 - 0.08ms

Bitmap Heap Scan on two  (cost=5.380..392.150 rows=98 width=339) (actual time=0.038..0.039 rows=1 loops=1)
    Recheck Cond: (victor = 'delta'::text)
  ->  Bitmap Index Scan on tango  (cost=0.000..5.360 rows=98 width=0) (actual time=0.029..0.029 rows=1 loops=1)
          Index Cond: (victor = 'delta'::text)

[编辑]

虽然correlation 最初看起来在选择索引扫描方面起着重要作用,但@Mike 观察到在他的数据库中接近 0 的correlation 值仍然会导致索引扫描。改变填充因子和完全吸尘有所帮助,但我不确定为什么。

【讨论】:

  • 我的 9.2.9 表上的相关性是 0.006;在我的 9.3 表上是 0.003。然而,我对它们都进行了仅索引扫描。
  • 在 jkj 提交的示例中,我上次检查时的值为 0.4。可能是计划程序仅使用该数字作为参数之一来决定是否应该进行索引扫描。也就是说,在我的案例中,执行这些查询确实改变了规划者的手,我的结论是使用correlation 作为唯一的决定因素可能是一个红鲱鱼。我在 psql 中偶然发现了cost estimation functions,这可能有助于解释这种行为,但并没有走得太远。
【解决方案3】:

仅当 WHERE 条件匹配时才使用部分索引。因此,只有当您在 SELECT 中使用相同的条件时,才能使用带有 WHERE text_col IS NOT NULL 的索引。排序规则不匹配也可能造成伤害。

尝试以下方法:

  1. 制作尽可能简单的 btree 索引CREATE INDEX foo ON table (text_col)
  2. ANALYZE table
  3. 查询

【讨论】:

  • 试过了。它会再次导致序列扫描。
  • @DeepakBala 在 SQLfiddle 中尝试过,似乎使用了索引 (sqlfiddle.com/#!15/b576c/2)。请再次ANALYZE整个数据库。由于某种原因,您的索引没有被使用。您能否为新索引显示CREATE INDEX
  • 我完全按照您在 #1 中的建议执行了创建索引。仅更改了索引/表和列的名称。现在分析了整个数据库,仍然执行序列扫描。结果看起来很尴尬,因为我本地数据库上的同一张表正确地获取了索引,尽管表的大小是生产中的 10%。
猜你喜欢
  • 2021-12-01
  • 2017-06-30
  • 1970-01-01
  • 2011-03-20
  • 1970-01-01
  • 2018-03-25
  • 2023-02-05
  • 1970-01-01
  • 2012-05-21
相关资源
最近更新 更多