PostgreSQL LIKE查询性能差异


112

我已经看到有关LIKE对数据库中特定表的查询的响应时间差异很大。有时,我会在200-400毫秒内获得结果(非常可以接受),但其他时候可能需要多达30秒才能返回结果。

我了解LIKE查询非常耗费资源,但我只是不明白为什么响应时间会有如此大的差异。我已经在该owner1字段上建立了一个btree索引,但是我认为这对LIKE查询没有帮助。有人有主意吗?

示例SQL:

SELECT gid, owner1 FORM parcels
WHERE owner1 ILIKE '%someones name%' LIMIT 10

我也尝试过:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%') LIMIT 10

和:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('someones name%') LIMIT 10

结果相似。
表格行数:约95,000。

Answers:


280

FTS不支持 LIKE

以前接受的答案是不正确的。具有全文索引的全文搜索根本适合LIKE运算符,它具有自己的运算符,不适用于任意字符串。它运行在的话基于字典和制止。它确实支持单词的前缀匹配,但不支持LIKE运算符:

的Trigram索引 LIKE

安装附加模块pg_trgm,该模块为GIN和GiST Trigram索引提供运算符类,以支持all LIKEILIKEpattern,而不仅仅是左锚定的:

索引示例:

CREATE INDEX tbl_col_gin_trgm_idx  ON tbl USING gin  (col gin_trgm_ops);

要么:

CREATE INDEX tbl_col_gist_trgm_idx ON tbl USING gist (col gist_trgm_ops);

查询示例:

SELECT * FROM tbl WHERE col LIKE '%foo%';   -- leading wildcard
SELECT * FROM tbl WHERE col ILIKE '%foo%';  -- works case insensitively as well

三连词?较短的弦呢?

文字少于3个字母的索引值仍然工作。手册:

在确定字符串中包含的三字母组时,每个单词都被认为具有两个前缀和一个后缀。

以及少于3个字母的搜索模式?手册:

对于LIKE正则表达式搜索,请记住,没有可提取三字母组的模式将退化为全索引扫描。

意思是,索引/位图索引扫描仍然有效(准备好的语句的查询计划不会中断),只是不会为您带来更好的性能。通常不会造成很大的损失,因为1或2个字母的字符串几乎没有选择性(超过基础表的百分之几),并且索引支持从一开始就不会提高性能,因为全表扫描速度更快。


text_pattern_ops 用于前缀匹配

对于仅左固定模式(无前导通配符),您可以通过为btree索引使用合适的运算符类来获得最佳值:text_pattern_opsvarchar_pattern_ops。标准Postgres的两个内置功能,无需其他模块。性能相似,但索引要小得多。

索引示例:

CREATE INDEX tbl_col_text_pattern_ops_idx ON tbl(col text_pattern_ops);

查询示例:

SELECT * FROM tbl WHERE col LIKE 'foo%';  -- no leading wildcard

或者,如果您应该使用“ C”语言环境(实际上没有语言环境)来运行数据库,那么无论如何,所有内容均根据字节顺序进行排序,并且具有默认运算符类的纯btree索引可以完成此工作。

dba.SE上这些相关答案中的更多详细信息,解释,示例和链接:


在50万行的表上没有领先的通配符时,带有gin_trgm_ops的gin索引似乎比btree快10倍
nicolas

@nicolas:比较取决于许多变量。密钥长度,数据分布,模式长度,可能的索引仅可扫描...而且最重要的是:Postgres版本。GIN指数在9.4和9.5页中得到了显着改善。pg_trgm的新版本(将与pg 9.6一起发布)将带来更多改进。
Erwin Brandstetter,2016年

1
如果我的文档正确无误,则pg_trgm需要至少3个字符的查询字符串,例如fo%不会命中索引而是执行扫描。要注意的事情。
Tuukka Mustonen

1
@TuukkaMustonen:好点。好吧,(位图)索引扫描仍然有效,它们不会为您带来更好的性能。我在上面添加了一些说明。
Erwin Brandstetter

7

最快的可能是具有区分大小写的固定模式,例如可以使用索引。也就是说,在匹配字符串的开头没有通配符,因此执行者可以使用索引范围扫描。(文档中的相关注释在此处)Lower和ilike也会失去使用索引的能力,除非您为此目的专门创建了索引(请参阅功能性索引)。

如果要在字段中间搜索字符串,则应查看全文索引trigram索引。它们中的第一个位于Postgres核心中,另一个位于contrib模块中。


我没有考虑过在该字段的小写值上创建索引。这样,我可以在查询之前将查询文本转换为小写。
杰森

4

您可以在PostgreSQL中安装Wildspeed,这是另一种类型的索引。Wildspeed确实可以使用%word%通配符,没问题。缺点是索引的大小,这可能很大,也很大。


3

请执行以下提到的查询,以提高postgresql中的LIKE查询性能。为更大的表创建这样的索引:

CREATE INDEX <indexname> ON <tablename> USING btree (<fieldname> text_pattern_ops)

仅当模式不是以通配符开头时才有效-在这种情况下,前两个示例查询都以通配符开头。
cbz


1

最近,我在一个包含200000条记录的表中遇到了类似的问题,我需要重复执行LIKE查询。就我而言,正在搜索的字符串是固定的。其他领域各不相同。因此,我能够重写:

SELECT owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%');

CREATE INDEX ix_parcels ON parcels(position(lower('someones name') in lower(owner1)));

SELECT owner1 FROM parcels
WHERE position(lower('someones name') in lower(owner1)) > 0;

当查询快速返回并验证索引是否与EXPLAIN ANALYZE以下对象一起使用时,我感到非常高兴:

 Bitmap Heap Scan on parcels  (cost=7.66..25.59 rows=453 width=32) (actual time=0.006..0.006 rows=0 loops=1)
   Recheck Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
   ->  Bitmap Index Scan on ix_parcels  (cost=0.00..7.55 rows=453 width=0) (actual time=0.004..0.004 rows=0 loops=1)
         Index Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
 Planning time: 0.075 ms
 Execution time: 0.025 ms

0

您喜欢的查询可能无法使用您创建的索引,因为:

1)您的“喜欢”条件以通配符开头。

2)您已使用符合LIKE标准的功能。


0

每当您在具有函数(例如LIKE,ILIKE,upper,lower等)的列上使用子句时,postgres都不会考虑您的普通索引。它将对通过每一行的表进行全面扫描,因此将很慢。

正确的方法是根据您的查询创建新索引。例如,如果我想匹配不区分大小写的列,并且我的列是varchar。然后,您可以这样做。

create index ix_tblname_col_upper on tblname (UPPER(col) varchar_pattern_ops);

同样,如果您的专栏是文字,则可以执行以下操作

create index ix_tblname_col_upper on tblname (UPPER(col) text_pattern_ops);

同样,您可以将函数上限更改为所需的任何其他函数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.