73

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

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

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

4

9 回答 9

82

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

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

更新

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

于 2010-01-11T14:10:15.013 回答
27

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

LIKE 'text%'

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

于 2010-01-11T14:09:36.937 回答
21

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

LIKE '%text%'

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

LIKE 'text%'

于 2010-01-11T14:08:33.277 回答
18

添加全文索引并使用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 毫秒。不错,尤其是与LIKE %text%需要 16,400 毫秒的等效通配符查询相比。

基准

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

LIKE %text%需要 16400 毫秒

快 16400 倍!

于 2019-12-29T21:25:58.443 回答
12

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

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

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

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

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

于 2013-11-26T14:59:42.400 回答
6

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

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

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

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

于 2017-06-06T04:45:09.493 回答
4

其他方式:

您可以使用这些字符串 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;

然后在xRev等上创建yRev索引

于 2019-01-09T10:23:57.677 回答
0

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

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');
于 2018-06-08T09:16:20.320 回答
0

优化SELECT foo FROM bar WHERE baz LIKE 'ZOT%'查询时,您希望索引长度至少与请求中的字符数匹配。

这是刚刚的一个真实的例子:

这是查询:

EXPLAIN SELECT COUNT(*) FROM client_detail cd
JOIN client_account ca ON cd.client_acct_id = ca.client_acct_id
WHERE cd.first_name LIKE 'XX%' AND cd.last_name_index LIKE 'YY%';

没有索引:

+-------+
| rows  |
+-------+
| 13994 |
|     1 |
+-------+

所以首先尝试一个 4x 索引,

CREATE INDEX idx_last_first_4x4 on client_detail(last_name_index(4), first_name(4));
+------+
| rows |
+------+
| 7035 |
|    1 |
+------+

好一点,但 COUNT(*) 显示只有 102 个结果。所以现在让我们添加一个 2x 索引:

CREATE INDEX idx_last_first_2x2 on client_detail(last_name_index(2), first_name(2));

产量:

+------+
| rows |
+------+
|  102 |
|    1 |
+------+

此时两个索引仍然存在,并且 MySQL 为该查询选择了后一个索引 --- 但是,如果它更有效,它仍然会选择 4x4 查询。

索引排序可能很有用,在 4x4 之前尝试 2x2,反之亦然,看看它在您的环境中的表现如何。要重新排序索引,您必须删除并重新创建较早的索引。

于 2022-02-08T22:56:53.253 回答