web-dev-qa-db-fra.com

LOWER LIKE vs iLIKE

Comment les performances des deux composants de requête suivants se comparent-elles?

PLUS BAS COMME

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

iLIKE

... description iLIKE '%abcde%' ...
29
user664833

La réponse dépend de nombreux facteurs comme la version Postgres, l'encodage et les paramètres régionaux - LC_COLLATE en particulier.

L'expression nue lower(description) LIKE '%abc%' est généralement un peu plus rapide que description ILIKE '%abc%' Et l'une ou l'autre est un peu plus rapide que l'expression régulière équivalente: description ~* 'abc'. Cela est important pour les analyses séquentielles où l'expression doit être évaluée pour chaque ligne testée.

Mais pour les grands tableaux comme vous le démontrez dans votre réponse, on utiliserait certainement un index. Pour les modèles arbitraires (non seulement ancrés à gauche), je suggère un index de trigramme en utilisant le module supplémentaire pg_trgm . Ensuite, nous parlons de millisecondes au lieu de secondes et la différence entre les expressions ci-dessus est annulée.

Les index GIN et Gist (utilisant les classes d'opérateur gin_trgm_ops Ou Gist_trgm_ops) Prennent en charge LIKE (~~), ILIKE (~~*), ~, ~* (Et quelques autres variantes). Avec un index GIN trigramme sur description (généralement plus grand que Gist, mais plus rapide pour les lectures), votre requête utiliserait description ILIKE 'case_insensitive_pattern'.

En relation:

Principes de base de la correspondance de motifs dans Postgres:

Lorsque vous travaillez avec ledit index de trigramme, il est généralement plus pratique de travailler avec:

description ILIKE '%abc%'

Ou avec l'opérateur d'expression rationnelle insensible à la casse (sans caractères génériques %):

description ~* 'abc'

Un index sur (description) Ne prend pas en charge les requêtes sur lower(description) comme:

lower(description) LIKE '%abc%'

Et vice versa.

Avec des prédicats sur lower(description) exclusivement, l'index d'expression est l'option légèrement meilleure.

Dans tous les autres cas, un index sur (description) Est préférable car il prend en charge les deux prédicats sensibles à la casse et sensibles à la casse.

20
Erwin Brandstetter

Selon mes tests ( dix de chaque requête), LOWERLIKE est environ 17% plus rapide que iLIKE.

Explication

J'ai créé un million de lignes contenant des données de texte mixtes aléatoires:

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)

Vérifiez le nombre de lignes:

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

(Oui, j'ai neuf lignes supplémentaires provenant d'autres tests - ce n'est pas un problème.)

Exemple de requête et de résultats:

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

Résultats pour 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

Résultats pour 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

Divulgation d'informations sur la base de données

Version 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

Paramètre de classement:

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

Définition du tableau:

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)
25
user664833

Dans mon projet Rails. ILIKE est presque 10 fois plus rapide que LOWER LIKE, J'ajoute un index GIN sur entities.name colonne

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

L'index GIN est vraiment utile pour améliorer les performances de ILIKE

0
lfx_cool