您好,登錄后才能下訂單哦!
什么是MySQL 連接查詢?相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。
在數(shù)據(jù)庫(kù)中join
操作被稱為連接,作用是能連接多個(gè)表的數(shù)據(jù)(通過(guò)連接條件),從多個(gè)表中獲取數(shù)據(jù)合并在一起作為結(jié)果集返回給客戶端。例如:
表A:
id | name | age |
---|---|---|
1 | A | 18 |
2 | B | 19 |
3 | C | 20 |
表B:
id | uid | gender |
---|---|---|
1 | 1 | F |
2 | 2 | M |
通過(guò)連接可以獲取到合并兩個(gè)表的數(shù)據(jù):
select A.*,B.gender from A left join B on A.id=B.uid
id | name | age | gender |
---|---|---|---|
1 | A | 18 | F |
2 | B | 19 | M |
3 | C | 20 | null |
連接兩個(gè)表我們可以用兩個(gè)關(guān)鍵字:on
,using
。on
可以指定具體條件,using
則指定相同名字和數(shù)據(jù)類型的列作為等值判斷的條件,多個(gè)則通過(guò)逗號(hào)隔開(kāi)。
如下:
on: select * from A join B on A.id=B.id and B.name='' using: select * from A join B using(id,name) = select * from A join B on A.id=B.id and A.name=B.name
A join | inner join | cross join B
cross join
,inner join
和join
所實(shí)現(xiàn)的功能是一樣的。因此在MySQL的官方文檔中,指明了三者是等價(jià)的關(guān)系。from A,B,C
on
和using
的join
A left join B
null
A right join B
null
MySQL不支持全外連接,只支持左外連接和右外連接。如果要獲取全連接的數(shù)據(jù),要可以通過(guò)合并左右外連接的數(shù)據(jù)獲取到,如 select * from A left join B on A.name = B.name union select * from A right join B on B.name = B.name;
。
這里union
會(huì)自動(dòng)去重,這樣取到的就是全外連接的數(shù)據(jù)了。
A natural join B ==== A natural left join B ==== A natural right join B
在連接過(guò)程中,MySQL各關(guān)鍵字執(zhí)行的順序如下:
from -> on|using -> where -> group by -> having -> select -> order by -> limit
可以看到,連接的條件是先于where
的,也就是先連接獲得結(jié)果集后,才對(duì)結(jié)果集進(jìn)行where
篩選,所以在使用join
的時(shí)候,我們要盡可能提供連接的條件,而少用where
的條件,這樣才能提高查詢性能。
join
有三種算法,分別是Nested Loop Join
,Hash join
,Sort Merge Join
。MySQL官方文檔中提到,MySQL只支持Nested Loop Join
這一種算法。
具體來(lái)說(shuō)Nested Loop Join
又分三種細(xì)分的算法:
我們來(lái)看下對(duì)于連接語(yǔ)句select * from A left join B on A.id=B.tid
,這三種算法是怎么連接的。
SNLJ
是在沒(méi)有使用到索引的情況下,通過(guò)兩層循環(huán)全量掃描連接的兩張表,得到符合條件的兩條記錄則輸出。也就是讓兩張表做笛卡爾積進(jìn)行掃描,是比較暴力的算法,會(huì)比較耗時(shí)。其過(guò)程如下:
for (a in A) { for (b in B) { if (a.id == b.tid) { output <a, b>; } } }
當(dāng)然,MySQL即使在無(wú)索引可用,或者判斷全表掃描可能比使用索引更快的情況下,還是不會(huì)選擇使用過(guò)于粗暴的SNLJ
算法,而是采用下面的算法。
INLJ
是MySQL無(wú)法使用索引的時(shí)候采用的join
算法。會(huì)將外層循環(huán)的行分片存入join buffer
, 內(nèi)層循環(huán)的每一行與整個(gè)buffer
中的記錄做比較,從而減少內(nèi)層循環(huán)的次數(shù),具體邏輯如下:
for (blockA in A.blocks) { for (b in B) { if (b.tid in blockA.id) { output <a, b>; } } }
相比于SNLJ
算法,BNLJ
算法通過(guò)外層循環(huán)的結(jié)果集的分塊,可以有效的減少內(nèi)層循環(huán)的次數(shù)。
原理
舉例來(lái)說(shuō),外層循環(huán)的結(jié)果集是100行,使用SNLJ
算法需要掃描內(nèi)部表100次,如果使用BNLJ
算法,假設(shè)每次分片的數(shù)量是10,則會(huì)先把對(duì)Outer Loop
表(外部表)每次讀取的10行記錄放到join buffer
,然后在InnerLoop
表(內(nèi)部表)中每次循環(huán)都直接匹配這10行數(shù)據(jù),這樣內(nèi)層循環(huán)只需要10次,對(duì)內(nèi)部表的掃描減少了9/10,所以BNLJ
算法就能夠顯著減少內(nèi)層循環(huán)表掃描的次數(shù)。
當(dāng)然這里,不管SNLJ
還是BNLJ
算法,他們總的比較次數(shù)都是一樣的,都是要拿外層循環(huán)的每一行與內(nèi)層循環(huán)的每一行進(jìn)行比較。
BNLJ
算法減少的是總的掃描行數(shù),SNLJ
算法是外層循環(huán)要一行行掃描A
表的數(shù)據(jù),然后取A.id
去表B
一行行掃描看是否匹配。而BNLJ
算法則是外層循環(huán)要一行行掃描A
表的數(shù)據(jù),然后放到內(nèi)存分塊里,然后去表B
一行行掃描,掃描出來(lái)的B
的一行數(shù)據(jù)與內(nèi)存分塊里的A
的數(shù)據(jù)塊進(jìn)行比較。這里可以一次就是很多行A
的數(shù)據(jù)與B
的數(shù)據(jù)進(jìn)行比較,而且是在內(nèi)存中進(jìn)行比較,速度更加快了。
影響因素
這里BNLJ
算法總的掃描行數(shù)是由外層循環(huán)的數(shù)據(jù)量N
,和分塊數(shù)量K
還有內(nèi)層循環(huán)的數(shù)據(jù)量M
決定的。其中分塊數(shù)量K
與外層循環(huán)的數(shù)據(jù)量N
又是息息相關(guān)的,我們可以表示為λN
,其中λ
取值為(0~1)
。則總掃描次數(shù)C=N+λNM
。
可以看出,在這個(gè)式子里,N
和λ
的大小都會(huì)影響掃描行數(shù),但是λ
才是影響掃描行數(shù)的關(guān)鍵因素,這個(gè)值越小越好(除非N
和M
的差值非常大,這時(shí)候N
才會(huì)成為關(guān)鍵影響因素)。
那什么會(huì)影響 λ
的大小呢?那就是 MySQL的join_buffer_size
設(shè)置項(xiàng)的大小了。λ
和join_buffer_size
成倒數(shù)關(guān)系,join_buffer_size
越大,分塊越大,λ
越小,分塊數(shù)量也就越少,也就是外層循環(huán)的次數(shù)也越少。所以在使用不上索引的時(shí)候,我們要優(yōu)先考慮擴(kuò)大join_buffer_size
的大小,這樣優(yōu)化效果會(huì)更明顯。而在能使用上索引的時(shí)候,MySQL會(huì)使用以下算法來(lái)進(jìn)行join
。
INLJ是MySQL判斷能使用到被驅(qū)動(dòng)表的索引的情況下采用的算法。假設(shè)A
表的數(shù)據(jù)行為10,B
表的數(shù)據(jù)行為100,且B.tid
建立了索引,則對(duì)于select * from A left join B on A.id=B.tid
,MySQL會(huì)采用Index Nested Loop Join
。其過(guò)程如下:
for (a in A) { if (a.id in B.tid.Index) { output <a, tid.Index所在行>; } }
總共需要循環(huán)10次A
,每次循環(huán)的時(shí)候通過(guò)索引查詢一次B
的數(shù)據(jù)。而如果我們反過(guò)來(lái)是B left join A
的話,總共要循環(huán)100次B
,由此可見(jiàn)如果使用join的話,需要讓小表做驅(qū)動(dòng)表,這樣才能有效減少循環(huán)次數(shù)。但是需要注意的是,這個(gè)結(jié)論的前提是可以使用被驅(qū)動(dòng)表的索引。
INLJ內(nèi)層循環(huán)讀取的是索引,可以減少內(nèi)存循環(huán)的次數(shù),提高join
效率,但是也有缺點(diǎn)的,就是如果掃描的索引是非聚簇索引,并且需要訪問(wèn)非索引的數(shù)據(jù),會(huì)產(chǎn)生一個(gè)回表讀取數(shù)據(jù)的操作,這就多了一次隨機(jī)的I/O操作。例如上面在索引里匹配到了tid
,還要去找tid
所在的行在磁盤(pán)所在的位置,具體可以見(jiàn)我以前的文章:MySQL索引詳解之索引的存儲(chǔ)方式。
join
后數(shù)據(jù)集的大小join
的字段要建立索引,且使用上索引。使用上索引包括使用該字段,且不會(huì)有索引失效的情況出現(xiàn)join_buffer_size
Q:如果想篩選驅(qū)動(dòng)表的數(shù)據(jù),例如左連接篩選左表的數(shù)據(jù),該在連接條件還是where
篩選?
A:要通過(guò)where
篩選,連接條件只影響連接過(guò)程,不影響連接返回的結(jié)果數(shù)(某些情況下連接條件會(huì)影響連接返回的結(jié)果數(shù),例如左連接中,右側(cè)匹配的數(shù)據(jù)不唯一的時(shí)候)
Q:被驅(qū)動(dòng)表匹配的數(shù)據(jù)行不唯一導(dǎo)致最終連接數(shù)據(jù)超過(guò)驅(qū)動(dòng)表數(shù)據(jù)量該怎么辦?例如對(duì)于左連接,右表匹配的數(shù)據(jù)行不唯一。
A:join
之前先對(duì)被驅(qū)動(dòng)表去重,例如通過(guò)group by
去重:A lef join (select * from B group by name)
。
看完上述內(nèi)容,你們掌握什么是MySQL 連接查詢的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。