Comment les performances des deux composants de requête suivants se comparent-elles?
PLUS BAS COMME
... LOWER(description) LIKE '%abcde%' ...
iLIKE
... description iLIKE '%abcde%' ...
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.
Selon mes tests ( dix de chaque requête), LOWER
LIKE
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)
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