本文記錄中文模糊查詢優化的方向是 使模式匹配使用索引
有一張 2 千萬多的 user 表,其中需要按照 users.chinese_name 字段進行模糊查找。
啟用 pg_trgm 擴展
pg_trgm 模塊提供函數和操作符測定字母,數字,文本基于三元模型匹配的相似性, 還有支持快速搜索相似字符串的索引操作符類。
這里提到了一個三元模型,其實很簡單。打個比方 foo 的三元模型的集合為{" f"," fo","foo","oo "}, foo|bar 的三元模型的集合為{" f"," fo","foo","oo "," b"," ba","bar","ar "}。也就是說將字符串拆解成三個字符一組,每個字符串被認為有兩個空格前綴和一個空格后綴。
Postgres 使用 trigram 將字符串分解成更小的單元便于有效地索引它們。pg_trgm 模塊支持 GIST 或 GIN 索引,從 9.1 開始,這些索引支持 LIKE/ILIKE 查詢。
要使用 pg_trgm 模塊,首先要啟用該擴展,然后使用 gin_trgm_ops 創建索引
CREATE EXTENSION pg_trgm;
創建索引
在字段上創建 GIN 類型的索引可以處理包含多個鍵的值,如數組等. 與 GIST 類似, GIN支持用戶定義的索引策略,可以通過定義GIN索引的特定操作符類型實現不同的功能。 PostgreSQL的標準中發布了用于一維數組的GIN操作符類, 比如它支持 包含操作符 '@>'、被包含操作符 '<@'、相等操作符 '='、重疊操作符 '&&',等等。
但是這種索引對中文不起作用,需要把中文轉換成字節(ASCII碼),然后使用函數索引
create or replace function textsend_i (text) returns bytea as
$$
select textsend($1);
$$
language sql strict immutable;
CREATE INDEX trgm_idx_users_chinese_name ON users USING GIN(text(textsend_i(chinese_name)) gin_trgm_ops);
查詢語句
SELECT chinese_name FROM users WHERE text(textsend_i(chinese_name)) ~ ltrim(text(textsend_i('深圳')), '\x');
再優化
添加 GIN 索引后,查詢性能提升很多。如上所說,GIN 不支持中文,在查詢的時候,先把 chinese_name 字段轉化為 bytea,然后進行匹配。這里也耽誤了不少時間,我們可以在users 表上在添加一個 chinese_name_bytea 字段,存儲 chinese_name 的字節形式,然后直接在該字段上進行創建 GIN 索引。也算是一種空間換取時間的方式。
ALTER TABLE users;
ADD COLUMN chinese_name_bytea VARCHAR;
UPDATE users SET chinese_name_bytes = textsend(chinese_name);
CREATE INDEX trgm_idx_users_chinese_name_bytea ON users USING GIN(chinese_name_bytea gin_trgm_ops);
查詢時:
SELECT chinese_name FROM users WHERE chinese_name_bytea ~ ltrim(text(textsend_i('深圳')), '\x');