PostgreSQL不使用部分索引

时间:2014-09-25 04:11:32

标签: sql database performance postgresql indexing

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

查询

text_col

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。总之......

  1. 此查询返回的行数很小。
  2. 鉴于非空行非常独特,对文本的索引扫描应该更快。
  3. 清理和分析表并没有帮助优化器选择索引。
  4. 我的问题

    1. 为什么数据库会通过索引扫描选择序列扫描?
    2. 当一个表有一个应该检查相等条件的文本列时,是否有任何我可以遵循的最佳实践?
    3. 如何减少此查询所需的时间?
    4. [编辑 - 更多信息]

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

3 个答案:

答案 0 :(得分:9)

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

VOILÀ。

Per documentation

  

但是,请记住谓词必须符合条件   用于应该从索引中受益的查询中。成为   精确地,只有系统可以在查询中使用部分索引   认识到查询的WHERE条件在数学上意味着   索引的谓词。 PostgreSQL没有复杂的功能   可以识别数学等价的定理证明器   以不同形式编写的表达式。 (这不仅仅是这样的   一般定理证明极难创造,它会   可能太慢而无法实际使用。)系统可以识别   简单的不等式含义,例如“x <1”表示“x <2”;   否则谓词条件必须完全匹配部分   查询的WHERE条件或索引将不会被识别为可用。   匹配发生在查询计划时,而不是在运行时。作为一个   结果,参数化查询子句不适用于部分索引。

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


Postgres 9.6 中的重要更新大大提高了index-only scans的机会(这可以使查询更便宜,查询计划员将更容易选择此类查询计划)。相关:

答案 1 :(得分:1)

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

尝试以下方法:

  1. 制作一个最简单的btree索引CREATE INDEX foo ON table (text_col)
  2. ANALYZE table
  3. 查询

答案 2 :(得分:0)

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

相关性

  

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

在我的本地方框中,相关数为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值仍导致索引扫描。改变填充因子和完全抽真空有所帮助,但我不确定为什么。