FindInSet性能优化
背景:最近在mysql性能優(yōu)化中遇到一個(gè)findinset()函數(shù)的性能問(wèn)題:坦然說(shuō)在數(shù)據(jù)量低的情況下,find_in_set的性能還是不錯(cuò)的,但是在30w左右開(kāi)始嚴(yán)重劣化,到那時(shí)數(shù)據(jù)庫(kù)結(jié)構(gòu)已經(jīng)定下來(lái)了,拆表的代價(jià)太大,而且?guī)?lái)很多冗余數(shù)據(jù),找了很多方法還是不行,甚至我都開(kāi)始考慮用ES來(lái)做了,但是自己搭一套ES集群實(shí)在麻煩,最終跑到隔壁組找了個(gè)專(zhuān)業(yè)dba,很快就解決了我的問(wèn)題,看來(lái)專(zhuān)業(yè)的事情還是得交給專(zhuān)業(yè)的人來(lái)做哦!
findinset()
先介紹一下findinset吧!
不少數(shù)據(jù)表設(shè)計(jì)的時(shí)候使用一個(gè)字段來(lái)存儲(chǔ)多對(duì)多關(guān)系,比如:
表 user中有一個(gè)字段叫 category, category存儲(chǔ)的是 "1,3,9" 這樣的類(lèi)型的數(shù)據(jù),實(shí)際上是category的id 用逗號(hào)分隔開(kāi)來(lái)的
?要查詢(xún)一個(gè)用戶(hù)屬于id為2分類(lèi)的用戶(hù)可以這么寫(xiě):
select?*?from?`user`?where?find_in_set('2',`user`.`category`);
具體find_in_set 的使用請(qǐng)參照手冊(cè):
http://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_find-in-set
雖然這樣很好用,但問(wèn)題是如果數(shù)據(jù)量大,又無(wú)法走索引,是很慢的。取網(wǎng)友的一個(gè)例子:
user 表錄入 100萬(wàn)的數(shù)據(jù),同時(shí)建立 user_category 表,每個(gè)user有 3 個(gè)分類(lèi),那么category表里有300萬(wàn)條記錄:
CREATE?TABLE?`user_category`?(?? ??`id`?int(11)?NOT?NULL?AUTO_INCREMENT,?? ??`user_id`?int(11)?DEFAULT?NULL,?? ??`category_id`?int(11)?DEFAULT?NULL,?? ??PRIMARY?KEY?(`id`),?? ??KEY?`category_id`?(`category_id`),?? ??KEY?`user_id`?(`tax_id`)?? )?ENGINE=InnoDB?AUTO_INCREMENT=1?DEFAULT現(xiàn)在比較一下在百萬(wàn)級(jí)的數(shù)據(jù)量上使用 join 鏈接外鍵查詢(xún)和find_in_set查詢(xún)的性能?1. 使用 find_in_set 查詢(xún),平均時(shí)間在2.2秒左右
SELECT?SQL_NO_CACHE?COUNT(*)?FROM?`user`?WHERE?FIND_IN_SET(65,category)2. 使用left join , 使用了右表中的索引,平均時(shí)間在0.2秒左右
SELECT?SQL_NO_CACHE?COUNT(DISTINCT(`user`.id))?FROM?`user`??? LEFT?JOIN?`user_category`?ON?`user`.`id`=?`user_category`.`user_id`?? WHERE?`user_category`.`category_id`=75這是采用一種空間換時(shí)間的辦法。但是如果實(shí)在項(xiàng)目后期,又無(wú)法改變表結(jié)構(gòu)又該怎么辦呢?
既然問(wèn)題的核心在于:findinset函數(shù)無(wú)法走索引,那給他加上索引不久好了
那么,全文索引登場(chǎng)了。
全文索引
通過(guò)數(shù)值比較、范圍過(guò)濾等就可以完成絕大多數(shù)我們需要的查詢(xún),但是,如果希望通過(guò)關(guān)鍵字的匹配來(lái)進(jìn)行查詢(xún)過(guò)濾,那么就需要基于相似度的查詢(xún),而不是原來(lái)的精確數(shù)值比較。全文索引就是為這種場(chǎng)景設(shè)計(jì)的。你可能會(huì)說(shuō),用 like + % 就可以實(shí)現(xiàn)模糊匹配了,為什么還要全文索引?like + % 在文本比較少時(shí)是合適的,但是對(duì)于大量的文本數(shù)據(jù)檢索,是不可想象的。全文索引在大量的數(shù)據(jù)面前,能比 like + % 快 N 倍,速度不是一個(gè)數(shù)量級(jí),但是全文索引可能存在精度問(wèn)題。你可能沒(méi)有注意過(guò)全文索引,不過(guò)至少應(yīng)該對(duì)一種全文索引技術(shù)比較熟悉:各種的搜索引擎。雖然搜索引擎的索引對(duì)象是超大量的數(shù)據(jù),并且通常其背后都不是關(guān)系型數(shù)據(jù)庫(kù),不過(guò)全文索引的基本原理是一樣的.
關(guān)于全文索引的詳情請(qǐng)參考這個(gè):https://blog.csdn.net/mrzhouxiaofei/article/details/79940958
使用全文索引
和常用的模糊匹配使用 like + % 不同,全文索引有自己的語(yǔ)法格式,使用 match 和 against 關(guān)鍵字,比如:
select * from fulltext_test where match(content,tag) against('xxx xxx');
注意:?match() 函數(shù)中指定的列必須和全文索引中指定的列完全相同,否則就會(huì)報(bào)錯(cuò),無(wú)法使用全文索引,這是因?yàn)槿乃饕粫?huì)記錄關(guān)鍵字來(lái)自哪一列。如果想要對(duì)某一列使用全文索引,請(qǐng)單獨(dú)為該列創(chuàng)建全文索引。
如:首先創(chuàng)建測(cè)試表,插入測(cè)試數(shù)據(jù)
create table test (
? ? id int(11) unsigned not null auto_increment,
? ? content text not null,
? ? primary key(id),
? ? fulltext key content_index(content)
) engine=MyISAM default charset=utf8;
insert into test (content) values ('aaaa'),('bbbb'),('cccc');
按照全文索引的使用語(yǔ)法執(zhí)行下面查詢(xún):
select * from test where match(content) against('aaa');
注意:
MySQL 中的全文索引,有兩個(gè)變量,最小搜索長(zhǎng)度和最大搜索長(zhǎng)度,對(duì)于長(zhǎng)度小于最小搜索長(zhǎng)度和大于最大搜索長(zhǎng)度的詞語(yǔ),都不會(huì)被索引。通俗點(diǎn)就是說(shuō),想對(duì)一個(gè)詞語(yǔ)使用全文索引搜索,那么這個(gè)詞語(yǔ)的長(zhǎng)度必須在以上兩個(gè)變量的區(qū)間內(nèi)。
對(duì)比結(jié)果:
使用findinset需要10s左右,使用全文索引只需要0.3s左右
?
?
?
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的FindInSet性能优化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 从建表到SQL优化
- 下一篇: 可视化工具第一篇(百度Echarts)