Performance 低人一等

Performance 低人一等,performance,postgresql,pattern-matching,database-performance,Performance,Postgresql,Pattern Matching,Database Performance,以下两个查询组件的性能比较如何 较低的类 ... LOWER(description) LIKE '%abcde%' ... ... description iLIKE '%abcde%' ... my_test_db=# EXPLAIN ANALYZE SELECT "books".* FROM "books" WHERE "books"."published" = 'f' and (LOWER(description) LIKE '%abcde%') ;

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

较低的类

... LOWER(description) LIKE '%abcde%' ...
... description iLIKE '%abcde%' ...
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
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
我喜欢

... LOWER(description) LIKE '%abcde%' ...
... description iLIKE '%abcde%' ...
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
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
根据我的测试(每次查询10次),
LOWER
LIKE
大约比
iLIKE
17%

解释

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

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 "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
较低级别的LIKE结果

... LOWER(description) LIKE '%abcde%' ...
... description iLIKE '%abcde%' ...
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
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
iLIKE的结果

... LOWER(description) LIKE '%abcde%' ...
... description iLIKE '%abcde%' ...
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
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
数据库信息披露

博士后版本:

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版本、编码和语言环境——尤其是

像“%abc%”这样的裸表达式
lower(description)
通常比像“%abc%”这样的
description(description)要快一点,并且两者都比等价的正则表达式快一点:
description~*'abc'
。这对于顺序扫描很重要,其中必须为每个测试行计算表达式

但是对于您在回答中演示的大型表格,您肯定会使用索引。对于任意模式(不仅仅是左锚定模式),我建议使用附加模块创建三角图索引。然后我们讨论毫秒而不是秒,上面表达式之间的差异为空

GIN和GiST索引(使用
GIN\u trgm\u ops
GiST\u trgm\u ops
操作符类)支持
,如
~
)、
ILIKE
~*
)、
~*
(以及其他一些变体)等。如果在
description
上有一个trigram GIN索引(通常比GiST大,但读取速度更快),您的查询将使用
description类似于“不区分大小写的模式”

相关的:

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

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

description ILIKE '%abc%'
或者使用不区分大小写的regexp运算符(不带
%
通配符):

(说明)
上的索引不支持对
下部(说明)
的查询,例如:

lower(description) LIKE '%abc%'
反之亦然

使用
lower(description)
上的谓词,表达式索引是更好的选择


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

在我的rails项目中
ILIKE
LOWER LIKE
快近10倍,我在
实体上添加了
GIN
索引。name

> 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]]

GIN索引确实有助于提高ILIKE的性能,因为您的测试用例是单面的,数据中只有大写字母。此外,在实际应用程序中,您将使用索引进行操作,这将改变整个评估。基本细节未披露:Postgres版本、排序规则设置、确切的表定义。@ErwinBrandstetter我重新运行了测试,并更新了答案以反映混合案例数据。我还添加了有关数据库的详细信息<代码>较低
仍然比
iLIKE
快17%
(比
25%
低一点)。+1现在好多了。不过,我不会说“x比y快17%”,因为这只适用于您的特定测试用例。顺便说一句,字符串的长度也是相关的。@ErwinBrandstetter-我意识到精确的百分比结果会有所不同,但我也认为仅仅说“x比y快”太过开放。我认为,通过你的评论,那些足够好奇的人会得到一个更完整的画面。顺便问一下,您是否注意到字符串长度、排序规则设置或其他一些情况,这些情况会持续导致
i像
LOWER那样执行
LOWER
?不。不过,不确定。我经常看到更接近的结果。在Postgres 9.1中的一个实际表格上运行了一个快速测试,其中有105万行和实际的“描述”,COLLATON de_AT.UTF-8,OS Debian Linux。LOWER/LIKE快了约2%。你的目标是什么?你有一个缓慢的SQL命令,你想加快它还是只是一个关于PostgreSQL?堆栈溢出的一般问题,还没有(现在?)标签(除非你问C或C++问题)。@ MartinStrejc我的目标只是PostgreSQL上的一个一般问题。在选择这两个盒子外的解决方案时,我想知道使用哪一个。我会考虑你的建议,虽然我只是想澄清我的意图是比较这两个盒子外的解决方案。我重新运行了测试,并更新了答案,以反映混合案例数据。我还添加了有关数据库的详细信息。结果是
LOWER
LIKE
仍然比
iLIKE
快约17%
(从
25%
下降)。值得注意的是,报告的17%用于无索引的顺序扫描。我得到了2%的相似测试。如果在设置中添加了三元索引,这两种方法都不适用-这会消除差异。感谢您的跟进。您认为在您的回答中添加此评论公平吗?-我认为选择的答案应该得出这样的结论:
LOWER LIKE
更快(除非添加了三元索引,在这种情况下,正如您所说,没有区别)。。。但关键是人们应该使用的是
LOWER LIKE
,而不是
i LIKE
,后者要么是等效的,要么是较慢的。@user664833:不,不是一般的。我在上面解释过。like和ilike的使用不取决于您如何存储数据吗?如果数据库中有'Joe',则需要降低两次:'lower(input)LIKE lower('%Joe%')。我想这就是为什么要创建ILIKE的原因,
name
上的索引不支持对
lower(name)的查询