【问题标题】:unaccent() preventing index usage in Postgresunaccent() 防止在 Postgres 中使用索引
【发布时间】:2015-03-06 12:42:43
【问题描述】:

我想从导入 PostgreSQL 9.3.5 的 OpenStreetMap 数据库中检索具有给定名称的方式,操作系统是 Win7 64 位。为了有点容错,我使用了 Postgres 的 unaccent 扩展。

我的查询如下所示:

SELECT * FROM germany.ways
WHERE lower(tags->'name') like lower(unaccent('unaccent','Weststrasse'))

查询计划:

Seq Scan on ways  (cost=0.00..2958579.31 rows=122 width=465)
  Filter: (lower((tags -> 'name'::text)) ~~ lower(unaccent('unaccent'::regdictionary, 'Weststrasse'::text)))

奇怪的是,这个查询使用顺序扫描方式,尽管lower(tags->'name') 上存在索引:

CREATE INDEX ways_tags_name ON germany.ways (lower(tags -> 'name'));

一旦我从查询中删除 unaccent,Postgres 就会使用索引:

SELECT * FROM germany.ways
WHERE lower(tags->'name') like lower('Weststrasse')

查询计划:

Index Scan using ways_tags_name on ways  (cost=0.57..495.43 rows=122 width=465)
  Index Cond: (lower((tags -> 'name'::text)) = 'weststrasse'::text)
  Filter: (lower((tags -> 'name'::text)) ~~ 'weststrasse'::text)

为什么 unaccent 会阻止 Postgres 使用索引?在我看来,这没有意义,因为在执行实际查询之前,应该已经完全知道 unaccent 的结果(删除变音符号等)。所以 Postgres 应该可以使用索引。使用 unaccent 时如何避免 seq 扫描?

【问题讨论】:

  • tags 的数据类型必须为hstore。这应该是问题所在,最好提供表定义(您从 psql 中的\d tbl 获得的内容)。

标签: sql postgresql indexing openstreetmap unaccent


【解决方案1】:

unaccent() 的不可变变体

澄清currently accepted, incorrect answer中的错误信息:
表达式索引只允许IMMUTABLE 函数(出于显而易见的原因),而unaccent() 仅允许STABLEsolution you suggested in the the comment 也有问题。详细解释和适当的解决方案

根据tags->name 的内容,将unaccent() 添加到表达式索引可能会很有用,但这与为什么不使用索引的问题是正交的:

实际问题/解决方案

您查询中的运算符LIKE 有轻微的错误(很可能)。您确实想将“Weststrasse”解释为搜索模式,您想按原样匹配(标准化)字符串。替换为 = 运算符,您将看到(位图)索引扫描与您当前的索引,不考虑 unaccent() 的函数波动:

SELECT * FROM germany.ways
WHERE lower(tags->'name') = lower(unaccent('unaccent','Weststrasse'))

为什么?

LIKE 的右操作数是一个模式。 Postgres 不能使用普通的 btree 索引进行模式匹配 (exceptions apply)。可以通过对 btree 索引的相等性检查来优化具有纯字符串作为模式(无特殊字符)的 LIKE。但是如果字符串中有特殊字符,this索引就出来了。

如果LIKE右侧有IMMUTABLE函数,可以立即求值,上述优化仍然可以。每documentation on Function Volatility Categories

IMMUTABLE ...
此类别允许优化器在以下情况下预先评估函数 查询使用常量参数调用它。

对于较小的函数波动性(STABLEVOLATILE),这是不可能的。这就是为什么你伪装IMMUTABLE unaccent() 的“解决方案”似乎有效,但它实际上是在给猪涂口红。

重申:

  • 如果您想使用LIKE 和模式,请使用trigram index
  • 如果您不想使用LIKE 和模式,请使用等式运算符=

【讨论】:

  • 哇,很详细的解释。谢谢。但是,我确实出于某种目的使用LIKE。这可能应该放在另一个问题中,但 OSM 中的名称通常倾向于包含有趣的字符。所以我用下划线_ 替换它们,以实现更模糊的匹配。我在发布之前稍微简化了查询,但 LIKE 操作数仍然存在。知道如何使用支持索引的 = 运算符并忽略像 `/()-.' 这样的字符。在查询中?
  • @AlfKortig:我只能回答您发布的问题。你问为什么,我解释了。许多人在这里阅读并希望学到一些东西。当前接受的答案不正确且具有误导性。你是对的,你的新问题应该放在一个新问题中。评论不是地方。在手头的情况下,简化是错误的。一定要给出完整、正确的图片,并在 psql 中添加\d tbl 的输出。您始终可以链接到此以获取上下文。您可以在此处发表评论以引起我的注意。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-26
  • 2012-08-25
  • 1970-01-01
相关资源
最近更新 更多