【发布时间】:2019-09-19 01:32:00
【问题描述】:
搜索表达式索引
我正在构建一个表达式索引,它对源字段进行子串化以避免溢出 B 树上的 2172 个字符限制:
CREATE INDEX record_changes_log_detail_old_value_ix_btree
ON record_changes_log_detail
USING btree ((substring(old_value,1,1024)::text) text_pattern_ops);
记录在案:
-- RDS 上的 Postgres 11.4,家庭 macOS 上的 11.5。
-- 在我的测试设置中,record_changes_log_detail 表大约有 8M。
-- old_value 字段的类型为 citext。
-- 字段中的值的长度范围从 1 个字符到超过 5,000 个字符。大多数都很短。
此搜索使用上面指定的索引:
select * from record_changes_log_detail
where substring(old_value,1,1024) = 'Gold Kerrison Neuro';
此搜索不使用索引:
where old_value = 'Gold Kerrison Neuro';
我觉得这很令人惊讶,也很令人沮丧。如果我正确理解 jjanes 对另一个问题的评论,则规划器仅在您的查询语句使用 exactly 相同的表达式时才识别出索引适用。换句话说,任何编写查询的人都需要知道索引定义的详细信息,否则索引将不会被使用。
我一直假设在构建表达式索引时,缩写/提取/等。值已存储,计划者将对其进行检查。 除了概括整个表达式之外,还有什么方法可以提示规划器?索引有正确的数据,但规划器似乎跳过了它。
我根据 Erwin Brandstetter 的回答添加了一些细节:
我有很多类似的情况,这就是我在这里挖掘细节的原因。在这种情况下,在我的约 8M 行中,只有 6 行的值超过 2172 个字符,并且 99.93% 的值是 100 个字符或更少。
我希望的是一种其他人容易接受的方法。阴影字段很可能是答案,因为必须知道索引构造的确切细节让我觉得完全错误的可见性。一旦你知道使用它,阴影场就不会遇到这个问题。正如您所提到的,我可以使用 LEFT(old_field,128) 或其他长度或 texthash(old_field) 填充它。我会试验一下。我的数据非常偏向于短值,散列似乎会导致高冲突率。
对于它的价值,我和团队来自一个系统,当在 B 树中索引时,文本字段被静默修剪为 1024 个字符。它对用户完全透明,搜索查询索引。苹果和火花塞,我知道。关键是我不希望 Postgres 成为 AI,但我 am 提出了不准确的先验。因此,感谢您和其他所有人帮助我了解有关 Postgres 实际工作原理的更多信息。
跟进
这个问题已经回答了,但我想为档案添加一些后续行动。我一直在从旧答案中学到很多东西,其中一些非常古老。所以这里有一些关于未来的信息。我尝试了四种解决方案:
- 部分 citext 字段上的 B-tree。
- citext 字段哈希上的 B-tree。
- citext 字段的哈希索引。
- citext 字段的 Tri-gram GIN 索引。
由于似乎没有任何方法可以在文本可能太长的 citext 上获取 LIKE 类型的查询,因此目标是为 = 创建一个索引。以上三个中的任何一个都可以正常工作,但它们有很大的不同。这是测试的一些设置代码:
DROP INDEX IF EXISTS record_changes_log_detail_old_value_ix_btree;
CREATE INDEX record_changes_log_detail_old_value_ix_btree
ON record_changes_log_detail
USING btree ((left(old_value,1024)::citext) citext_pattern_ops);
DROP INDEX IF EXISTS record_changes_log_detail_old_value_hash_ix_btree;
CREATE INDEX record_changes_log_detail_old_value_hash_ix_btree
ON record_changes_log_detail
USING btree (hashtext(old_value));
DROP INDEX IF EXISTS record_changes_log_detail_old_value_ix_hash;
CREATE INDEX record_changes_log_detail_old_value_ix_hash
ON record_changes_log_detail
USING hash (old_value);
DROP INDEX IF EXISTS record_changes_log_detail_old_value_ix_tgrm;
CREATE INDEX record_changes_log_detail_old_value_ix_tgrm
ON record_changes_log_detail
USING gin (old_value gin_trgm_ops);
VACUUM ANALYZE;
这些索引都用于查找记录,但语法不同:
-- Uses the LEFT()::citext index
explain analyze
select * from record_changes_log_detail
where left(old_value,1024)::citext = 'Gold Kerrison Neuro';
-- Uses the HASH index
explain analyze
select * from record_changes_log_detail
where old_value = 'Gold Kerrison Neuro';
-- Uses the HASHTEXT() index
explain analyze
select * from record_changes_log_detail
where hashtext(old_value) = hashtext('Gold Kerrison Neuro');
-- Uses the tri-gram() index
explain analyze
select * from record_changes_log_detail
where old_value::text LIKE '%Gold Kerrison Neuro%';
散列索引提供了最好的语法,因为它是透明的……但是散列索引在其他所有方面都是最差的。这是尺寸搜索和结果。我在这里手动添加了报告的索引构建时间。
select
'B-tree on LEFT(old_value,1024)::citext' as index_description,
pg_size_pretty(pg_relation_size ('record_changes_log_detail_old_value_ix_btree')) as pretty
union all
select
'B-tree on HASHTEXT(old_value)' as index_description,
pg_size_pretty(pg_relation_size ('record_changes_log_detail_old_value_hash_ix_btree')) as pretty
union all
select
'Hash index on old_value' as index_description,
pg_size_pretty(pg_relation_size ('record_changes_log_detail_old_value_ix_hash')) as pretty
union all
select
'GIN tri-gram index on old_value' as index_description,
pg_size_pretty(pg_relation_size ('record_changes_log_detail_old_value_ix_tgrm')) as pretty;
index_description pretty seconds
B-tree on LEFT(old_value,1024)::citext 238 MB 38
B-tree on HASHTEXT(old_value) 166 MB 7
Hash index on old_value 362 MB 3,802
GIN tri-gram index on old_value 106 MB 56
我会说这个数据与哈希索引的匹配很糟糕,所以请不要将这些结果视为典型结果。尽管如此,时间和规模还是很糟糕的。 = 搜索的明显赢家是 Erwin Brandstetter 对 B-tree 哈希的巧妙建议。好的!搜索所需的额外语法糖在这里并不像基于 LEFT 的索引那样糟糕。展望未来,这将受益于 PG 12 中承诺的 B-tree 改进。
还有更多好消息,三元组索引非常棒。 Laurenz Albe 建议尝试一下,我很高兴我做到了。即时包含/喜欢搜索,完美。这正是我所需要的。再次在这里,我怀疑索引大小是典型的......我的数据很奇怪。对于使用 citext 的用户,请注意您必须将搜索条件转换为要使用的索引的文本:
select * from record_changes_log_detail
where old_value::text LIKE '%Gold Kerrison Neuro%';
对于那些不知道的人,tri-gram 是长度为 3 的 n-gram 的一个实例。N-gram 有时被称为 q-gram 或 k-gram。不管怎样,都是一样的。在所有幼稚(非概率或统计)模糊文本匹配算法中,它可能是最好的。在不同的数据集和语言上鲁棒,灵活,真棒。所以我对它在 Postgres 中的表现非常满意。
【问题讨论】:
-
有用的回顾,感谢您的跟进。也让我想起了对哈希冲突的缺失防御。
标签: postgresql indexing expression