【问题标题】:How to speed up SELECT .. LIKE queries in MySQL on multiple columns?如何加快 SELECT .. LIKE 在 MySQL 中对多列的查询?
【发布时间】:2011-01-03 18:39:40
【问题描述】:

我有一个 MySQL 表,我经常对其进行SELECT x, y, z FROM table WHERE x LIKE '%text%' OR y LIKE '%text%' OR z LIKE '%text%' 查询。任何类型的索引都有助于加快速度吗?

表中有几百万条记录。如果有什么可以加快搜索速度,它会严重影响数据库文件的磁盘使用率以及INSERTDELETE 语句的速度吗? (从未执行过UPDATE

更新:发帖后很快看到很多关于LIKE在查询中的使用方式的信息和讨论;我想指出,解决方案必须使用LIKE '%text%'(也就是说,我要查找的文本前面加上了% 通配符)。出于多种原因,包括安全性,数据库也必须是本地的。

【问题讨论】:

  • 这样想并不是一个完美的类比,但它证明了这一点。考虑一本背面有单词索引的书。如果您想查找一个简单的单词,甚至是以相同的前几个字母开头的单词,没问题。考虑一下当您想使用该书籍索引来查找单词中任何位置包含字母“exe”的所有单词时会发生什么。该索引几乎没有用,您不妨阅读实际的书来寻找这些词。这大致就是您对 mySQL 施加的问题。

标签: mysql sql-like


【解决方案1】:

索引不会加快查询速度,因为对于文本列,索引是通过从左侧开始索引 N 个字符来工作的。当您执行 LIKE '%text%' 时,它不能使用索引,因为文本之前可以有可变数量的字符。

你应该做的是根本不使用这样的查询。相反,您应该使用 MySQL 支持的 MyISAM 表的 FTS(全文搜索)之类的东西。自己为非 MyISAM 表制作这样的索引系统也很容易,您只需要一个单独的索引表,在实际表中存储单词及其相关 ID。

更新

全文搜索可用于 MySQL 5.6+ 的 InnoDB 表。

【讨论】:

  • 我应该补充一点,从 MySQL 5.6 开始,InnoDB 也可以进行全文搜索。
  • 在某些情况下无法进行全文搜索,例如使用表意语言。 dev.mysql.com/doc/refman/5.7/en/fulltext-restrictions.html
  • 请注意,FULLTEXT 还允许您同时进行所有 3 个测试:MATCH(x, y, z) AGAINST('text')
  • 全文搜索使用单词边界而不是通配符搜索
  • 我不明白为什么这个答案被接受,因为提供的灵魂不是 OP 所要求的,它必须与%string% 一样工作,并且全文不会像搜索那样做通配符
【解决方案2】:

索引不会帮助文本与前导通配符匹配,索引可用于:

LIKE 'text%'

但我猜这不会削减它。对于这种类型的查询,如果您想扩展可以搜索的记录数量,您真的应该查看全文搜索提供程序。我的首选提供商是Sphinx,功能非常齐全/速度很快等。Lucene 也可能值得一看。 MyISAM 表上的全文索引也可以工作,但最终为任何具有大量写入的数据库使用 MyISAM 并不是一个好主意。

【讨论】:

【解决方案3】:

在搜索条件以通配符开头的情况下,索引不能用于加速查询:

LIKE '%text%'

索引可以(并且可能,取决于选择性)用于以下形式的搜索词:

LIKE 'text%'

【讨论】:

    【解决方案4】:

    添加全文索引并使用MATCH() AGAINST()

    普通索引无法帮助您处理 like 查询,尤其是那些在搜索词两边都使用通配符的查询。

    您可以在有兴趣搜索的列上添加全文索引,然后使用MATCH() AGAINST() 查询来搜索这些全文索引。

    1. 在您需要的列上添加全文索引:

      ALTER TABLE table ADD FULLTEXT INDEX index_table_on_x_y_z (x, y, z);
      
    2. 然后查询那些列:

      SELECT * FROM table WHERE MATCH(x,y,z) AGAINST("text")
      

    根据我们的试验,我们发现这些查询在包含超过 100 万条记录的表中大约需要 1 毫秒。不错,尤其是与需要 16,400 毫秒的等效通配符 LIKE %text% 查询相比。

    基准测试

    MATCH(x,y,z) AGAINST("text") 需要 1 毫秒

    LIKE %text% 需要 16400 毫秒

    快 16400 倍!

    【讨论】:

    • 你没有提到 MATCH, AGAINST 和 like wildcard 不一样!就像 where "=",性能更高。
    • @Dev'Hamz 你确定吗?
    • 是的,我测试过了
    • 有趣。在我们的测试中,使用MATCH(x,y,z) AGAINST("text") 给了我们与LIKE %text% 相同的返回结果,除了它使用FULLTEXT 索引,因此它更快。 MATCH AGAINST 不是天生就没有使用通配符还是我遗漏了什么?
    • @JoshuaPinter 它不会找到包含“我正在发短信”字符串的行。所以不,全文和通配符不一样
    【解决方案5】:

    我要补充一点,在某些情况下,如果您正在查看的字段通常为空或包含常量,您可以使用索引和 like/rlike 来加快查询速度。

    在这种情况下,您似乎可以通过添加具有固定值的“and”子句来限制使用索引访问的行。

    我尝试在一个通常不包含很多标签的大表中搜索“标签”。

    SELECT * FROM objects WHERE tags RLIKE("((^|,)tag(,|$))" AND tags!=''

    如果您有标签索引,您会看到它用于限制正在搜索的行。

    【讨论】:

    • 这出人意料地是一个非常好的替代方案,我能够以这种方式索引列。
    • 更好:RLIKE '[[:<:]]tag[[:>:]]'——即使用“单词边界”。
    • @RickJames 这不是关于正则表达式,而是关于在查询中添加 AND。除此之外,使用的正则表达式取决于数据。如果您的标签包含空格,您将不会对单词边界感到满意。我也不确定在微基准级别上搜索单词边界是否会更快。
    • @OderWat - 一个小测试:SELECT "What about this?" REGEXP "[[:<:]]about this[[:>:]]";1 (true)。
    • @RickJames 当然这是真的。但是使用单词分隔符对于从字段中搜索标签仍然不实用。想想:SELECT "white lion steak,lion steak,white" REGEXP "[[:<:]]white lion[[:>:]]"。这也是正确的,但可能不应该。这完全取决于您的数据是如何存储的。
    【解决方案6】:

    也许你可以尝试将mysql5.1升级到mysql5.7。

    我有大约 70,000 条记录。并运行以下 SQL:

    select * from comics where name like '%test%'; 
    

    在mysql5.1中需要2000ms。 在mysql5.7或mysql5.6中需要200ms

    【讨论】:

    • 这真的闻起来好像并非所有数据都缓存在 5.1 上的 buffer_pool 中。 10:1 是一个典型的因素。从 5.1 到 5.6,这方面的内容没有任何变化。
    【解决方案7】:

    另一种方式:

    您可以使用这些字符串 REVERSEd 来维护计算列并使用

    SELECT x, y, z FROM table WHERE x LIKE 'text%' OR y LIKE 'text%' OR z LIKE 'text%' OR xRev LIKE 'txet%' OR yRev LIKE 'txet%' OR zRev LIKE 'txet%' 
    

    如何添加存储的持久列的示例

    ALTER TABLE table ADD COLUMN xRev VARCHAR(N) GENERATED ALWAYS AS REVERSE(x) stored;
    

    然后在xRevyRev等上创建索引。

    【讨论】:

    • 是的,但那不是内部通配符搜索 %s%
    【解决方案8】:

    另一种避免全表扫描的方法是选择子字符串并在 have 语句中检查它们:

    SELECT 
        al3.article_number,
        SUBSTR(al3.article_number, 2, 3) AS art_nr_substr,
        SUBSTR(al3.article_number, 1, 3) AS art_nr_substr2,
        al1.*
    FROM
        t1 al1 
        INNER JOIN t2 al2 ON al2.t1_id = al1.id
        INNER JOIN t3 al3 ON al3.id = al2.t3_id
    WHERE
        al1.created_at > '2018-05-29'
    HAVING 
        (art_nr_substr = "FLA" OR art_nr_substr = 'VKV' OR art_nr_subst2 = 'PBR');
    

    【讨论】:

    • 没有。真正的成本是需要查看很多行。您没有更改行数。
    猜你喜欢
    • 1970-01-01
    • 2021-03-15
    • 1970-01-01
    • 2017-12-23
    • 1970-01-01
    • 2016-01-13
    • 1970-01-01
    • 1970-01-01
    • 2020-12-10
    相关资源
    最近更新 更多