【问题标题】:Optimization of MySQL search using "like" and wildcards使用“like”和通配符优化 MySQL 搜索
【发布时间】:2011-01-06 02:56:38
【问题描述】:

如何查询像

SELECT * FROM sometable WHERE somefield LIKE '%value%'

优化?

这里的主要问题是第一个阻止 DBMS 使用索引的通配符。

编辑:更重要的是,somefield 值是实心字符串(不是一段文本),因此无法执行全文搜索。

【问题讨论】:

  • 如果您需要在值中查找子字符串,您的数据库设置可能会经过调整,以便为您提供更好的选择。您能否提供您在某些上下文中实际使用的数据/查询示例。

标签: mysql optimization wildcard


【解决方案1】:

你的琴弦有多长?

如果它们相对较短(例如英文单词;avg_len=5)并且您有可用的数据库存储空间,请尝试以下方法:

  • 对于要存储在表中的每个单词,取该单词的所有可能后缀。换句话说,你一直在剥离第一个字符,直到什么都没有。例如,单词value 给出:
    • value
    • alue
    • lue
    • ue
    • e
  • 在数据库中存储每个这些后缀。
  • 您现在可以使用 LIKE 'alu%' 搜索子字符串(它会查找 'alu' 作为 'value' 的一部分)。

通过存储所有后缀,您无需使用前导通配符(允许使用索引进行快速查找),但会占用存储空间。

存储成本

存储一个单词所需的字符数变为word_len*word_len / 2,即单词长度的二次方,以每个单词为基础。以下是各种字长的增加因素:

  • 三字词:(3*3/2) / 3 = 1.5
  • 5字词:(5*5/2) / 5 = 2.5
  • 7字词:(7*7/2) / 7 = 3.5
  • 12字词:(12*12/2) / 12 = 6

存储单词所需的行数从 1 增加到 word_len。请注意此开销。额外的列应保持在最低限度,以避免存储大量冗余数据。例如,最初找到该词的页码应该没问题(想想 unsigned smallint),但该词的大量元数据应该按每个词而不是每个后缀存储在单独的表中。

注意事项

我们在拆分“单词”(或片段)的位置进行权衡。作为一个真实的例子:我们如何处理连字符?我们将形容词five-letter 存储为一个词还是两个词?

取舍如下:

  • 任何被分解的东西都不能作为单个元素找到。如果我们分别存储fiveletter,搜索five-letterfiveletter会失败。
  • 分解的任何内容都将占用更多存储空间。记住,存储 要求在字长上呈二次方增长。

为方便起见,您可能希望删除连字符并存储fiveletter。现在可以通过搜索 fiveletterfiveletter 找到该词。 (如果您也从任何搜索查询中去掉连字符,用户仍然可以成功找到five-letter。)

最后,有一些存储后缀数组的方法不会产生太多开销,但我还不确定它们是否能很好地转换为数据库。

【讨论】:

  • 这是一个非常的好答案,也是解决问题的唯一答案。 (诚​​然,它的限制是您的字符串必须足够短,以至于您不介意将行数乘以平均字符串长度,但这可能是不可避免的。)
  • 现在是 2020 年,我正在考虑使用您的解决方案。你有任何更新吗?此外,是否必须为每个字符串截断列创建索引?如果是这样,查询是什么样的? SELECT * FROM user_table WHERE username_1 LIKE %string% OR username_2 LIKE %string% OR username_3 LIKE %string% ......?
  • 另见 ElasticSearch solves this(向下滚动到“它是如何工作的”)通过为每个字符串存储一堆 3 字符的 engram。这是一种可比较的方法,但具有存储成本的明显优势,即线性,而不是字大小的二次方。它以更复杂的查找逻辑为代价,并且在技术上降低了对查找性能的保证(由于潜在的误报)。
【解决方案2】:

两种方式:

(1) 使用内存表,因此运行速度非常快。

(2) 设计出比foo LIKE '%bar%' 更好的索引和搜索算法。在不了解您的问题的情况下,无法就此提出任何建议。

正如您所指出的,%bar% 模式保证每次查找都进行表扫描,这会抵消数据库软件中任何可能的搜索独创性。

【讨论】:

    【解决方案3】:

    使用Full Text Search。 “Initial Idea”标题具有相同的示例,并导致工作示例解决方案。

    And the MySQL docs

    编辑:它不能在 SQL 本身中进行调整。使用 LOCATE 或 PAINEX 之类的函数也无济于事。

    【讨论】:

    • 其实我不需要在文本中找到某个特定的词。我需要在值中找到子字符串(我将更新问题以澄清这一点)。
    • 整词与否都没有关系:你可以优化这个查询
    • 也许有更复杂的解决方案,而不仅仅是优化查询以更快地执行此类搜索。
    【解决方案4】:

    考虑到您的问题在于通配符,这不会有很大的不同,但不使用“SELECT *”会提高查询性能。如果您实际上并没有使用返回的所有字段,那是一个胜利,“SELECT *”会触发两个查询,一个是查找表的字段,然后是添加了字段名称的查询。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多