FindInSet性能优化
背景:最近在mysql性能優化中遇到一個findinset()函數的性能問題:坦然說在數據量低的情況下,find_in_set的性能還是不錯的,但是在30w左右開始嚴重劣化,到那時數據庫結構已經定下來了,拆表的代價太大,而且帶來很多冗余數據,找了很多方法還是不行,甚至我都開始考慮用ES來做了,但是自己搭一套ES集群實在麻煩,最終跑到隔壁組找了個專業dba,很快就解決了我的問題,看來專業的事情還是得交給專業的人來做哦!
findinset()
先介紹一下findinset吧!
不少數據表設計的時候使用一個字段來存儲多對多關系,比如:
表 user中有一個字段叫 category, category存儲的是 "1,3,9" 這樣的類型的數據,實際上是category的id 用逗號分隔開來的
?要查詢一個用戶屬于id為2分類的用戶可以這么寫:
select?*?from?`user`?where?find_in_set('2',`user`.`category`);
具體find_in_set 的使用請參照手冊:
http://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_find-in-set
雖然這樣很好用,但問題是如果數據量大,又無法走索引,是很慢的。取網友的一個例子:
user 表錄入 100萬的數據,同時建立 user_category 表,每個user有 3 個分類,那么category表里有300萬條記錄:
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現在比較一下在百萬級的數據量上使用 join 鏈接外鍵查詢和find_in_set查詢的性能?1. 使用 find_in_set 查詢,平均時間在2.2秒左右
SELECT?SQL_NO_CACHE?COUNT(*)?FROM?`user`?WHERE?FIND_IN_SET(65,category)2. 使用left join , 使用了右表中的索引,平均時間在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這是采用一種空間換時間的辦法。但是如果實在項目后期,又無法改變表結構又該怎么辦呢?
既然問題的核心在于:findinset函數無法走索引,那給他加上索引不久好了
那么,全文索引登場了。
全文索引
通過數值比較、范圍過濾等就可以完成絕大多數我們需要的查詢,但是,如果希望通過關鍵字的匹配來進行查詢過濾,那么就需要基于相似度的查詢,而不是原來的精確數值比較。全文索引就是為這種場景設計的。你可能會說,用 like + % 就可以實現模糊匹配了,為什么還要全文索引?like + % 在文本比較少時是合適的,但是對于大量的文本數據檢索,是不可想象的。全文索引在大量的數據面前,能比 like + % 快 N 倍,速度不是一個數量級,但是全文索引可能存在精度問題。你可能沒有注意過全文索引,不過至少應該對一種全文索引技術比較熟悉:各種的搜索引擎。雖然搜索引擎的索引對象是超大量的數據,并且通常其背后都不是關系型數據庫,不過全文索引的基本原理是一樣的.
關于全文索引的詳情請參考這個:https://blog.csdn.net/mrzhouxiaofei/article/details/79940958
使用全文索引
和常用的模糊匹配使用 like + % 不同,全文索引有自己的語法格式,使用 match 和 against 關鍵字,比如:
select * from fulltext_test where match(content,tag) against('xxx xxx');
注意:?match() 函數中指定的列必須和全文索引中指定的列完全相同,否則就會報錯,無法使用全文索引,這是因為全文索引不會記錄關鍵字來自哪一列。如果想要對某一列使用全文索引,請單獨為該列創建全文索引。
如:首先創建測試表,插入測試數據
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');
按照全文索引的使用語法執行下面查詢:
select * from test where match(content) against('aaa');
注意:
MySQL 中的全文索引,有兩個變量,最小搜索長度和最大搜索長度,對于長度小于最小搜索長度和大于最大搜索長度的詞語,都不會被索引。通俗點就是說,想對一個詞語使用全文索引搜索,那么這個詞語的長度必須在以上兩個變量的區間內。
對比結果:
使用findinset需要10s左右,使用全文索引只需要0.3s左右
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的FindInSet性能优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从建表到SQL优化
- 下一篇: 可视化工具第一篇(百度Echarts)