各位观众老爷们,晚上好!我是你们的老朋友,今天咱们来聊聊MySQL里一个既让人爱又让人恨的家伙:LIKE '%keyword%'
。
开场白:爱恨交织的LIKE
LIKE '%keyword%'
,这玩意儿在模糊搜索里简直是居家旅行必备,哪里需要模糊搜,哪里就有它的身影。你想想,用户在搜索框里随便敲几个字,你的系统就能嗖嗖嗖地把相关结果都找出来,用户体验简直不要太好。
但是,就像所有美好的事物一样,LIKE '%keyword%'
也有它的阴暗面——性能。当你的数据量小的时候,它可能还能凑合用,但是当数据量蹭蹭蹭地往上涨,几百万、几千万甚至上亿的时候,LIKE '%keyword%'
就会变成你的噩梦。你会发现,查询速度慢到让人怀疑人生,CPU占用率高到服务器风扇狂转。
所以,今天咱们就来扒一扒LIKE '%keyword%'
的皮,看看它到底是怎么拖慢速度的,以及,更重要的是,有什么更好的办法来替代它。
LIKE '%keyword%'
的性能瓶颈:全表扫描
要理解LIKE '%keyword%'
的性能问题,首先要明白它背后的原理。当你使用LIKE '%keyword%'
的时候,MySQL实际上会进行全表扫描。
啥叫全表扫描?简单来说,就是MySQL会一行一行地检查你的数据表,看看每一行是不是包含你搜索的关键词。这就像你在一堆书里找一本包含特定词语的书一样,你必须从第一本开始,一本一本翻,直到找到为止。
如果你的数据表只有几百行,那全表扫描可能还能接受。但是如果你的数据表有几百万行,甚至更多,那全表扫描就会变得非常耗时。因为MySQL需要读取每一行的数据,然后进行比较,这会消耗大量的CPU和IO资源。
更糟糕的是,LIKE '%keyword%'
通常无法使用索引。索引就像书的目录一样,可以帮助你快速找到想要的书。但是LIKE '%keyword%'
的查询条件是模糊的,MySQL无法利用索引来加速查询,只能乖乖地进行全表扫描。
代码示例:LIKE '%keyword%'
的慢查询
为了更直观地感受LIKE '%keyword%'
的性能问题,咱们来写一个简单的代码示例。
首先,创建一个包含大量数据的表:
CREATE TABLE `article` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`title` varchar(255) NOT NULL DEFAULT '' COMMENT '文章标题',
`content` text NOT NULL COMMENT '文章内容',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章表';
然后,插入100万条数据:
-- 模拟插入数据的存储过程 (这里仅为伪代码,实际需要编写MySQL存储过程)
DELIMITER //
CREATE PROCEDURE insert_article(IN num INT)
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= num DO
INSERT INTO article (title, content) VALUES (CONCAT('文章标题', i), CONCAT('文章内容', REPEAT('测试', 100), i));
SET i = i + 1;
END WHILE;
END //
DELIMITER ;
CALL insert_article(1000000);
现在,执行一个LIKE '%keyword%'
的查询:
SELECT * FROM article WHERE content LIKE '%测试12345%';
你会发现,这个查询需要花费很长的时间才能完成。这就是LIKE '%keyword%'
的威力,它能让你的数据库瞬间瘫痪。
全文索引:救星来了!
既然LIKE '%keyword%'
这么慢,那有没有什么更好的办法来替代它呢?答案是肯定的,那就是全文索引。
全文索引是一种特殊的索引,它可以帮助你快速地搜索文本数据。与LIKE '%keyword%'
不同,全文索引不需要进行全表扫描,它可以利用索引来加速查询。
全文索引的原理:倒排索引
全文索引的原理是倒排索引。倒排索引是一种将文档中的词语映射到文档ID的数据结构。
举个例子,假设你有以下两个文档:
- 文档1: "The quick brown fox jumps over the lazy dog"
- 文档2: "The lazy cat sleeps on the mat"
那么,倒排索引就会是这样的:
- The: 1, 2
- quick: 1
- brown: 1
- fox: 1
- jumps: 1
- over: 1
- lazy: 1, 2
- dog: 1
- cat: 2
- sleeps: 2
- on: 2
- mat: 2
当你搜索"lazy"的时候,全文索引会直接找到包含"lazy"的文档ID:1和2,然后返回这两个文档。
使用全文索引的步骤
要在MySQL中使用全文索引,需要经过以下几个步骤:
-
创建全文索引
在创建表的时候,可以指定哪些列需要创建全文索引:
CREATE TABLE `article` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `title` varchar(255) NOT NULL DEFAULT '' COMMENT '文章标题', `content` text NOT NULL COMMENT '文章内容', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), FULLTEXT INDEX `idx_content` (`content`) -- 创建全文索引 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章表';
或者,在已有的表上创建全文索引:
ALTER TABLE `article` ADD FULLTEXT INDEX `idx_content` (`content`);
-
使用
MATCH...AGAINST
进行查询使用
MATCH...AGAINST
语法来进行全文搜索:SELECT * FROM article WHERE MATCH (content) AGAINST ('测试12345');
MATCH (content)
指定要搜索的列,AGAINST ('测试12345')
指定要搜索的关键词。
全文索引的注意事项
FULLTEXT
索引只能用于MyISAM
和InnoDB
存储引擎。MySQL 5.6 以后InnoDB存储引擎开始支持全文索引。FULLTEXT
索引只能用于CHAR
、VARCHAR
和TEXT
类型的列。- 全文索引有最小长度限制,默认情况下,最小长度为4个字符。这意味着,如果你的关键词长度小于4个字符,全文索引可能无法正常工作。可以通过修改
ft_min_word_len
参数来调整最小长度。 - 全文索引会忽略停用词。停用词是指那些在文本中频繁出现,但是没有实际意义的词语,比如"the"、"a"、"is"等等。MySQL有一个默认的停用词列表,你可以根据自己的需要修改这个列表。
- 全文索引的搜索结果可以按照相关性排序。
MATCH...AGAINST
语法会返回一个相关性分数,你可以使用ORDER BY
子句按照相关性分数进行排序。
代码示例:全文索引的快速查询
让我们再次使用之前的article
表,这次我们使用全文索引来进行查询:
SELECT * FROM article WHERE MATCH (content) AGAINST ('测试12345');
你会发现,这个查询的速度比使用LIKE '%keyword%'
快得多。这就是全文索引的威力,它可以让你在海量数据中快速地找到你想要的结果。
更高级的全文索引技巧
除了基本的全文搜索之外,MySQL还提供了一些更高级的全文索引技巧:
-
布尔全文搜索
布尔全文搜索允许你使用布尔运算符(比如
+
、-
、>
、<
)来组合多个关键词。例如,要搜索包含"测试"但不包含"12345"的文章,可以使用以下查询:
SELECT * FROM article WHERE MATCH (content) AGAINST ('+测试 -12345' IN BOOLEAN MODE);
+
表示必须包含-
表示必须不包含>
表示提高相关性<
表示降低相关性
-
查询扩展
查询扩展允许你根据初始查询结果,自动扩展查询范围。
例如,要搜索与"测试"相关的文章,可以使用以下查询:
SELECT * FROM article WHERE MATCH (content) AGAINST ('测试' WITH QUERY EXPANSION);
查询扩展会根据初始查询结果,找到一些与"测试"相关的词语,然后将这些词语添加到查询条件中,从而扩大查询范围。
其他替代方案:ngram 分词
除了全文索引,还有一些其他的替代方案可以用来解决LIKE '%keyword%'
的性能问题。其中,比较常用的是 ngram 分词。
ngram 分词是一种将文本分割成固定长度的片段的技术。例如,对于字符串 "hello",如果使用 2-gram 分词,就会得到以下片段:
- he
- el
- ll
- lo
然后,你可以将这些片段存储到数据库中,并建立索引。当你进行模糊搜索的时候,可以将搜索关键词也进行 ngram 分词,然后搜索包含这些片段的记录。
ngram 分词的优点是实现简单,适用于各种语言。缺点是索引体积较大,且搜索精度可能不高。
代码示例:ngram 分词
这里给出一个简单的 ngram 分词的示例(仅为演示,实际应用需要更完善的处理):
- 创建辅助表:
CREATE TABLE `article_ngram` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`article_id` bigint(20) unsigned NOT NULL COMMENT '文章ID',
`ngram` varchar(255) NOT NULL COMMENT 'ngram片段',
PRIMARY KEY (`id`),
INDEX `idx_article_id` (`article_id`),
INDEX `idx_ngram` (`ngram`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章ngram表';
-
插入数据 (需要编程实现): 将
article
表中的内容进行 ngram 分词,然后插入到article_ngram
表中。 这里以2-gram为例。 -
查询:
-- 假设要搜索 '测试12'
SELECT DISTINCT a.id, a.title, a.content
FROM article a
JOIN article_ngram ng1 ON a.id = ng1.article_id AND ng1.ngram = '测试'
JOIN article_ngram ng2 ON a.id = ng2.article_id AND ng2.ngram = '试12';
这个查询会将 "测试12" 分成 "测试" 和 "试12" 两个片段,然后分别搜索包含这两个片段的文章。
总结:选择合适的方案
LIKE '%keyword%'
虽然简单易用,但是在处理海量数据的时候,性能会变得非常糟糕。为了解决这个问题,我们可以使用全文索引、ngram 分词等替代方案。
选择哪种方案取决于你的具体需求:
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
全文索引 | 搜索速度快,相关性排序,支持布尔搜索,查询扩展 | 只能用于特定存储引擎和数据类型,需要维护停用词列表 | 需要高性能模糊搜索,对搜索精度要求较高,数据量较大 |
ngram 分词 | 实现简单,适用于各种语言 | 索引体积较大,搜索精度可能不高 | 对性能要求不高,对搜索精度要求不高,数据量较小 |
希望今天的讲座能帮助你更好地理解LIKE '%keyword%'
的性能问题,并选择合适的替代方案。记住,没有银弹,只有最合适的解决方案。
感谢各位的观看!下次再见!