MySQL之join查詢優(yōu)化方式
MySQL join查詢優(yōu)化
在日常的開發(fā)中,我們經(jīng)常遇到這樣情況:select * from TableA inner join TableB...它響應速度一直很快的,隨著數(shù)據(jù)的增長,突然有一天開始很慢了。那該怎么破?
對,驅(qū)動表是突破口,
1. 那什么是驅(qū)動表呢?
- 指定了聯(lián)接條件時,滿足查詢條件的記錄行數(shù)少的表為驅(qū)動表
- 未指定聯(lián)接條件時,行數(shù)少的表為驅(qū)動表(Important!)
如果你搞不清楚該讓誰做驅(qū)動表、誰 join 誰,就別指定誰 left/right join 誰了,請交給 MySQL優(yōu)化器 運行時決定吧。
2. 復雜的sql怎么識別驅(qū)動表呢?
按經(jīng)驗談,使用EXPLAIN, 第一行出現(xiàn)的表就是驅(qū)動表。
3. 關(guān)聯(lián)查詢原理是怎樣的?
MySQL 表關(guān)聯(lián)的算法是 Nest Loop Join,是通過驅(qū)動表的結(jié)果集作為循環(huán)基礎(chǔ)數(shù)據(jù),然后一條一條地通過該結(jié)果集中的數(shù)據(jù)作為過濾條件到下一個表中查詢數(shù)據(jù),然后合并結(jié)果。
//例: user表10000條數(shù)據(jù),class表20條數(shù)據(jù) select * from user u left join class c u.userid=c.userid
上面sql的后果就是需要用user表循環(huán)10000次才能查詢出來,而如果用class表驅(qū)動user表則只需要循環(huán)20次就能查詢出來。
4. 該如如何優(yōu)化?
優(yōu)化的目標是盡可能減少JOIN中Nested Loop的循環(huán)次數(shù),以此保證:永遠用小結(jié)果集驅(qū)動大結(jié)果集。
排序的字段也有影響,有條原則:對驅(qū)動表可以直接排序,對非驅(qū)動表(的字段排序)需要對循環(huán)查詢的合并結(jié)果(臨時表)進行排序!
5. 實例
explain select * from user u left join class c on u.userid=c.userid INNER JOIN subject s on c.subjectId=s.id? ?WHERE 1=1 ORDER BY u.create_time DESC limit 0,10
夠復雜吧。假如,user表有千萬級記錄,class表要少得多,從執(zhí)行計劃的得知驅(qū)動表(數(shù)據(jù)到千萬級)。由于動用了“LEFT JOIN”,所以相當于已經(jīng)指定了驅(qū)動表。
如何優(yōu)化?
//優(yōu)化第一步:LEFT JOIN改為JOIN,對,直接 join! explain select * from user u join class c on u.userid=c.userid INNER JOIN subject s on c.subjectId=s.id? ?WHERE 1=1 ORDER BY u.create_time DESC limit 0,10 //優(yōu)化第二步:從上面執(zhí)行計劃得知, 有Using temporary(臨時表);Using filesort,解決方法是調(diào)整排序字段(借助前面講過排序的原則) explain select * from user u join class c on u.userid=c.userid INNER JOIN subject s on c.subjectId=s.id? ?WHERE 1=1 ORDER BY c.id DESC limit 0,10
總之,sql優(yōu)化中explain工具是非常重要的武器。
MySQL優(yōu)化(關(guān)聯(lián)查詢優(yōu)化)
準備數(shù)據(jù)
#分類 CREATE TABLE IF NOT EXISTS `class` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `card` INT(10) UNSIGNED NOT NULL, PRIMARY KEY (`id`) ); #圖書 CREATE TABLE IF NOT EXISTS `book` ( `bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `card` INT(10) UNSIGNED NOT NULL, PRIMARY KEY (`bookid`) ); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
left join左外連接
看這個分析結(jié)果發(fā)現(xiàn):在 class 表上添加的索引起的作用不大。
結(jié)論:
- **小表驅(qū)動大表**
- - 小表:相對來說記錄較少的表
- - 大表:相對來說記錄較多的表
- 驅(qū)動方式識別
- left join:左邊驅(qū)動右邊(此時把小表放在左邊)
- right join:右邊驅(qū)動左邊(此時把小表放在右邊)
- 加索引的方式:通常建議在大表(被驅(qū)動)的表加索引,效率提升更明顯。
- 原因:
- 原因1:被驅(qū)動表加了索引之后,收益更大。從 ALL -> ref
- 原因2:外連接首先讀取驅(qū)動表的全部數(shù)據(jù),被驅(qū)動只讀取滿足連接條件的數(shù)據(jù)。
inner join:MySQL會自動根據(jù)表中的數(shù)據(jù)選擇驅(qū)動表
小結(jié):
- 保證被驅(qū)動表的 join 字段被索引。join 字段就是作為連接條件的字段。
- left join 時,選擇小表作為驅(qū)動表(放左邊),大表作為被驅(qū)動表(放右邊)
- inner join 時,mysql 會自動將小結(jié)果集的表選為驅(qū)動表。
- 子查詢盡量不要放在被驅(qū)動表,衍生表建不了索引
- 能夠直接多表關(guān)聯(lián)的盡量直接關(guān)聯(lián),不用子查詢
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Mysql常用函數(shù)之Rank排名函數(shù)詳解
這篇文章主要介紹了Mysql常用函數(shù)之Rank排名函數(shù)詳解,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01mysql中一個普通ERROR 1135 (HY000)錯誤引發(fā)的血案
ERROR 1135 (HY000): Can’t create a new thread (errno 11);if you are not out of available memory,you can consult the manual for a possible OS-dependent bug2015-08-08MySQL 處理重復數(shù)據(jù)的方法(防止、刪除)
這篇文章主要介紹了MySQL 處理重復數(shù)據(jù)的方法,文中示例代碼非常詳細,幫助大家更好的理解和學習,感興趣的朋友可以了解下2020-07-07在MySQL中創(chuàng)建實現(xiàn)自增的序列(Sequence)的教程
這篇文章主要介紹了在MySQL中創(chuàng)建實現(xiàn)自增的序列(Sequence)的教程,分別列舉了兩個實例并簡單討論了一些限制因素,需要的朋友可以參考下2015-12-12