Postgres LIKE查询触发全表扫描



我有一个Postgres查询,我们有几个索引设置,包括一个文本字段,我们有一个GIN索引。基于pg_trgm文档,我对这一点的理解是,它仅适用于搜索字符串由字母数字文本组成的情况。测试证明了这一点,在拥有数千万条记录的数据库中,执行以下操作效果非常好:

SELECT * FROM my_table WHERE target_field LIKE '%foo%'

我在不同的地方读到过,任何不是字母数字字符串的东西在三元组搜索中都被视为一个单独的单词,所以像下面这样的东西也很好地工作:

SELECT * FROM my_table WHERE target_field LIKE '%foo & bar%'

然而,有人运行了一个搜索,字面上只是三个问号一行,它触发了一个全表扫描。由于某些原因,当在查询中单独使用多个&符号或问号时,它们的处理方式与放置在实际字母数字字符旁边或中间的单个&符号不同。

我所做的研究表明,这可能是一些数据库驱动程序处理问号的方式,有时将其解释为需要提供的参数,但随后会感到困惑,因为它找不到参数并触发表扫描。我不太相信这是真的。我可能倾向于相信它会抛出一个错误,而不是完成查询,但无论如何运行它似乎是一个设计缺陷。

更有意义的是问号不是字母数字字符,因此它被区别对待。在某些技术中,常见的符号如&都被认为是字母数字,但我不认为Postgres是这样的。事实上,文档建议在基于gin的索引中将非字母数字字符视为单词边界。

奇怪的是我可以搜索%foo & bar%,这似乎工作得很好。我甚至可以搜索%&%,它很快返回,虽然不是我想要的结果。但是,如果我把(例如)三个像这样放在一起:%&&&%,它会触发一个全表扫描。

在进行了各种实验之后,我看到了以下结果:

  1. %%:使用索引
  2. %&%使用索引
  3. %?%使用索引
  4. %foo & bar%使用索引
  5. %foo ? bar%使用索引
  6. %foo && bar%使用索引
  7. %foo ?? bar%使用索引
  8. %&&%:触发全表扫描
  9. %??%:触发全表扫描
  10. %foo&bar%:使用索引,但不返回结果

我认为所有这些都是有意义的,直到你看到#8和#9。如果&符号是一个单词边界,那么#10不应该返回结果吗?

谁能解释一下为什么多个连续的标点符号与单个标点符号的区别?

我无法在v11中复制这个充满md5哈希值的表:我获得前3个模式的seq扫描(全表扫描)。

如果我通过设置enable_seqscan=false来强制它们使用索引,那么我就去让它使用索引,但它实际上比做seq扫描要慢。所以它做出了正确的决定。你呢?你不应该强迫它使用索引,当它实际上更慢的时候。

看到它认为所有这些示例将返回的估计行数将会很有趣。

实际上,文档建议在基于gin的索引中将非字母数字字符视为单词边界。

GIN中的G表示"一般化"。你不能对一般化的事物做出这样的笼统陈述。它们甚至根本不需要对文本进行操作。但是在您的例子中,您使用的是LIKE运算符,而LIKE运算符并不关心单词边界。任何声称支持LIKE操作符的GIN索引都必须为LIKE操作符返回正确的结果。如果它不能这样做,那么它声称支持它就是一个bug。

pg_trgm处理&然后呢?在提取三元组时,它与空白相同,但它必须将LIKE与此决定的影响隔离开来。它通过两种方法做到这一点。一是它返回&;maybe &;这意味着必须重新检查它报告的所有元组,以确定它们是否确实满足LIKE。所以'%foo&bar%'和'%foo&Bar %'将向堆扫描返回相同的元组集合,但是堆扫描将重新检查它们,因此最终将返回不同的元组集合给用户,这取决于哪些元组在重新检查中幸存下来。第二件事是,如果pg_trgm不能从查询字符串中提取任何三元组,那么它必须返回整个表,然后重新检查。这就是'%%','%?%, % ? ?%’等。当然,重新检查所有行比首先进行seq扫描要慢。

最新更新