我在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
。总之......
答案 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À。
但是,请记住谓词必须符合条件 用于应该从索引中受益的查询中。成为 精确地,只有系统可以在查询中使用部分索引 认识到查询的
WHERE
条件在数学上意味着 索引的谓词。 PostgreSQL没有复杂的功能 可以识别数学等价的定理证明器 以不同形式编写的表达式。 (这不仅仅是这样的 一般定理证明极难创造,它会 可能太慢而无法实际使用。)系统可以识别 简单的不等式含义,例如“x <1”表示“x <2”; 否则谓词条件必须完全匹配部分 查询的WHERE
条件或索引将不会被识别为可用。 匹配发生在查询计划时,而不是在运行时。作为一个 结果,参数化查询子句不适用于部分索引。
对于参数化查询:再次,将部分索引的(冗余)谓词添加为附加的常量WHERE
条件,并且它可以正常工作。
Postgres 9.6 中的重要更新大大提高了index-only scans的机会(这可以使查询更便宜,查询计划员将更容易选择此类查询计划)。相关:
答案 1 :(得分:1)
仅在WHERE条件匹配时才使用部分索引。因此,只有在WHERE text_col IS NOT NULL
中使用相同条件时,才能使用SELECT
的索引。整理不匹配也可能造成伤害。
尝试以下方法:
CREATE INDEX foo ON table (text_col)
ANALYZE table
答案 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';
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条件会使用位图堆扫描获取另一个索引。
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
值仍导致索引扫描。改变填充因子和完全抽真空有所帮助,但我不确定为什么。