8

我想从导入 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 扫描?

4

1 回答 1

16

不可变的变体unaccent()

为了澄清当前接受的不正确答案中的错误信息:
表达式索引仅允许IMMUTABLE函数(出于明显的原因)并且unaccent()STABLE. 您在评论中建议的解决方案也有问题。详细解释和适当的解决 方案

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

实际问题/解决方案

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

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

为什么?

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

如果IMMUTABLE的右边有一个函数LIKE,则可以立即对其进行评估,并且仍然可以进行所述优化。根据函数波动率类别的文档

IMMUTABLE...
当查询使用常量参数调用该函数时,此类别允许优化器预先评估该函数。

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

重申:

  • 如果您想使用LIKE和 模式,请使用trigram index
  • 如果您不想使用LIKEand 模式,请使用相等运算符=
于 2015-03-06T17:01:21.177 回答