跨多个列 PostgreSQL/Rails 的慢速通配符搜索 LIKE
Posted
技术标签:
【中文标题】跨多个列 PostgreSQL/Rails 的慢速通配符搜索 LIKE【英文标题】:Slow wildcard search LIKE across multiple columns PostgreSQL/Rails 【发布时间】:2021-12-05 20:44:47 【问题描述】:我正在尝试优化这些慢查询(请原谅 SQL 与 Ruby on Rails 混合):
WHERE name ILIKE %<the user's search text>%
WHERE lower(NAME) LIKE :search OR lower(BARCODE) LIKE :search OR lower(SKU) like :search, search: "%<the user's search text>%"
如您所见,这些都是以%
开头和结尾的通配符查询,这意味着正常的索引是无用的。该表由项目组成,当用户的项目不是很多时查询很好,但是当用户有很多项目(数万)时,这需要很长时间(如数十秒)。我怎样才能提高性能?搜索文本是条形码或产品名称的一部分,因此它与我只是尝试搜索文本不同(在这种情况下我会使用全文搜索,以便搜索“狗”会产生包含'dogs' 或 'doggy' 等)。在其中一个用例中,我也在同一张表的多个列中进行搜索。
我考虑过的一些初步方法,但不确定这些方法是否可行:
全文搜索(添加一列是要搜索的多个列中的to_tsvector
,然后为新列添加 gin 索引)
三元索引(更合适?)
我没有想到的其他建议
我正在使用 PostgreSQL 13 和 Ruby on Rails。
【问题讨论】:
AFAIK 三元组索引是您想要的。一个快速的检查方法是获取你要使用的 SQL (Model.where(...).to_sql
),查看psql
中的explain the_sql
(你可能会看到表扫描),然后添加索引并查看再次在 EXPLAIN 输出(您应该会看到它正在查看新索引)。
【参考方案1】:
就像 mu 已经暗示的那样:在目标列上创建一个三元组索引。第一个例子:
CREATE INDEX tbl_name_gin_trgm_idx ON tbl USING gin (name gin_trgm_ops);
三元索引也支持不区分大小写的匹配,但要配合普通的三元索引,将第二个例子重写为ILIKE
:
WHERE name ILIKE :search OR barcode ILIKE :search OR sku ILIKE :search
您可以使用上面示例中的三个索引来支持这一点。这是最通用的。比如,在同一个搜索中可能会组合不同的列。
或者一个索引包含三个索引列。通用性较差,但速度更快、体积更小。
或一个三元组索引和一个连接表达式。用途最少,但速度最快。使用此处定义和解释的自定义函数immutable_concat_ws()
:
CREATE INDEX tbl_special_gin_trgm_idx ON tbl USING gin (immutable_concat_ws('|', name, barcode, sku) gin_trgm_ops);
在查询中结合WHERE
子句:
WHERE immutable_concat_ws('|', name, barcode, sku) ILIKE :search
AND (name ILIKE :search OR barcode ILIKE :search OR sku ILIKE :search)
WHERE
子句的第一行引入了快速索引。
如果:search
与分隔符匹配,则第二行排除可能的(罕见的)误报。
如果 :search
从不 包含所选的分隔符 |
也不包含嵌套通配符,我们可以删除第二行。
见:
PostgreSQL LIKE query performance variations
LOWER LIKE vs iLIKE
String concatenation using operator "||" or format() function
Query performance with concatenation and LIKE
【讨论】:
以上是关于跨多个列 PostgreSQL/Rails 的慢速通配符搜索 LIKE的主要内容,如果未能解决你的问题,请参考以下文章