系列
MySQL實(shí)戰(zhàn)45講閱讀筆記-MySQL入門(mén)
MySQL實(shí)戰(zhàn)45講閱讀筆記-日志
MySQL實(shí)戰(zhàn)45講閱讀筆記-鎖
MySQL實(shí)戰(zhàn)45講閱讀筆記-索引
MySQL實(shí)戰(zhàn)45講閱讀筆記-MVCC
索引用于快速查找具有特定值的行。如果沒(méi)有索引,MySQL必須從第一行開(kāi)始,然后讀取整個(gè)表以查找相關(guān)行。表越大,成本越高。如果表中有相關(guān)??列的索引,MySQL可以快速確定要在數(shù)據(jù)文件中間尋找的位置,而無(wú)需進(jìn)行全表掃描,這比按順序讀取每一行要快得多。
索引模型
B Tree是一種平衡多叉樹(shù),是為了磁盤(pán)或其它存儲(chǔ)設(shè)備而設(shè)計(jì)的一種多叉平衡查找樹(shù);為什么這么說(shuō)呢;磁盤(pán)的最小存儲(chǔ)單位是扇區(qū)(sector),而操作系統(tǒng)的塊(block)通常是整數(shù)倍的sector,操作系統(tǒng)以頁(yè)(page)為單位管理內(nèi)存,一頁(yè)(page)通常默認(rèn)為4K,數(shù)據(jù)庫(kù)的頁(yè)通常設(shè)置為操作系統(tǒng)頁(yè)的整數(shù)倍;數(shù)據(jù)庫(kù)操作是以數(shù)據(jù)頁(yè)為基本單位進(jìn)行操作的,讀取某一行數(shù)據(jù)時(shí)是去尋找該行所在的頁(yè),然后把這整個(gè)數(shù)據(jù)頁(yè)(一般數(shù)據(jù)頁(yè)大小為16k,可以查詢(xún)Innodb_page_size
)加載進(jìn)內(nèi)存里面,所以說(shuō)加載的內(nèi)存頁(yè)的數(shù)量決定了加載速度;B Tree相比其他的數(shù)據(jù)模型例如平衡二叉樹(shù),它的樹(shù)高遠(yuǎn)遠(yuǎn)小于平衡二叉樹(shù),樹(shù)高小意味著IO次數(shù)少,每個(gè)節(jié)點(diǎn)保存的數(shù)據(jù)也遠(yuǎn)多于二叉樹(shù),這就意味著訪問(wèn)一次磁盤(pán)訪問(wèn)能加載更多的內(nèi)存頁(yè);
B-Tree
B樹(shù)對(duì)比平衡二叉樹(shù)來(lái)看,每個(gè)節(jié)點(diǎn)允許有更多的子節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)都可以?xún)?chǔ)存數(shù)據(jù);
B樹(shù)具有以下的特點(diǎn):
- 每個(gè)節(jié)點(diǎn)都儲(chǔ)存盡量多的數(shù)據(jù),保證樹(shù)的層級(jí)盡量的少;
- 查找時(shí)有可能在非葉子節(jié)點(diǎn)結(jié)束,查找性能接近二分查找算法;
B+Tree
相比于B樹(shù),B+樹(shù)的所有數(shù)據(jù)都儲(chǔ)存在葉子節(jié)點(diǎn),非葉子節(jié)點(diǎn)只儲(chǔ)存關(guān)鍵字,各葉子節(jié)點(diǎn)指針進(jìn)行連接;
因?yàn)锽+樹(shù)所有數(shù)據(jù)都儲(chǔ)存在葉子節(jié)點(diǎn),所以在查找關(guān)鍵字的時(shí)候一次性加載進(jìn)內(nèi)存的關(guān)鍵字就越多(因?yàn)榉侨~節(jié)點(diǎn)不保存數(shù)據(jù),每次能加載的key更多),相對(duì)來(lái)說(shuō)讀取IO的次數(shù)就越少;每次查詢(xún)的路徑長(zhǎng)度都是相同的所以查詢(xún)效率相當(dāng),因?yàn)槿~子節(jié)點(diǎn)連接起來(lái)了,可以實(shí)現(xiàn)遍歷葉子節(jié)點(diǎn)就完成整個(gè)樹(shù)的遍歷,這樣在范圍查詢(xún)的時(shí)候效率較高;
Innodb索引
Innodb索引類(lèi)型分為主鍵索引和非主鍵索引,主鍵索引也稱(chēng)為聚簇索引,葉子節(jié)點(diǎn)儲(chǔ)存的是整行的數(shù)據(jù);非主鍵索引也稱(chēng)二級(jí)索引,葉子節(jié)點(diǎn)儲(chǔ)存的是數(shù)據(jù)頁(yè);Innodb的索引和數(shù)據(jù)都是存儲(chǔ)在一個(gè)文件中的,不像MyISAM索引文件和數(shù)據(jù)文件是分離,索引文件只保存數(shù)據(jù)的地址;
假如一個(gè)表結(jié)構(gòu)是下面這個(gè)樣的
mysql> create table test(
id int primary key,
age int not null,
name varchar(16),
index (age))engine=InnoDB;
二級(jí)索引樹(shù)儲(chǔ)存的內(nèi)容是對(duì)應(yīng)的主鍵,聚簇索引節(jié)點(diǎn)存儲(chǔ)的是數(shù)據(jù)頁(yè),所以一般情況下用二級(jí)索引查詢(xún)效率沒(méi)有主鍵查詢(xún)效率高;B+樹(shù)索引并不能找到具體的某一行,只能定位到具體的數(shù)據(jù)頁(yè),把數(shù)據(jù)頁(yè)加載到內(nèi)存后再通過(guò)二分查找法查找;
為了保證索引的有序性,在插入數(shù)據(jù)的時(shí)候可能會(huì)對(duì)樹(shù)做一些調(diào)整,如果現(xiàn)在要添加一個(gè)id為7的數(shù)據(jù),直接添加在5的屁股后面就好了,但是又再添加一個(gè)id為6的數(shù)據(jù)的時(shí)候則需要在邏輯上挪動(dòng)5后面的數(shù)據(jù),這時(shí)不巧5所在的數(shù)據(jù)頁(yè)滿了,按照B+樹(shù)的算法這時(shí)需要申請(qǐng)一個(gè)數(shù)據(jù)頁(yè),然后挪動(dòng)部分?jǐn)?shù)據(jù)過(guò)去,這種情況稱(chēng)為頁(yè)分裂,同時(shí)一個(gè)頁(yè)的數(shù)據(jù)分為了兩個(gè)頁(yè),整體的利用率下降了大約50%;
當(dāng)然有分裂就有合并,當(dāng)相鄰的兩個(gè)數(shù)據(jù)頁(yè)利用率很低的時(shí)候會(huì)將數(shù)據(jù)頁(yè)合并;
所以推薦建表用自增主鍵,這樣插入數(shù)據(jù)的時(shí)候都是追加操作,能帶來(lái)較好的性能;還有主鍵若是占用的字節(jié)較小的話,二級(jí)索引占用的空間也越小,因?yàn)槎?jí)索引節(jié)點(diǎn)的內(nèi)容是主鍵;
覆蓋索引
在查詢(xún)的時(shí)候在二級(jí)索引樹(shù)找到主鍵再回溯主鍵索引樹(shù)稱(chēng)為回表;而直接在二級(jí)索引樹(shù)上面查找到數(shù)據(jù)則稱(chēng)為覆蓋索引,因?yàn)樯倭嘶厮葸@一步驟,減少了樹(shù)的搜索次數(shù),查找性能有效的提高;
最左前綴匹配原則
當(dāng)表中有聯(lián)合索引時(shí),比如有一個(gè)表test是下面這個(gè)樣子
CREATE TABLE `test` (
`id` int(11) NOT NULL,
`age` int(11) NOT NULL,
`name` varchar(16) DEFAULT NULL,
`pwd` varchar(16) DEFAULT NULL,
`email` varchar(16) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`,`age`,`pwd`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
現(xiàn)在需要查詢(xún)的語(yǔ)句中包含name字段的時(shí)候是用到了索引的
explain select * from test where name = 'ss' and age = 10 and pwd = 'ss';
+----+-------------+-------+------------+------+---------------+------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-------------------+------+----------+-------+
| 1 | SIMPLE | test | NULL | ref | name | name | 42 | const,const,const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+-------------------+------+----------+-------+
但是如果只使用pwd字段查找時(shí)
explain select * from test where pwd = 'ws'
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | test | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
可以看到并沒(méi)有使用到索引,mysql會(huì)一直向右匹配直到遇到范圍查詢(xún)(>、<、between、like)就停止匹配;最左前綴可以是聯(lián)合索引的最左N個(gè)字段,也可以是最左M個(gè)字符;
- 什么時(shí)候索引會(huì)失效
- where條件中有or,即使其中有條件帶索引也不會(huì)使用;如果要想使用or又想讓索引生效,只能將or條件中的每個(gè)列都加上索引;
- 對(duì)于多列索引,不是使用的第一部分,則不會(huì)使用索引(即不符合最左前綴原則);
- like查詢(xún)是以%開(kāi)頭;
- 如果列類(lèi)型是字符串,那一定要在條件中將數(shù)據(jù)使用引號(hào)引用起來(lái),否則不使用索引;
- 如果mysql估計(jì)使用全表掃描要比使用索引快,則不使用索引;
索引下推(Index Condition Pushdown)
在Mysql5.6以后引入了索引下推優(yōu)化,可以在索引遍歷過(guò)程中對(duì)索引所包含的字段先做判斷,直接過(guò)濾不滿足的條件,旨在減少回表次數(shù)和減少M(fèi)ySQL server層和引擎層的交互次數(shù);
select * from test where name like 's%' and age = 19;
根據(jù)最左前綴原則,上面查詢(xún)語(yǔ)句只能用到's%'這個(gè)條件來(lái)搜索索引樹(shù)
Innodb在name,age索引樹(shù)內(nèi)部就已經(jīng)判斷了age是否等于19,只取需要的數(shù)據(jù)進(jìn)行回表;
唯一索引
UNIQUE KEY 可以保證不會(huì)出現(xiàn)重復(fù)的值( null 除外),如果能確定某個(gè)數(shù)據(jù)列只能存在彼此各不相同的值,可以使用唯一索引,在插入新數(shù)據(jù)的時(shí)候MySQL會(huì)檢查這個(gè)記錄是否存在;
唯一索引查找
如果使用查詢(xún)語(yǔ)句select * from test where age = 18;
,首先根據(jù)索引樹(shù)的根節(jié)點(diǎn)開(kāi)始按層搜索到葉子節(jié)點(diǎn),找到age18所在的數(shù)據(jù)頁(yè),數(shù)據(jù)頁(yè)內(nèi)部通過(guò)二分法定位到所在行;
- 對(duì)于唯一索引來(lái)說(shuō),查找到滿足第一個(gè)符合條件的行就會(huì)停止搜索;
- 而對(duì)于普通索引來(lái)說(shuō),查找到滿足第一個(gè)符合條件的行后還會(huì)繼續(xù)搜索,直到找到第一個(gè)不滿足條件的行;
因?yàn)橐媸前错?yè)讀取到內(nèi)存的,意味著如果在數(shù)據(jù)頁(yè)內(nèi)查找一條數(shù)據(jù)和多條數(shù)據(jù)的效率是一樣的,所以唯一索引和普通索引在查找方面的效率沒(méi)有什么區(qū)別;
唯一索引更新
當(dāng)需要更新一個(gè)數(shù)據(jù)頁(yè)的時(shí)候,如果數(shù)據(jù)頁(yè)在內(nèi)存中則直接更新,但是數(shù)據(jù)頁(yè)不在內(nèi)存的時(shí)候,則會(huì)把更新操作緩存在Change Buffer
中,下次查詢(xún)這個(gè)數(shù)據(jù)頁(yè)的時(shí)候就把該頁(yè)讀取到內(nèi)存中,然后執(zhí)行Change Buffer中與該頁(yè)相關(guān)的操作,通過(guò)這種方式能保證數(shù)據(jù)邏輯的正確性;
但是對(duì)于唯一索引來(lái)說(shuō),每一次的更新操作都需要判斷該記錄的唯一性,這就必須要把最新的數(shù)據(jù)頁(yè)讀取到內(nèi)存中,再執(zhí)行insert或update操作;
相比使用Change Buffer,這樣做會(huì)導(dǎo)致大量的隨機(jī)IO訪問(wèn),這就是Change Buffer
可以避免很多隨機(jī)磁盤(pán)IO的原因;
所以在業(yè)務(wù)可以不需要去重或者可以用業(yè)務(wù)保證數(shù)據(jù)的唯一性的時(shí)候優(yōu)先使用普通索引;
- ChangeBuffer和RedoLog的關(guān)系
假如現(xiàn)在需要執(zhí)行insert into test(id, name, age) values(1, 'ss', 33), (2, 'sss', 18)
,再假如前面所插入的values的數(shù)據(jù)頁(yè)1在內(nèi)存中,而后面的values的數(shù)據(jù)頁(yè)2不在內(nèi)存中;
那么會(huì)進(jìn)行以下操作
- 在數(shù)據(jù)頁(yè)1直接更新(1, 'ss', 33)
- 因?yàn)閿?shù)據(jù)頁(yè)2不在內(nèi)存,所以在內(nèi)存的ChangeBuffer中記錄
insert into test(id, name, age) values(2, 'sss', 18)
- 將上面兩個(gè)動(dòng)作記錄到redo log中;(redolog同樣會(huì)記錄Change Buffer里面的操作)
完成后事務(wù)就可以提交了,這里寫(xiě)了兩次內(nèi)存(數(shù)據(jù)頁(yè)1一次,ChangeBuffer一次),寫(xiě)磁盤(pán)一次(redo log寫(xiě)盤(pán));
讀取的時(shí)候假如行在數(shù)據(jù)頁(yè)1中,直接可以從內(nèi)存中返回,假如行在數(shù)據(jù)頁(yè)2中,則需要把數(shù)據(jù)頁(yè)2加載到內(nèi)存中然后根據(jù)Change Buffer里面的日志進(jìn)行更新后才會(huì)讀?。?/p>
- 寫(xiě)ChangeBuffer時(shí)候MySQL異常退出了有什么影響?
如果在ChangeBuffer merge(ChangeBuffer應(yīng)用到舊數(shù)據(jù)頁(yè)得到新數(shù)據(jù)頁(yè)的過(guò)程稱(chēng)為merge)之后掉電,這部分已經(jīng)應(yīng)用到數(shù)據(jù)頁(yè)上面,所以不需要進(jìn)行恢復(fù);
主要是分析沒(méi)有merge的部分
- redolog沒(méi)有完成二階段提交,處于
prepare-fsync
階段,binlog未fsync,這部分?jǐn)?shù)據(jù)會(huì)丟失; - redolog沒(méi)有完成二階段提交,redolog已經(jīng)fsync但是還未commit,binlog已經(jīng)fsync,這部分?jǐn)?shù)據(jù)可以通過(guò)binlog恢復(fù)redolog,然后通過(guò)redolog恢復(fù)ChangeBuffer;
- redolog已經(jīng)commit,可以直接使用redolog恢復(fù)ChangeBuffer;
前綴索引
索引是很方便,但是對(duì)于長(zhǎng)度過(guò)長(zhǎng)的字段建立索引是很耗空間的一種操作,所以就有了前綴索引,選取字段的前n個(gè)字符建立索引可以有效的縮小索引空間,但是也就意味著查詢(xún)時(shí)通過(guò)索引判斷的精度會(huì)有所變小,掃描的行數(shù)會(huì)變多;
- 語(yǔ)法
ALTER TABLE table_name ADD KEY(column_name(length));
前綴索引只能用在普通索引上面,且使用了前綴索引就不能使用覆蓋索引了,因?yàn)橄到y(tǒng)不確定根據(jù)前綴索引查找出來(lái)的結(jié)果集是否全部滿足需求,必須回到主索引樹(shù)上面再過(guò)濾一遍;所以在選擇使用前綴索引的時(shí)候還是要根據(jù)業(yè)務(wù)來(lái)判斷是否是最優(yōu)選項(xiàng);
MRR優(yōu)化
Multi-Range Read(MRR)是MySQL5.6版本推出來(lái)性能優(yōu)化,主要功能是對(duì)于非聚簇索引查找時(shí)將隨機(jī)IO轉(zhuǎn)換為順序IO;
因?yàn)椴荒鼙WC二級(jí)索引上面儲(chǔ)存的主鍵是順序排序的,那么再回表的時(shí)候讀取的數(shù)據(jù)頁(yè)可能散落在各個(gè)節(jié)點(diǎn)上面,勢(shì)必會(huì)造成隨機(jī)IO讀;
MRR的優(yōu)化就是在回表的過(guò)程中,將二級(jí)索引樹(shù)上查找的主鍵放在一塊叫read_rnd_buffer
的內(nèi)存中先保存起來(lái),然后對(duì)buffer的主鍵進(jìn)行排序,排序后的主鍵在表中查找時(shí)效率就會(huì)變高;
buffer的大小由read_rnd_buffer_size
參數(shù)控制,如果說(shuō)一次MRR中buffer里面的主鍵越多那么優(yōu)化效果也就越好;
MRR只是在范圍查詢(xún)中有效,optimizer_switch
中的mrr
控制是否使用MRR優(yōu)化,mrr_cost_based
表示優(yōu)化器是否會(huì)計(jì)算mrr的使用成本來(lái)決定是否使用mrr機(jī)制;
Order By
Innodb里面有兩種排序的方式,索引排序和filesort排序,所謂的索引排序是依賴(lài)B+樹(shù)結(jié)構(gòu)中數(shù)據(jù)一定是有序的;而filesort排序
則是讀出來(lái)的數(shù)據(jù)集需要經(jīng)過(guò)額外的排序操作;
select a, b, c from test where a(非聚簇索引字段)='a' order by b(非索引字段) limit 10000;
上面語(yǔ)句的流程大概是這樣的
- 初始化sort_buffer,sort_buffer是排序時(shí)候用到的內(nèi)存,是每個(gè)線程獨(dú)有的,由參數(shù)
sort_buffer_size
控制大??;(還有一個(gè)Innodb_sort_buffer_size
參數(shù),這個(gè)參數(shù)是指在創(chuàng)建innodb索引期間用于對(duì)數(shù)據(jù)排序的排序緩存區(qū)大小) - 根據(jù)索引a找到主鍵值,回到主索引樹(shù)上面取出a、b、c和主鍵的值存入sort_buffer中;
- 從索引a中繼續(xù)取下一個(gè)主鍵的值,重復(fù)步驟2和3直到找到所有滿足的行;
- 在sort_buffer中對(duì)數(shù)據(jù)按照b做快排;
- 取排序后的前10000條數(shù)據(jù)當(dāng)作結(jié)果集返回;
其中如果需要排序的數(shù)據(jù)小于sort_buffer_size,那么排序可以在內(nèi)存中完成,如果大于緩存區(qū),則需要借助磁盤(pán)的臨時(shí)文件輔助排序;
使用下面這個(gè)方法可以確定一個(gè)排序語(yǔ)句是否用到了臨時(shí)文件
/* 打開(kāi) optimizer_trace,只對(duì)本線程有效 */
SET optimizer_trace='enabled=on';
/* @a 保存 Innodb_rows_read 的初始值 */
select VARIABLE_VALUE into @a from performance_schema.session_status where variable_name = 'Innodb_rows_read';
/* 執(zhí)行語(yǔ)句 */
select a, b, c from test where a(非聚簇索引字段)='a' order by b(非索引字段) limit 10000;
/* 查看 OPTIMIZER_TRACE 輸出 */
SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`
/* @b 保存 Innodb_rows_read 的當(dāng)前值 */
select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = 'Innodb_rows_read';
/* 計(jì)算 Innodb_rows_read 差值 */
select @b-@a; #得到36764
### 關(guān)鍵部分
"filesort_summary": {
"rows": 36764,
"examined_rows": 36764,
"number_of_tmp_files": 6,
"sort_buffer_size": 262144,
"sort_mode": "<sort_key, packed_additional_fields>"
}
number_of_tmp_files
表示使用了6個(gè)臨時(shí)文件,examined_rows
表示有36764行數(shù)據(jù)參與排序,packed_additional_fields
表示排序過(guò)程中對(duì)字符串做緊湊處理;
現(xiàn)在執(zhí)行下面這個(gè)語(yǔ)句
SET max_length_for_sort_data = 16;
然后再一次執(zhí)行一次上面的語(yǔ)句
select @b-@a; #得到46764
"filesort_summary": {
"rows": 36764,
"examined_rows": 36764,
"number_of_tmp_files": 4,
"sort_buffer_size": 262136,
"sort_mode": "<sort_key, rowid>"
}
max_length_for_sort_data
是控制用于排序行數(shù)據(jù)的最大長(zhǎng)度,假設(shè)abc三個(gè)字段長(zhǎng)度為36,改成16之后不能將abc都裝入sort_buffer中,所以MySQL會(huì)選擇只把主鍵和將要排序的b字段放入sort_buffer中,所以排序的流程變成了下面這樣
- 初始化sort_buffer,執(zhí)行器查看表定義,發(fā)現(xiàn)abc三個(gè)字段長(zhǎng)度大于
max_length_for_sort_data
,確認(rèn)放入的字段有b和主鍵字段; - 執(zhí)行器調(diào)用儲(chǔ)存引擎的查找接口,根據(jù)索引a找到主鍵值,回到主索引樹(shù)上面取出b的值和主鍵值存入sort_buffer中;
- 從索引a中繼續(xù)取下一個(gè)主鍵的值,重復(fù)步驟2和3直到找到所有滿足的行;
- 在sort_buffer中對(duì)數(shù)據(jù)按照b做快排;
- 遍歷排序結(jié)果取前10000行并按照主鍵值回到原表取出abc三個(gè)字段返回;
sort_buffer不屬于innodb引擎內(nèi)部,所以需要兩次調(diào)用innodb引擎的查找接口,這就是方式二innodb讀取行數(shù)比方式一多1w行的原因;
兩種排序各有優(yōu)缺點(diǎn),方式一需要?jiǎng)?chuàng)建更多的臨時(shí)文件,但是查詢(xún)數(shù)據(jù)的次數(shù)少,方式二則相反;
當(dāng)然更好的方法就是不使用filesort排序
直接使用索引排序,比如建立(city, name, age)
的聯(lián)合索引;
JOIN
多表查詢(xún)的時(shí)候可以使用join關(guān)鍵字,合理的join可以帶來(lái)較好的性能,但是往往使用不當(dāng)?shù)那闆r較多,有些則干脆不推薦使用,像淘寶的阿里巴巴Java開(kāi)發(fā)手冊(cè)就有一條超過(guò)3個(gè)表就禁止使用join;到底join該怎么用還是需要了解關(guān)于它的基本信息;
JOIN的類(lèi)型
Cross Join
返回表a和表b的笛卡爾乘積,即是a中所有行*b中所有行;
select * from a cross join b
Inner Join
inner join(內(nèi)連接)是選取驅(qū)動(dòng)表的數(shù)據(jù)集去匹配被驅(qū)動(dòng)表的數(shù)據(jù)集中符合條件的集合;當(dāng)不存在任何條件時(shí),返回的結(jié)果和cross join是一樣的, 當(dāng)使用on ...
返回的相當(dāng)于兩個(gè)表中符合條件的交集;
select * from a inner join b on condition where ...
當(dāng)使用join或inner join時(shí),mysql會(huì)選擇數(shù)據(jù)量比較小的表作為驅(qū)動(dòng)表,大表作為被驅(qū)動(dòng)表;
Left Join
left join(左連接)查找的結(jié)果集包括表a的所有行并顯示對(duì)應(yīng)匹配表b的行,對(duì)于表a存在而表b不存在的行則顯示null;如果要使用left\right join,對(duì)于等值判斷或不等值判斷就只能寫(xiě)到on里不能寫(xiě)在where中;
select * from a left join b on condition where ...
當(dāng)使用left join時(shí),左表是驅(qū)動(dòng)表,右表是被驅(qū)動(dòng)表;
Right Join
right join和left join相反,顯示表b所有行和對(duì)應(yīng)匹配表a的行,不存在則顯示null;
select * from a right join b on condition where ...
當(dāng)使用right join時(shí),右表時(shí)驅(qū)動(dòng)表,左表是驅(qū)動(dòng)表;
Join的原理
Join使用的是Nested-Loop Join算法,即是驅(qū)動(dòng)表的數(shù)據(jù)作為循環(huán)主體,然后把符合條件的行再拿去和被驅(qū)動(dòng)表中符合條件的行組成結(jié)果集;NLJ算法總共細(xì)分了3種類(lèi)型;
Simple Nested-Loop Join
拿驅(qū)動(dòng)表所有的數(shù)據(jù)去被驅(qū)動(dòng)表逐條匹配查找符合條件的行,每一次去被驅(qū)動(dòng)表查找的時(shí)候走的是全表掃描,假設(shè)驅(qū)動(dòng)表行數(shù)是n,被驅(qū)動(dòng)表行數(shù)位m,那么總共掃描的行數(shù)為n*m;Index Nested-Loop Join
和上面的相比最大的不同是被驅(qū)動(dòng)表中關(guān)聯(lián)的字段是有索引的,在查找的時(shí)候走的是樹(shù)搜索,一次樹(shù)查詢(xún)的時(shí)間復(fù)雜度為log?m,如果被驅(qū)動(dòng)表所關(guān)聯(lián)的字段是非聚簇索引的話,還需要回表到主索引樹(shù)上,那么時(shí)間復(fù)雜度是2 * log?m
,所以整個(gè)查找是驅(qū)動(dòng)表行數(shù)n * 2 * log?m
;在explain中發(fā)現(xiàn)沒(méi)有關(guān)鍵字Using join buffer(Block Nested Loop)
表明使用的就是NLJ算法;Block Nested-Loop Join
被驅(qū)動(dòng)表上面沒(méi)有可用索引,那么是不會(huì)選擇Simple Nested-Loop Join
,而是Block Nested-Loop Join
;
會(huì)把驅(qū)動(dòng)表的數(shù)據(jù)逐一放到一個(gè)叫做join_buffer
的內(nèi)存中,然后再到被驅(qū)動(dòng)表的每一行數(shù)據(jù)拿出來(lái)到join_buffer
中進(jìn)行比較,符合條件的數(shù)據(jù)作為結(jié)果集返回;
join_buffer
的大小由參數(shù)join_buffer_size
控制的,如果buffer中不能一次性裝下驅(qū)動(dòng)表的數(shù)據(jù),則會(huì)再buffer裝滿的時(shí)候和被驅(qū)動(dòng)表數(shù)據(jù)對(duì)比,對(duì)比完后清空buffer再繼續(xù)裝驅(qū)動(dòng)表的數(shù)據(jù),直到完成join;
和Simple Nested-Loop Join對(duì)比這個(gè)算法的優(yōu)勢(shì)是判斷是在內(nèi)存中完成的,效率較高,join_buffer
也是影響join速度的因素之一,越大的buffer,被驅(qū)動(dòng)表被掃描次數(shù)就越少,IO次數(shù)也就越少;如果被驅(qū)動(dòng)表掃描次數(shù)變多,可能會(huì)造成Innodb buffer pool
中的Young區(qū)域被被驅(qū)動(dòng)表的數(shù)據(jù)頁(yè)填滿,以至于正常訪問(wèn)的熱點(diǎn)數(shù)據(jù)頁(yè)被淘汰掉影響內(nèi)存命中率;Batched Key Access
MySQL5.6后引入了BKA算法,是對(duì)NLJ算法的優(yōu)化,NLJ算法時(shí)從驅(qū)動(dòng)表中遍歷的數(shù)據(jù)一行行的拿去和被驅(qū)動(dòng)表中匹配,而B(niǎo)KA算法則是從驅(qū)動(dòng)表中拿出來(lái)的數(shù)據(jù)先儲(chǔ)存在join_buffer
中進(jìn)行排序,然后使用MRR接口中去被驅(qū)動(dòng)表中查找對(duì)應(yīng)行;BKA算法提升性能的地方在于將原本一行一行的與被驅(qū)動(dòng)表進(jìn)行對(duì)比改成了批量拿去和被驅(qū)動(dòng)表對(duì)比,將隨機(jī)IO轉(zhuǎn)換成了順序IO;
如果是多表join,那么每一次join得到的數(shù)據(jù)集會(huì)新增一個(gè)join_buffer
用來(lái)存在下一次join的數(shù)據(jù)集;
BNL和BKA都是用到了join_buffer
,區(qū)別在于BKA用于被驅(qū)動(dòng)表有可用索引的情況下,而B(niǎo)NL是用于被驅(qū)動(dòng)表上沒(méi)有可用索引;
啟用BKA之前需要啟動(dòng)MRR優(yōu)化,然后設(shè)置set global optimizer_switch = 'batched_key_access=on'
;Join的優(yōu)化
- 用小表做驅(qū)動(dòng)表,盡量減少join語(yǔ)句中的Nested Loop的循環(huán)總次數(shù);
- 對(duì)被驅(qū)動(dòng)表的join字段上建立索引;
- 使用臨時(shí)表建立索引,把原本被驅(qū)動(dòng)表上的數(shù)據(jù)插入到臨時(shí)表中,使用臨時(shí)表作為被驅(qū)動(dòng)表從而用上索引;
- 當(dāng)被驅(qū)動(dòng)表的join字段上無(wú)法建立索引的時(shí)候,設(shè)置足夠的Join Buffer Size。
join查詢(xún)?cè)谟兴饕龡l件下
- 驅(qū)動(dòng)表有索引不會(huì)使用到索引
- 被驅(qū)動(dòng)表建立索引會(huì)使用到索引
Group By
Group By主要有3種方式實(shí)現(xiàn)
-
松散索引掃描(Loose Index Scan)
依賴(lài)于索引的有序性直接使用索引,且只需掃描部分的索引而不用掃描全部索引,所以稱(chēng)為松散索引掃描;使用松散索引的條件是- 只能單表查詢(xún)
- group by選擇的列還滿足最左前綴原則且沒(méi)有其他多余的列,如果有多余的列必須是以常量形式存在;
- 使用聚合函數(shù)(min、max)的列必須在group by的列里面;
- 只能對(duì)列的整個(gè)值進(jìn)行g(shù)roup by,比如
c varchar(20)
,索引必須是index(c(20))
而不能是前綴索引如index(c(10))
;
緊湊索引掃描(Tight Index Scan)
緊湊索引掃描可以是完整索引掃描也可以是范圍索引掃描,如果不滿足松散索引掃描的條件,則使用緊湊索引掃描,緊湊索引掃描仍然可以避免使用臨時(shí)表來(lái)進(jìn)行額外的排序;創(chuàng)建臨時(shí)表實(shí)現(xiàn)Group by
創(chuàng)建一張內(nèi)存臨時(shí)表用來(lái)保存這個(gè)groupby的所有關(guān)聯(lián)字段然后再在臨時(shí)表中排序后得到結(jié)果集返回給客戶端;
在explain中顯示Using index; Using temporary; Using filesort
分別表示使用覆蓋索引不需要回表,使用臨時(shí)表和使用filesort排序;如果不需要排序可以在語(yǔ)句后面添加order by null
;
參數(shù)tmp_table_size
是控制臨時(shí)表的大小,默認(rèn)16M,如果大于這個(gè)則會(huì)在磁盤(pán)中創(chuàng)建臨時(shí)表;-
優(yōu)化總結(jié)
- 盡量使用索引,確保explain中不會(huì)出現(xiàn)
Using temporary; Using filesort
; - 如果group by沒(méi)有排序要求,在語(yǔ)句后面添加
order by null
,在使用臨時(shí)表的時(shí)候能避免使用sort_buffer
; - 使用臨時(shí)表時(shí)可以適當(dāng)調(diào)大
tmp_table_size
來(lái)滿足groupby,避免使用磁盤(pán)臨時(shí)文件;
- 盡量使用索引,確保explain中不會(huì)出現(xiàn)