LOWER LIKE vs iLIKE

Posted

技术标签:

【中文标题】LOWER LIKE vs iLIKE【英文标题】: 【发布时间】:2013-12-18 15:43:19 【问题描述】:

以下两个查询组件的性能比较如何?

低赞

... LOWER(description) LIKE '%abcde%' ...

喜欢

... description iLIKE '%abcde%' ...

【问题讨论】:

你的目标是什么?您是否有想要加快速度的慢速 SQL 命令,或者它只是 PostgreSQL 上的一个普遍问题? Stack Overflow 还没有(还没有?)#hashtags(除非你问的是 C 或 C++ 问题)。 @MartinStrejc 我的目标只是关于 PostgreSQL 的一般性问题。看到这两种开箱即用的解决方案可供选择,我想知道使用哪个。 【参考方案1】:

根据我的测试(每个查询的十次),LOWER LIKE17% 快大约iLIKE

说明

我创建了一百万行包含一些随机混合文本数据:

require 'securerandom'
inserts = []
1000000.times do |i|
        inserts << "(1, 'fake', '#SecureRandom.urlsafe_base64(64)')"
end
sql = "insert into books (user_id, title, description) values #inserts.join(', ')"
ActiveRecord::Base.connection.execute(sql)

验证行数:

my_test_db=# select count(id) from books ;
  count  
---------
 1000009

(是的,我有 9 行来自其他测试的额外行 - 不是问题。)

示例查询和结果:

my_test_db=# SELECT "books".* FROM "books" WHERE "books"."published" = 'f'
my_test_db=# and (LOWER(description) LIKE '%abcde%') ;
   id    | user_id | title |                                      description                                       | published 
---------+---------+-------+----------------------------------------------------------------------------------------+------
 1232322 |       1 | fake  | 5WRGr7oCKABcdehqPKsUqV8ji61rsNGS1TX6pW5LJKrspOI_ttLNbaSyRz1BwTGQxp3OaxW7Xl6fzVpCu9y3fA | f
 1487103 |       1 | fake  | J6q0VkZ8-UlxIMZ_MFU_wsz_8MP3ZBQvkUo8-2INiDIp7yCZYoXqRyp1Lg7JyOwfsIVdpPIKNt1uLeaBCdelPQ | f
 1817819 |       1 | fake  | YubxlSkJOvmQo1hkk5pA1q2mMK6T7cOdcU3ADUKZO8s3otEAbCdEcmm72IOxiBdaXSrw20Nq2Lb383lq230wYg | f

LOWER LIKE 的结果

my_test_db=# EXPLAIN ANALYZE SELECT "books".* FROM "books" WHERE "books"."published" = 'f' and (LOWER(description) LIKE '%abcde%') ;
                                                   QUERY PLAN                                                   
----------------------------------------------------------------------------------------------------------------
 Seq Scan on books  (cost=0.00..32420.14 rows=1600 width=117) (actual time=938.627..4114.038 rows=3 loops=1)
   Filter: ((NOT published) AND (lower(description) ~~ '%abcde%'::text))
   Rows Removed by Filter: 1000006
 Total runtime: 4114.098 ms

iLIKE 的结果

my_test_db=# EXPLAIN ANALYZE SELECT "books".* FROM "books" WHERE "books"."published" = 'f' and (description iLIKE '%abcde%') ;
                                                   QUERY PLAN                                                   
----------------------------------------------------------------------------------------------------------------
 Seq Scan on books  (cost=0.00..29920.11 rows=100 width=117) (actual time=1147.612..4986.771 rows=3 loops=1)
   Filter: ((NOT published) AND (description ~~* '%abcde%'::text))
   Rows Removed by Filter: 1000006
 Total runtime: 4986.831 ms

数据库信息披露

Postgres 版本:

my_test_db=# select version();
                                                                                 version
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 PostgreSQL 9.2.4 on x86_64-apple-darwin12.4.0, compiled by i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00), 64-bit

排序规则设置:

my_test_db=# select datcollate from pg_database where datname = 'my_test_db';
 datcollate  
-------------
 en_CA.UTF-8

表定义:

my_test_db=# \d books 
                                      Table "public.books"
   Column    |            Type             |                       Modifiers
-------------+-----------------------------+-------------------------------------------------------
 id          | integer                     | not null default nextval('books_id_seq'::regclass)
 user_id     | integer                     | not null
 title       | character varying(255)      | not null
 description | text                        | not null default ''::text
 published   | boolean                     | not null default false
Indexes:
    "books_pkey" PRIMARY KEY, btree (id)

【讨论】:

你的测试用例是单面的,数据中只有大写字母。此外,在现实生活中的应用程序中,您将使用索引进行操作,这会改变整个评估。并且基本详细信息未公开:Postgres 版本、您的排序规则设置、您的确切表定义。 @ErwinBrandstetter 我重新运行了我的测试并更新了我的答案以反映混合大小写的数据。我还添加了有关我的数据库的详细信息。 LOWER LIKE 仍然比 17% 快大约 iLIKE(比 25% 下降)。 +1 现在好多了。不过,我不会说“x 比 y 快 17%”,因为这仅适用于您的特定测试用例。顺便说一句,字符串的长度也是相关的。 @ErwinBrandstetter - 我意识到精确的百分比结果会有所不同,但我也认为仅仅说“x 比 y 快”太开放了。我认为通过您的评论,足够好奇的人会得到更完整的画面。顺便说一句,您是否知道字符串长度、排序规则设置或其他条件,这将始终导致iLIKE 执行LOWER LIKE 没有。不过不确定。我经常看到更接近的结果。在 Postgres 9.1 中使用 105 万行和真实“描述”、COLLATON de_AT.UTF-8、OS Debian Linux 的真实表进行快速测试。 LOWER / LIKE 快了 ~ 2%。【参考方案2】:

答案取决于许多因素,例如 Postgres 版本、编码和语言环境 - 特别是 LC_COLLATE

裸表达式lower(description) LIKE '%abc%' 通常比description ILIKE '%abc%' 快一点,并且比等效的正则表达式description ~* 'abc' 快一点。这对于必须为每个测试行计算表达式的顺序扫描很重要。

但是对于像您在答案中展示的大表,肯定会使用索引。对于任意模式(不仅是左锚定),我建议使用附加模块pg_trgm 进行三元组索引。然后我们说毫秒而不是秒,上面的表达式之间的差异就无效了。

GIN 和 GiST 索引(使用 gin_trgm_opsgist_trgm_ops 运算符类)支持 LIKE (~~)、ILIKE (~~*)、~~*(以及更多变体)类似。使用description 上的 trigram GIN 索引(通常比 GiST 大,但读取速度更快),您的查询将使用 description ILIKE 'case_insensitive_pattern'

相关:

PostgreSQL LIKE query performance variations Similar UTF-8 strings for autocomplete field

Postgres 中模式匹配的基础知识:

Pattern matching with LIKE, SIMILAR TO or regular expressions in PostgreSQL

使用上述三元组索引时,通常更实用:

description ILIKE '%abc%'

或使用不区分大小写的正则表达式运算符(不带% 通配符):

description ~* 'abc'

(description) 上的索引不支持lower(description) 上的查询,例如:

lower(description) LIKE '%abc%'

反之亦然。

对于lower(description) 上的谓词,表达式索引是更好的选择。

在所有其他情况下,(description) 上的索引更可取,因为它支持 区分大小写和不区分大小写的谓词。

【讨论】:

我会考虑您的建议,但我只是想澄清一下,我的目的是比较这两个开箱即用的解决方案。我重新运行了我的测试并更新了我的答案以反映混合大小写的数据。我还添加了有关我的数据库的详细信息。事实证明,LOWERLIKE 仍然比17% 快大约iLIKE(比25% 下降)。 值得注意的是,报告的 17% 用于连续扫描没有索引。我得到了 2% 的类似测试,see other comment。如果将三元索引添加到设置中,则两者都不适用 - 这会使差异无效。 感谢您的跟进。您认为将此评论添加到您的答案中是否公平? - 我认为选定的答案应该得出结论,LOWER LIKE 更快(除非添加了三元组索引,在这种情况下,正如你所说,没有区别)......但关键是LOWER LIKE 是伙计们应该使用,而不是iLIKE,这将是等效的或更慢的。 @user664833:嗯,不,一般不会。我在上面澄清了。 like 与 ilike 的使用不取决于您存储数据的方式吗?如果数据库中有'Joe',那么你需要降低两次:'lower(input) LIKE lower('%Joe%')。我想这就是创建 ILIKE 的原因......【参考方案3】:

在我的 Rails 项目中。 ILIKE 几乎是 LOWER LIKE 的 10 倍,我在 entities.name 列上添加了一个 GIN 索引

> Entity.where("LOWER(name) LIKE ?", name.strip.downcase).limit(1).first
Entity Load (2443.9ms)  SELECT  "entities".* FROM "entities" WHERE (lower(name) like 'baidu') ORDER BY "entities"."id" ASC LIMIT $1  [["LIMIT", 1]]
> Entity.where("name ILIKE ?", name.strip).limit(1).first
Entity Load (285.0ms)  SELECT  "entities".* FROM "entities" WHERE (name ilike 'Baidu') ORDER BY "entities"."id" ASC LIMIT $1  [["LIMIT", 1]]
# explain analyze SELECT  "entities".* FROM "entities" WHERE (name ilike 'Baidu') ORDER BY "entities"."id" ASC LIMIT 1;
                                                                   QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=3186.03..3186.04 rows=1 width=1588) (actual time=7.812..7.812 rows=1 loops=1)
   ->  Sort  (cost=3186.03..3187.07 rows=414 width=1588) (actual time=7.811..7.811 rows=1 loops=1)
         Sort Key: id
         Sort Method: quicksort  Memory: 26kB
         ->  Bitmap Heap Scan on entities  (cost=1543.21..3183.96 rows=414 width=1588) (actual time=7.797..7.805 rows=1 loops=1)
               Recheck Cond: ((name)::text ~~* 'Baidu'::text)
               Rows Removed by Index Recheck: 6
               Heap Blocks: exact=7
               ->  Bitmap Index Scan on index_entities_on_name  (cost=0.00..1543.11 rows=414 width=0) (actual time=7.787..7.787 rows=7 loops=1)
                     Index Cond: ((name)::text ~~* 'Baidu'::text)
 Planning Time: 6.375 ms
 Execution Time: 7.874 ms
(12 rows)

GIN 索引对于提高ILIKE 性能确实很有帮助

【讨论】:

name 上的索引不支持lower(name) 上的查询。我在回答中提到了这一点。这很可能是 10 倍性能差异的原因。 (您将看到顺序扫描而不是(位图)索引扫描。 @ErwinBrandstetter 是的,谢谢,我刚刚在我的项目中得到了这个,所以我把它放在这里是为了展示索引如何改变性能规则。 :)

以上是关于LOWER LIKE vs iLIKE的主要内容,如果未能解决你的问题,请参考以下文章

Sequelize - 不区分大小写

关于 LOCATE vs LIKE vs INSTR 性能分析

instr vs like 效率

实体框架EF.Functions.Like vs string.Contains

带有'like'和'or'的Spring JPA @Query where子句

实体框架 EF.Functions.Like vs string.Contains