跨多个列 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 an immutable clone of 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的主要内容,如果未能解决你的问题,请参考以下文章

跨 MYSQL 中的多个列选择 SUM(DISTINCT 值)

scikit-learn 中跨多个列的标签编码

跨多个列的 SQL 联接

跨多个列变异以创建新的变量集

有啥建议可以加快慢速地理查询吗?

在python中跨多个列表查找列的最小值