MySQL之join查詢優(yōu)化方式
MySQL join查詢優(yōu)化
在日常的開發(fā)中,我們經(jīng)常遇到這樣情況:select * from TableA inner join TableB...它響應(yīng)速度一直很快的,隨著數(shù)據(jù)的增長(zhǎng),突然有一天開始很慢了。那該怎么破?
對(duì),驅(qū)動(dòng)表是突破口,
1. 那什么是驅(qū)動(dòng)表呢?
- 指定了聯(lián)接條件時(shí),滿足查詢條件的記錄行數(shù)少的表為驅(qū)動(dòng)表
- 未指定聯(lián)接條件時(shí),行數(shù)少的表為驅(qū)動(dòng)表(Important!)
如果你搞不清楚該讓誰做驅(qū)動(dòng)表、誰 join 誰,就別指定誰 left/right join 誰了,請(qǐng)交給 MySQL優(yōu)化器 運(yùn)行時(shí)決定吧。
2. 復(fù)雜的sql怎么識(shí)別驅(qū)動(dòng)表呢?
按經(jīng)驗(yàn)談,使用EXPLAIN, 第一行出現(xiàn)的表就是驅(qū)動(dòng)表。
3. 關(guān)聯(lián)查詢?cè)硎窃鯓拥模?/h3>
MySQL 表關(guān)聯(lián)的算法是 Nest Loop Join,是通過驅(qū)動(dòng)表的結(jié)果集作為循環(huán)基礎(chǔ)數(shù)據(jù),然后一條一條地通過該結(jié)果集中的數(shù)據(jù)作為過濾條件到下一個(gè)表中查詢數(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ū)動(dòng)user表則只需要循環(huán)20次就能查詢出來。
4. 該如如何優(yōu)化?
優(yōu)化的目標(biāo)是盡可能減少JOIN中Nested Loop的循環(huán)次數(shù),以此保證:永遠(yuǎn)用小結(jié)果集驅(qū)動(dòng)大結(jié)果集。
排序的字段也有影響,有條原則:對(duì)驅(qū)動(dòng)表可以直接排序,對(duì)非驅(qū)動(dòng)表(的字段排序)需要對(duì)循環(huán)查詢的合并結(jié)果(臨時(shí)表)進(jìn)行排序!
5. 實(shí)例
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
夠復(fù)雜吧。假如,user表有千萬級(jí)記錄,class表要少得多,從執(zhí)行計(jì)劃的得知驅(qū)動(dòng)表(數(shù)據(jù)到千萬級(jí))。由于動(dòng)用了“LEFT JOIN”,所以相當(dāng)于已經(jīng)指定了驅(qū)動(dòng)表。
如何優(yōu)化?
//優(yōu)化第一步:LEFT JOIN改為JOIN,對(duì),直接 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í)行計(jì)劃得知, 有Using temporary(臨時(shí)表);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)化)
準(zhǔn)備數(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左外連接
看這個(gè)分析結(jié)果發(fā)現(xiàn):在 class 表上添加的索引起的作用不大。
結(jié)論:
- **小表驅(qū)動(dòng)大表**
- - 小表:相對(duì)來說記錄較少的表
- - 大表:相對(duì)來說記錄較多的表
- 驅(qū)動(dòng)方式識(shí)別
- left join:左邊驅(qū)動(dòng)右邊(此時(shí)把小表放在左邊)
- right join:右邊驅(qū)動(dòng)左邊(此時(shí)把小表放在右邊)
- 加索引的方式:通常建議在大表(被驅(qū)動(dòng))的表加索引,效率提升更明顯。
- 原因:
- 原因1:被驅(qū)動(dòng)表加了索引之后,收益更大。從 ALL -> ref
- 原因2:外連接首先讀取驅(qū)動(dòng)表的全部數(shù)據(jù),被驅(qū)動(dòng)只讀取滿足連接條件的數(shù)據(jù)。
inner join:MySQL會(huì)自動(dòng)根據(jù)表中的數(shù)據(jù)選擇驅(qū)動(dòng)表
小結(jié):
- 保證被驅(qū)動(dòng)表的 join 字段被索引。join 字段就是作為連接條件的字段。
- left join 時(shí),選擇小表作為驅(qū)動(dòng)表(放左邊),大表作為被驅(qū)動(dòng)表(放右邊)
- inner join 時(shí),mysql 會(huì)自動(dòng)將小結(jié)果集的表選為驅(qū)動(dòng)表。
- 子查詢盡量不要放在被驅(qū)動(dòng)表,衍生表建不了索引
- 能夠直接多表關(guān)聯(lián)的盡量直接關(guān)聯(lián),不用子查詢
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
在linux命令下導(dǎo)出導(dǎo)入.sql文件的方法
這篇文章主要介紹了在linux命令下導(dǎo)出導(dǎo)入.sql文件的方法,具有很好的參考價(jià)值,給大家做個(gè)參考,跟隨小編過來看看吧2018-05-05Mysql常用函數(shù)之Rank排名函數(shù)詳解
這篇文章主要介紹了Mysql常用函數(shù)之Rank排名函數(shù)詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01mysql中一個(gè)普通ERROR 1135 (HY000)錯(cuò)誤引發(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 處理重復(fù)數(shù)據(jù)的方法(防止、刪除)
這篇文章主要介紹了MySQL 處理重復(fù)數(shù)據(jù)的方法,文中示例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07在MySQL中創(chuàng)建實(shí)現(xiàn)自增的序列(Sequence)的教程
這篇文章主要介紹了在MySQL中創(chuàng)建實(shí)現(xiàn)自增的序列(Sequence)的教程,分別列舉了兩個(gè)實(shí)例并簡(jiǎn)單討論了一些限制因素,需要的朋友可以參考下2015-12-12Mysql中通用表達(dá)式WITH?AS語句的使用實(shí)例代碼
with as也叫子查詢,用來定義一個(gè)sql片段,且該片段會(huì)被整個(gè)sql語句反復(fù)使用很多次,這個(gè)sql片段就相當(dāng)于是一個(gè)公用臨時(shí)表,下面這篇文章主要給大家介紹了關(guān)于Mysql中通用表達(dá)式WITH?AS語句使用的相關(guān)資料,需要的朋友可以參考下2022-08-08mysql二進(jìn)制日志文件恢復(fù)數(shù)據(jù)庫
喜歡的在服務(wù)器或者數(shù)據(jù)庫上直接操作的兄弟們你值得收藏下!不然你就悲劇了。-----(當(dāng)然我也是在網(wǎng)上搜索的資料!不過自己測(cè)試通過了的!)2014-08-08