P.P*_*ter 17 postgresql full-text-search pattern-matching postgresql-9.1 postgresql-9.4
在 Postgres 9.1 数据库中,我有一个table1
包含约 1.5M 行和一列的表label
(为了这个问题而简化了名称)。
上有一个功能性的三元组索引lower(unaccent(label))
(unaccent()
已被设为不可变以允许其在索引中使用)。
以下查询非常快:
SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword%')));
count
-------
1
(1 row)
Time: 394,295 ms
Run Code Online (Sandbox Code Playgroud)
但以下查询速度较慢:
SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword and some more%')));
count
-------
1
(1 row)
Time: 1405,749 ms
Run Code Online (Sandbox Code Playgroud)
添加更多单词甚至更慢,即使搜索更严格。
我尝试了一个简单的技巧来运行第一个单词的子查询,然后使用完整的搜索字符串进行查询,但是(可悲的是)查询计划员看穿了我的阴谋:
EXPLAIN ANALYZE
SELECT * FROM (
SELECT id, title, label from table1
WHERE lower(unaccent(label)) like lower(unaccent('%someword%'))
) t1
WHERE lower(unaccent(label)) like lower(unaccent('%someword and some more%'));
Run Code Online (Sandbox Code Playgroud)
表1上的位图堆扫描(成本=16216.01..16220.04行=1宽度=212)(实际时间=1824.017..1824.019行=1循环=1) 重新检查条件:((lower(unaccent((label)::text)) ~~ '%someword%'::text) AND (lower(unaccent((label)::text)) ~~ '%someword 等等%'::文本)) -> 位图索引扫描 table1_label_hun_gin_trgm (cost=0.00..16216.01 rows=1 width=0) (实际时间=1823.900..1823.900 rows=1 loops=1) 索引条件:((lower(unaccent((label)::text)) ~~ '%someword%'::text) AND (lower(unaccent((label)::text)) ~~ '%someword and some more %'::文本)) 总运行时间:1824.064 毫秒
我的最终问题是搜索字符串来自一个网络界面,它可能会发送很长的字符串,因此速度很慢,也可能构成一个 DOS 向量。
所以我的问题是:
jja*_*nes 35
在 PostgreSQL 9.6 中将会有一个新版本的 pg_trgm,1.2,在这方面会好很多。稍加努力,您也可以让这个新版本在 PostgreSQL 9.4 下工作(您必须应用补丁,并自己编译扩展模块并安装它)。
最旧的版本所做的是搜索查询中的每个三元组并取它们的并集,然后应用过滤器。新版本将做的是选择查询中最稀有的三元组并仅搜索该三元组,然后稍后过滤其余部分。
9.1 中不存在执行此操作的机制。在 9.4 中添加了该机制,但当时 pg_trgm 并未适应使用它。
您仍然会遇到潜在的 DOS 问题,因为恶意人员可以制作只有常见三元组的查询。像“%and%”,甚至“%a%”
如果你不能升级到 pg_trgm 1.2,那么另一种欺骗计划者的方法是:
WHERE (lower(unaccent(label)) like lower(unaccent('%someword%')))
AND (lower(unaccent(label||'')) like
lower(unaccent('%someword and some more%')));
Run Code Online (Sandbox Code Playgroud)
通过将空字符串连接到标签,您可以欺骗规划器认为它不能在 where 子句的那部分使用索引。所以它只在 %someword% 上使用索引,并只对那些行应用过滤器。
此外,如果您总是搜索整个单词,则可以使用函数将字符串标记为单词数组,并在该数组返回函数上使用常规的内置 GIN 索引(而不是 pg_trgm)。