이름에 "xiao"가 포함된 사용자를 쿼리하는 것과 같은 퍼지 쿼리는 "%xiao%"와 같은 일반적인 작성 방법으로 MySQL의 전체 테이블을 스캔하며 데이터 양은 적고 전체 테이블 스캔도 매우 빠르며, 데이터가 증가하면 속도가 느려지고 ES가 매우 무거워집니다. 이 기사에서는 유사 퍼지 일치 쿼리(MySQL 전체 텍스트 인덱스)를 느리게 만드는 솔루션을 소개합니다.
요구 사항
단어를 쿼리하려면 퍼지 일치가 필요합니다
select * from t_phrase where LOCATE('chang',phrase) = 0;
select * from t_chinese_phrase where instr(phrase,'chang' ) > 0;
select * from t_chinese_phrase 여기서 '%长%'
실행 계획을 설명하고 살펴보세요
구문에 대한 색인을 구축했지만 쿼리할 때 색인이 유효하지 않습니다.
이유:
mysql의 인덱스는 B+ 트리 구조입니다. InnoDB는 퍼지 쿼리 데이터로 인해 인덱스가 실패할 때 "%xx"를 사용합니다(여기서는 자세히 설명하지 않겠습니다)
쿼리 시간 측면에서 소요 시간은 90ms입니다.
현재 데이터량 : 93230(9.3W)은 이미 90ms가 필요합니다. 데이터 양이 늘어나면 이 시간은 계속해서 늘어납니다.
해결책:
데이터의 양이 많지 않은 경우 mysql의 전체 텍스트 인덱스를 사용하세요.
데이터의 양이 상대적으로 크거나 mysql의 전체 텍스트 인덱스가 기대에 미치지 못하는 경우 ES 사용을 고려해보세요
다음은 주로 MySQL 전체 텍스트 인덱스 관련입니다.
전체 텍스트 인덱스 소개
1. MySQL의 이전 버전의 전체 텍스트 인덱스는 MyISAM 스토리지 엔진의 char, varchar 및 텍스트 필드에만 사용할 수 있습니다.
전체 텍스트 검색(Full-Text Search) 은 데이터베이스에 저장된 책 전체 또는 기사 전체에서 임의의 내용 정보를 찾아주는 기술입니다. 필요에 따라 전문의 장, 절, 단락, 단어 등에 대한 정보를 얻을 수 있으며, 다양한 통계 및 분석도 수행할 수 있습니다
설정이 필요한 경우 대용량 데이터의 경우 전체 텍스트 인덱스를 먼저 추가하는 것이 좋습니다. 그런 다음 데이터가 인덱스됩니다.
1. 테이블 생성 시 전체 텍스트 인덱스 생성create table 表名(
字段名1,
字段名2,
字段名3,
字段名4,
FULLTEXT full_index_name (字段名)
)ENGINE=InnoDB;
2. 기존 테이블에 전체 텍스트 인덱스 추가
테이블 이름(필드 이름)에 전체 텍스트 인덱스 인덱스 이름 생성;
eg:
create table t_word
(
id int unsigned auto_increment comment '自增id' primary key,
uid char(32) not null comment '32位唯一id',
word varchar(256) null comment '英文单词',
translate varchar(256) null
);
create fulltext index full_idx_translate
on t_word (translate);
create fulltext index full_idx_word
on t_word (word);
INSERT INTO t_word (id, uid, word, translate) VALUES (1, '9d592499c65648b0a9519206688ef3f9', 'lion', '狮子');
INSERT INTO t_word (id, uid, word, translate) VALUES (2, 'ce26ac4239514bc6af481bcb1d9b67df', 'panda', '熊猫');
INSERT INTO t_word (id, uid, word, translate) VALUES (3, 'a7d6042853c44904b68275daafb44702', 'tiger', '老虎');
INSERT INTO t_word (id, uid, word, translate) VALUES (4, 'f13bd0a8ecea44fc9ade1625eeb4cc3c', 'goat', '山羊');
INSERT INTO t_word (id, uid, word, translate) VALUES (5, '27d5cbfc93a046388d712085e567474f', 'sheep', '绵羊');
INSERT INTO t_word (id, uid, word, translate) VALUES (6, 'ed35df138cf348aa937781be8ee21cbf', 'lamb', '羊羔');
INSERT INTO t_word (id, uid, word, translate) VALUES (7, 'fba5861d9527440990276e999f47ef8f', 'buffalo', '水牛');
INSERT INTO t_word (id, uid, word, translate) VALUES (8, '3a72e76f210841b1939fff0d3d721375', 'bull', '公牛');
INSERT INTO t_word (id, uid, word, translate) VALUES (9, '272e0b28ea7a48248a86f17533bf9943', 'cow', '母牛');
INSERT INTO t_word (id, uid, word, translate) VALUES (10, '47127adface54e418e4c1b9980af6d16', 'calf', '小牛');
INSERT INTO t_word (id, uid, word, translate) VALUES (11, '10592499c65648b0a9519206688ef3f9', 'little lion', '小狮子');
INSERT INTO t_word (id, uid, word, translate) VALUES (12, '1bf095110b634a01bee5b31c5ee7ee0c', 'little cow', '母牛');
INSERT INTO t_word (id, uid, word, translate) VALUES (13, '4813e588cde54c30bd65bfdbb243ad1f', 'little calf', '小小牛');
INSERT INTO t_word (id, uid, word, translate) VALUES (14, '5e377e281ad344048b6938a638b78ccb', 'little bull', '小公牛');
INSERT INTO t_word (id, uid, word, translate) VALUES (15, '2855ad0da2964c7682c178eb8271f13d', 'little buffalo', '小水牛');
INSERT INTO t_word (id, uid, word, translate) VALUES (16, '72f24c9a77644d57a36f3bdf2b8116b0', 'little lamb', '小羊羔');
INSERT INTO t_word (id, uid, word, translate) VALUES (17, '2d592499c65648b0a9519206688ef3f9', 'I''m a big lion', '我是一只大狮子');
3. 전체 텍스트 인덱스 삭제
create fulltext index 索引名称 on 表名(字段名);
eg:
MATCH(col1,col2,...) AGAINST(expr[search_modifier])
search_modifier:
{
IN NATURAL LANGUAGE MODE
| IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION
| IN BOOLEAN MODE
| WITH QUERY EXPANSION
}
3、删除全文索引
alter table 表名 drop index 索引名;
테이블 이름 변경 인덱스 인덱스 이름 삭제;
4 전체 텍스트 인덱스는
구문
// 默认是使用 in natural language mode
select * from t_word where match(word) against ('lion');
// 或者 显示写
select * from t_word where match(word) against ('lion' in natural language mode);
4.1을 사용합니다. 자연어 모드
자연어 모드는 MySQL default 전체 텍스트 검색 모드입니다. 자연어 모드는 연산자를 사용할 수 없으며, 나타나야 하거나 나타나지 않아야 하는 키워드와 같은 복잡한 쿼리를 지정할 수 없습니다.
// 默认是使用 in natural language mode
select * from t_word where match(word) against ('lion');
// 或者 显示写
select * from t_word where match(word) against ('lion' in natural language mode);
결과는 다음과 같습니다.
4.2 IN BOOLEAN MODE
BOOLEAN 모드연산자를 사용할 수 있습니다. 키워드의 가중치가 높거나 낮습니다. 부울 모드를 사용하는 것이 좋습니다
Operator
Description |
|
은 비어 있습니다.
기본값은 |
|
+
include라는 단어를 포함합니다. 존재해야 합니다. |
|
-
제외, 단어가 나오지 않아야 합니다. |
|
>(보다 큼)
순위 값을 포함하고 높이면 쿼리 결과가 높아집니다 |
|
순위 값을 포함하고 낮추면 쿼리 결과가 낮아집니다 | | ()
단어를 하위 표현식으로 그룹화합니다(그룹으로 포함, 제외, 순위 지정 등 가능). |
|
~
부정어의 순위값입니다. |
|
*
와일드카드는 단어 끝에 있습니다. |
|
""
문구를 정의합니다(전체 문구가 일치하여 포함하거나 제외되는 개별 단어 목록과 반대). |
|
示例:
// 默认是使用 in natural language mode
select * from t_word where match(word) against ('lion');
// 或者 显示写
select * from t_word where match(word) against ('lion' in natural language mode);
// 排除包含lion记录、查询出包含cow或者little的记录,提升包含calf单词的排名,降低包含cow记录的排名,查询出以go开头的记录
select * from t_word where match(word) against ('-lion cow little >calf <cow go*' in boolean mode) ;
好像问题都解决了, 但是问题才刚开始
回到最开始的需求,我想模糊搜索
select * from t_word where match(word) against('lio' in boolean mode);
预期值:把包含lion的都查询出来
实际结果:啥都没有。
全匹配查询的时候能查询出来
select * from t_word where match(translate) against('小水牛' in boolean mode);
只查询部分查询不出来。如:下面只查询 "小水" 或者"水牛" 都没有数据
select * from t_word where match(translate) against('小水' in boolean mode);
奇怪了,这咋没出来呢?
全文索引默认是只按照空格
进行分词的,所以当我完整的单个单词去查询的时候是能查出来的。但是使用部分单词去查询或者使用部分中文去查询时,是查询不出来数据的,像中文需要使用中文分词器进行分词。
中文分词与全文索引
InnoDB默认的全文索引parser非常合适于Latin,因为Latin是通过空格来分词的。但对于像中文,日文和韩文来说,没有这样的分隔符。一个词可以由多个字来组成,所以我们需要用不同的方式来处理。在MySQL 5.7.6中我们能使用一个新的全文索引插件来处理它们:N-gram parser。
什么是N-gram?
在全文索引中,n-gram就是一段文字里面连续的n个字的序列。例如,用n-gram来对“齿轮传动”来进行分词,得到的结果如下:
N=1 : '齿', '轮', '传', '动';
N=2 : '齿轮', '轮传', '传动';
N=3 : '齿轮传', '轮传动';
N=4 : '齿轮传动';
这个上面这个N是怎么去配置的?
查一下目前的值
show variables like '%token%';
参数解析:
innodb_ft_min_token_size
默认3,表示最小3个字符作为一个关键词,增大该值可减少全文索引的大小
innodb_ft_max_token_size
默认84,表示最大84个字符作为一个关键词,限制该值可减少全文索引的大小
ngram_token_size
默认2,表示2个字符作为内置分词解析器的一个关键词,合法取值范围是1-10,如对“abcd”建立全文索引,关键词为’ab’,‘bc’,‘cd’
当使用ngram分词解析器时,innodb_ft_min_token_size和innodb_ft_max_token_size 无效
修改方式
方式1: 在my.cnf中修改/添加参数
[mysqld]ngram_token_size = 1
方式2: 修改启动参数
mysqld --ngram_token_size=1复制代码
参数均不可动态修改,修改后需重启MySQL服务,并重新建立全文索引
实际使用
初始化测试数据
这里只提供部分测试数据,我下面sql使用全量数据,数据对不上
create table t_chinese_phrase
(
id int unsigned auto_increment comment 'id'
primary key,
phrase varchar(32) not null comment '词组'
)
collate = utf8mb4_general_ci;
INSERT INTO t_chinese_phrase (id, phrase) VALUES (278911, '阿昌族');
INSERT INTO t_chinese_phrase (id, phrase) VALUES (279253, '八一南昌起义');
INSERT INTO t_chinese_phrase (id, phrase) VALUES (282316, '昌明');
INSERT INTO t_chinese_phrase (id, phrase) VALUES (282317, '昌盛');
INSERT INTO t_chinese_phrase (id, phrase) VALUES (282318, '昌言');
INSERT INTO t_chinese_phrase (id, phrase) VALUES (286534, '东昌纸');
INSERT INTO t_chinese_phrase (id, phrase) VALUES (291525, '海昌蓝');
INSERT INTO test.t_chinese_phrase (id, phrase) VALUES (346682, '繁荣昌盛');
INSERT INTO test.t_chinese_phrase (id, phrase) VALUES (282317, '昌盛');
INSERT INTO test.t_chinese_phrase (id, phrase) VALUES (287738, '繁盛');
INSERT INTO test.t_chinese_phrase (id, phrase) VALUES (287736, '繁荣');
添加索引
mysql 全文索引使用倒排索引为 full inverted index
结构:{单词,(单词所在文档的ID,单词在具体文件中的位置)}
添加索引:
alter table t_chinese_phrase add fulltext ful_phrase (phrase) with parser ngram;
建完索引,我们可以通过查询INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE和INFORMATION_SCHEMA.INNODB_FT_TABLE_TABLE来查询哪些词在全文索引里面。这是一个非常有用的调试工具。如果我们发现一个包含某个词的文档,没有如我们所期望的那样出现在查询结果中,那么这个词可能是因为某些原因不在全文索引里面。比如,它含有stopword,或者它的大小小于ngram_token_size等等。这个时候我们就可以通过查询这两个表来确认。下面是一个简单的例子:
# test: 库名 t_chinese_phrase: 表名字
SET GLOBAL innodb_ft_aux_table="test/t_chinese_phrase";
# 查询分词情况
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
# 查询分词情况
select * from information_schema.innodb_ft_index_table;
查询结果如下:
因为我们上面设置了分词数是1,所以,可以看到都是按照一个词进行分词的。
字段解析:
FIRST_DOC_ID :word第一次出现的文档ID
LAST_DOC_ID : word最后一次出现的文档ID
DOC_COUNT :含有word的文档个数
DOC_ID :当前文档ID
POSITION : word 当在前文档ID的位置
查询
1、使用自然语言模式 NATURAL LANGUAGE MODE 查询
在自然语言模式(NATURAL LANGUAGE MODE)下,文本的查询被转换为n-gram分词查询的并集
。
例如,当ngram_token_size = 1 时,(‘繁荣昌盛’)转换为(‘繁 荣 昌 盛’)。下面一个例子:
SELECT * FROM t_chinese_phrase WHERE MATCH (phrase) AGAINST ('繁荣昌盛' in natural language mode) ;
2、使用布尔模式(BOOLEAN MODE)查询
布尔模式(BOOLEAN MODE)文本查询被转化为n-gram分词的短语查询
例如,当ngram_token_size = 1 时,(‘繁荣昌盛’)转换为(‘”繁荣昌盛“’)。下面一个例子:
SELECT * FROM t_chinese_phrase WHERE MATCH (phrase) AGAINST ('繁荣昌盛' in boolean mode) ;
实际使用
回到我们最开始的查询需求,看看实际的效果
查询包含了“昌”的数据
SELECT * FROM t_chinese_phrase WHERE MATCH (phrase) AGAINST ('昌' IN boolean MODE) ;
SELECT * FROM t_chinese_phrase WHERE MATCH (phrase) AGAINST ('昌' ) order by id asc;
可以看到结果:目前“昌”在任意位置都能被查询到。
查询执行计划如下:
耗时31ms(不走索引是90ms),耗时差不多是之前的1/3
。
注意点
1、自然语言全文索引创建索引时的字段需与查询的字段保持一致,即MATCH里的字段必须和FULLTEXT里的一模一样;
2、自然语言检索时,检索的关键字在所有数据中不能超过50%(即常见词),则不会检索出结果。可以通过布尔检索查询;
3、在mysql的stopword中的单词检索不出结果。可通过
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD
查询所有的stopword。遇到这种情况,有两种解决办法:
(1)stopword一般是mysql自建的,但可以通过设置ft_stopword_file变量为自定义文件,从而自己设置stopword,设置完成后需要重新创建索引。但不建议使用这种方法;
(2)使用布尔索引查询。
4、小于最短长度和大于最长长度的关键词无法查出结果。可以通过设置对应的变量来改变长度限制,修改后需要重新创建索引。
myisam引擎下对应的变量名为ft_min_word_len和ft_max_word_len
innodb引擎下对应的变量名为innodb_ft_min_token_size和innodb_ft_max_token_size
5、MySQL5.7.6之前的版本不支持中文,需使用第三方插件
6、全文索引只能在 InnoDB(MySQL 5.6以后) 或 MyISAM 的表上使用,并且只能用于创建 char,varchar,text 类型的列。
【相关推荐:mysql视频教程】
위 내용은 MySQL 전체 텍스트 인덱스가 퍼지 일치 쿼리와 같은 느린 문제를 어떻게 해결하는지 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!