十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
加鎖情況與死鎖原因分析
成都創(chuàng)新互聯(lián)專注于企業(yè)成都全網(wǎng)營銷、網(wǎng)站重做改版、萬載網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5高端網(wǎng)站建設(shè)、商城網(wǎng)站開發(fā)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計(jì)等建站業(yè)務(wù),價格優(yōu)惠性價比高,為萬載等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
為方便大家復(fù)現(xiàn),完整表結(jié)構(gòu)和數(shù)據(jù)如下:
CREATE TABLE `t3` (
`c1` int(11) NOT NULL AUTO_INCREMENT,
`c2` int(11) DEFAULT NULL,
PRIMARY KEY (`c1`),
UNIQUE KEY `c2` (`c2`)
) ENGINE=InnoDB
insert into t3 values(1,1),(15,15),(20,20);
在 session1 執(zhí)行 commit 的瞬間,我們會看到 session2、session3 的其中一個報死鎖。這個死鎖是這樣產(chǎn)生的:
1.?session1 執(zhí)行 delete ?會在唯一索引 c2 的 c2 = 15 這一記錄上加 X lock(也就是在MySQL 內(nèi)部觀測到的:X Lock but not gap);
2.?session2 和 session3 在執(zhí)行 insert 的時候,由于唯一約束檢測發(fā)生唯一沖突,會加 S Next-Key Lock,即對 (1,15] 這個區(qū)間加鎖包括間隙,并且被 seesion1 的 X Lock 阻塞,進(jìn)入等待;
3.?session1 在執(zhí)行 commit 后,會釋放 X Lock,session2 和 session3 都獲得 S Next-Key Lock;
4.?session2 和 session3 繼續(xù)執(zhí)行插入操作,這個時候 INSERT INTENTION LOCK(插入意向鎖)出現(xiàn)了,并且由于插入意向鎖會被 gap 鎖阻塞,所以 session2 和 session3 互相等待,造成死鎖。
死鎖日志如下:
請點(diǎn)擊輸入圖片描述
INSERT INTENTION LOCK
在之前的死鎖分析第四點(diǎn),如果不分析插入意向鎖,也是會造成死鎖的,因?yàn)椴迦胱罱K還是要對記錄加 X Lock 的,session2 和 session3 還是會互相阻塞互相等待。
但是插入意向鎖是客觀存在的,我們可以在官方手冊中查到,不可忽略:
Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.
插入意向鎖其實(shí)是一種特殊的 gap lock,但是它不會阻塞其他鎖。假設(shè)存在值為 4 和 7 的索引記錄,嘗試插入值 5 和 6 的兩個事務(wù)在獲取插入行上的排它鎖之前使用插入意向鎖鎖定間隙,即在(4,7)上加 gap lock,但是這兩個事務(wù)不會互相沖突等待。
當(dāng)插入一條記錄時,會去檢查當(dāng)前插入位置的下一條記錄上是否存在鎖對象,如果下一條記錄上存在鎖對象,就需要判斷該鎖對象是否鎖住了 gap。如果 gap 被鎖住了,則插入意向鎖與之沖突,進(jìn)入等待狀態(tài)(插入意向鎖之間并不互斥)??偨Y(jié)一下這把鎖的屬性:
1. 它不會阻塞其他任何鎖;
2. 它本身僅會被 gap lock 阻塞。
在學(xué)習(xí) MySQL 過程中,一般只有在它被阻塞的時候才能觀察到,所以這也是它常常被忽略的原因吧...
GAP LOCK
在此例中,另外一個重要的點(diǎn)就是 gap lock,通常情況下我們說到 gap lock 都只會聯(lián)想到 REPEATABLE-READ 隔離級別利用其解決幻讀。但實(shí)際上在 READ-COMMITTED 隔離級別,也會存在 gap lock ,只發(fā)生在:唯一約束檢查到有唯一沖突的時候,會加 S Next-key Lock,即對記錄以及與和上一條記錄之間的間隙加共享鎖。
通過下面這個例子就能驗(yàn)證:
請點(diǎn)擊輸入圖片描述
這里 session1 插入數(shù)據(jù)遇到唯一沖突,雖然報錯,但是對 (15,20] 加的 S Next-Key Lock 并不會馬上釋放,所以 session2 被阻塞。另外一種情況就是本文開始的例子,當(dāng) session2 插入遇到唯一沖突但是因?yàn)楸?X Lock 阻塞,并不會立刻報錯 “Duplicate key”,但是依然要等待獲取 S Next-Key Lock 。
有個困惑很久的疑問:出現(xiàn)唯一沖突需要加 S Next-Key Lock 是事實(shí),但是加鎖的意義是什么?還是說是通過 S Next-Key Lock 來實(shí)現(xiàn)的唯一約束檢查,但是這樣意味著在插入沒有遇到唯一沖突的時候,這個鎖會立刻釋放,這不符合二階段鎖原則。這點(diǎn)希望能與大家一起討論得到好的解釋。
如果是在 REPEATABLE-READ,除以上所說的唯一約束沖突外,gap lock 的存在是這樣的:
普通索引(非唯一索引)的S/X Lock,都帶 gap 屬性,會鎖住記錄以及前1條記錄到后1條記錄的左閉右開區(qū)間,比如有[4,6,8]記錄,delete 6,則會鎖住[4,8)整個區(qū)間。
對于 gap lock,相信 DBA 們的心情是一樣一樣的,所以我的建議是:
1. 在絕大部分的業(yè)務(wù)場景下,都可以把 MySQL 的隔離界別設(shè)置為 READ-COMMITTED;
2. 在業(yè)務(wù)方便控制字段值唯一的情況下,盡量減少表中唯一索引的數(shù)量。
鎖沖突矩陣
前面我們說的 GAP LOCK 其實(shí)是鎖的屬性,另外我們知道 InnoDB 常規(guī)鎖模式有:S 和 X,即共享鎖和排他鎖。鎖模式和鎖屬性是可以隨意組合的,組合之后的沖突矩陣如下,這對我們分析死鎖很有幫助:
請點(diǎn)擊輸入圖片描述
1、InnoDB存儲引擎
Mysql版本=5.5 默認(rèn)的存儲引擎,MySQL推薦使用的存儲引擎。支持事務(wù),行級鎖定,外鍵約束。事務(wù)安全型存儲引擎。更加注重數(shù)據(jù)的完整性和安全性。
存儲格式 : 數(shù)據(jù),索引集中存儲,存儲于同一個表空間文件中。
InnoDB的行鎖模式及其加鎖方法: InnoDB中有以下兩種類型的行鎖:共享鎖(讀鎖: 允許事務(wù)對一條行數(shù)據(jù)進(jìn)行讀?。┖?互斥鎖(寫鎖: 允許事務(wù)對一條行數(shù)據(jù)進(jìn)行刪除或更新), 對于update,insert,delete語句,InnoDB會自動給設(shè)計(jì)的數(shù)據(jù)集加互斥鎖,對于普通的select語句,InnoDB不會加任何鎖。
InnoDB行鎖的實(shí)現(xiàn)方式: InnoDB行鎖是通過給索引上的索引項(xiàng)加鎖來實(shí)現(xiàn)的,如果沒有索引,InnoDB將通過隱藏的聚簇索引來對記錄加鎖。InnoDB這種行鎖實(shí)現(xiàn)特點(diǎn)意味著:如果不通過索引條件檢索數(shù)據(jù),那么InnoDB將對表中的所有記錄加鎖,實(shí)際效果跟表鎖一樣。
(1)在不通過索引條件查詢時,InnoDB會鎖定表中的所有記錄。
(2)Mysql的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果使用相同的索引鍵,是會出現(xiàn)沖突的。
(3)當(dāng)表有多個索引的時候,不同的事務(wù)可以使用不同的索引鎖定不同的行,但都是通過行鎖來對數(shù)據(jù)加鎖。
優(yōu)點(diǎn):
1、支持事務(wù)處理、ACID事務(wù)特性;
2、實(shí)現(xiàn)了SQL標(biāo)準(zhǔn)的四種隔離級別( 原子性( Atomicity )、一致性( Consistency )、隔離性(Isolation )和持續(xù)性(Durability ));
3、支持行級鎖和外鍵約束;
4、可以利用事務(wù)日志進(jìn)行數(shù)據(jù)恢復(fù)。
5、鎖級別為行鎖,行鎖優(yōu)點(diǎn)是適用于高并發(fā)的頻繁表修改,高并發(fā)是性能優(yōu)于 MyISAM。缺點(diǎn)是系統(tǒng)消耗較大。
6、索引不僅緩存自身,也緩存數(shù)據(jù),相比 MyISAM 需要更大的內(nèi)存。
缺點(diǎn):
因?yàn)樗鼪]有保存表的行數(shù),當(dāng)使用COUNT統(tǒng)計(jì)時會掃描全表。
使用場景:
(1)可靠性要求比較高,或者要求事務(wù);(2)表更新和查詢都相當(dāng)?shù)念l繁,并且表鎖定的機(jī)會比較大的情況。
2、 MyISAM存儲引擎
MySQL= 5.5 MySQL默認(rèn)的存儲引擎。ISAM:Indexed Sequential Access Method(索引順序存取方法)的縮寫,是一種文件系統(tǒng)。擅長與處理,高速讀與寫。
功能:
(1)支持?jǐn)?shù)據(jù)壓縮存儲,但壓縮后的表變成了只讀表,不可寫;如果需要更新數(shù)據(jù),則需要先解壓后更新。
(2)支持表級鎖定,不支持高并發(fā);
(3)支持并發(fā)插入。寫操作中的插入操作,不會阻塞讀操作(其他操作);
優(yōu)點(diǎn):
1.高性能讀取;
2.因?yàn)樗4媪吮淼男袛?shù),當(dāng)使用COUNT統(tǒng)計(jì)時不會掃描全表;
缺點(diǎn):
1、鎖級別為表鎖,表鎖優(yōu)點(diǎn)是開銷小,加鎖快;缺點(diǎn)是鎖粒度大,發(fā)生鎖沖動概率較高,容納并發(fā)能力低,這個引擎適合查詢?yōu)橹鞯臉I(yè)務(wù)。
2、此引擎不支持事務(wù),也不支持外鍵。
3、INSERT和UPDATE操作需要鎖定整個表;
使用場景:
(1)做很多count 的計(jì)算;(2)插入不頻繁,查詢非常頻繁;(3)沒有事務(wù)。
InnoDB和MyISAM一些細(xì)節(jié)上的差別:
1、InnoDB不支持FULLTEXT類型的索引,MySQL5.6之后已經(jīng)支持(實(shí)驗(yàn)性)。
2、InnoDB中不保存表的 具體行數(shù),也就是說,執(zhí)行select count() from table時,InnoDB要掃描一遍整個表來計(jì)算有多少行,但是MyISAM只要簡單的讀出保存好的行數(shù)即可。注意的是,當(dāng)count()語句包含 where條件時,兩種表的操作是一樣的。
3、對于AUTO_INCREMENT類型的字段,InnoDB中必須包含只有該字段的索引,但是在MyISAM表中,可以和其他字段一起建立聯(lián)合索引。
4、DELETE FROM table時,InnoDB不會重新建立表,而是一行一行的刪除。
5、LOAD TABLE FROM MASTER操作對InnoDB是不起作用的,解決方法是首先把InnoDB表改成MyISAM表,導(dǎo)入數(shù)據(jù)后再改成InnoDB表,但是對于使用的額外的InnoDB特性(例如外鍵)的表不適用。
6、另外,InnoDB表的行鎖也不是絕對的,如果在執(zhí)行一個SQL語句時MySQL不能確定要掃描的范圍,InnoDB表同樣會鎖全表。
1.索引概述
利用關(guān)鍵字,就是記錄的部分?jǐn)?shù)據(jù)(某個字段,某些字段,某個字段的一部分),建立與記錄位置的對應(yīng)關(guān)系,就是索引。索引的關(guān)鍵字一定是排序的。索引本質(zhì)上是表字段的有序子集,它是提高查詢速度最有效的方法。一個沒有建立任何索引的表,就相當(dāng)于一本沒有目錄的書,在每次查詢時就會進(jìn)行全表掃描,這樣會導(dǎo)致查詢效率極低、速度也極慢。如果建立索引,那么就好比一本添加的目錄,通過目錄的指引,迅速翻閱到指定的章節(jié),提升的查詢性能,節(jié)約了查詢資源。
2.索引種類
從索引的定義方式和用途中來看:主鍵索引,唯一索引,普通索引,全文索引。
無論任何類型,都是通過建立關(guān)鍵字與位置的對應(yīng)關(guān)系來實(shí)現(xiàn)的。索引是通過關(guān)鍵字找對應(yīng)的記錄的地址。
以上類型的差異:對索引關(guān)鍵字的要求不同。
關(guān)鍵字:記錄的部分?jǐn)?shù)據(jù)(某個字段,某些字段,某個字段的一部分)。
普通索引,index:對關(guān)鍵字沒有要求。
唯一索引,unique index:要求關(guān)鍵字不能重復(fù)。同時增加唯一約束。
主鍵索引,primary key:要求關(guān)鍵字不能重復(fù),也不能為NULL。同時增加主鍵約束。
全文索引,fulltext key:關(guān)鍵字的來源不是所有字段的數(shù)據(jù),而是從字段中提取的特別關(guān)鍵詞。
PS:這里主鍵索引和唯一索引的區(qū)別在于:主鍵索引不能為空值,唯一索引允許空值;主鍵索引在一張表內(nèi)只能創(chuàng)建一個,唯一索引可以創(chuàng)建多個。主鍵索引肯定是唯一索引,但唯一索引不一定是主鍵索引。
3.索引原則
如果索引不遵循使用原則,則可能導(dǎo)致索引無效。
(1)列獨(dú)立
如果需要某個字段上使用索引,則需要在字段參與的表達(dá)中,保證字段獨(dú)立在一側(cè)。否則索引不會用到索引, 例如這條sql就不會用到索引:select * from A where id+1=10;
(2)左原則
Like:匹配模式必須要左邊確定不能以通配符開頭。例如:select * from A where name like '%小明%' ,不會用到索引,而select * from A where name like '小明%' 就可以用到索引(name字段有建立索引),如果業(yè)務(wù)上需要用到'%小明%'這種方式,有兩種方法:1.可以考慮全文索引,但mysql的全文索引不支持中文;2.只查詢索引列或主鍵列,例如:select name from A where name like '%小明%' 或 select id from A where name like '%小明%' 或 select id,name from A where name like '%小明%' 這三種情況都會用到name的索引;
復(fù)合索引:一個索引關(guān)聯(lián)多個字段,僅僅針對左邊字段有效果,添加復(fù)合索引時,第一個字段很重要,只有包含第一個字段作為查詢條件的情況才會使用復(fù)合索引(必須用到建索引時選擇的第一個字段作為查詢條件,其他字段的順序無關(guān)),而且查詢條件只能出現(xiàn)and拼接,不能用or,否則則無法使用索引.
(3)OR的使用
必須要保證 OR 兩端的條件都存在可以用的索引,該查詢才可以使用索引。
(4)MySQL智能選擇
即使?jié)M足了上面說原則,MySQL也能棄用索引,例如:select * from A where id 1;這里棄用索引的主要原因:查詢即使使用索引,會導(dǎo)致出現(xiàn)大量的隨機(jī)IO,相對于從數(shù)據(jù)記錄的第一條遍歷到最后一條的順序IO開銷,還要大。
4.索引的使用場景
(1)索引檢索:檢索數(shù)據(jù)時使用索引。
(2)索引排序: 如果order by 排序需要的字段上存在索引,則可能使用到索引。
(3)索引覆蓋: 索引擁有的關(guān)鍵字內(nèi)容,覆蓋了查詢所需要的全部數(shù)據(jù),此時,就不需要在數(shù)據(jù)區(qū)獲取數(shù)據(jù),僅僅在索引區(qū)即可。覆蓋就是直接在索引區(qū)獲取內(nèi)容,而不需要在數(shù)據(jù)區(qū)獲取。例如: select name from A where name like '小明%';
建立索引索引時,不能僅僅考慮where檢索,同時考慮其他的使用場景。(在所有的where字段上增加索引,就是不合理的)
5.前綴索引
前綴索引是建立索引關(guān)鍵字一種方案。通常會使用字段的整體作為索引關(guān)鍵字。有時,即使使用字段前部分?jǐn)?shù)據(jù),也可以去識別某些記錄。就比如一個班級里,我要找王xx,假如姓王的只有1個人,那么就可以建一個關(guān)鍵字為'王'的前綴索引。語法:Index `index_name` (`index_field`(N))使用index_name前N個字符建立的索引。
6.索引失效
(1) 應(yīng)盡量避免在 where 子句中使用 != 或 操作符,否則將引擎放棄使用索引而進(jìn)行全表掃描;
(2) 應(yīng)盡量避免在 where 子句中使用 or 來連接條件,如果一個字段有索引,一個字段沒有索引,將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描;
(3) 應(yīng)盡量避免在 where 子句中對字段進(jìn)行 null 值判斷,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描;
(4)應(yīng)盡量避免在 where 子句中對字段進(jìn)行表達(dá)式操作,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描;如select id from t where num/2 = 100;
(5) 應(yīng)盡量避免在where子句中對字段進(jìn)行函數(shù)操作,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描;如:select id from t where substring(name,1,3) = ’abc’ ;
(6)應(yīng)盡量避免在where子句中對字段進(jìn)行類型轉(zhuǎn)換,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描; 如果列類型是字符串,那一定要在條件中將數(shù)據(jù)使用引號引用起來,如select id from t where id = 1;如果id字段在表設(shè)計(jì)中是varchar類型,那么即使id列上存的是數(shù)字,在查詢時也一定要用varchar去匹配,sql應(yīng)改為select id from t where id = '1';
(7)應(yīng)盡量避免在where子句中單獨(dú)引用復(fù)合索引里非第一位置的索引;
join 的兩種算法:BNL 和 NLJ
NLJ(Nested Loop Join)嵌套循環(huán)算法;以如下 SQL 為例:
select * from t1 join t2 on t1.a=t2.a
SQL 執(zhí)行時內(nèi)部流程是這樣的:
1. 先從 t1(假設(shè)這里 t1 被選為驅(qū)動表)中取出一行數(shù)據(jù) X;
2. 從 X 中取出關(guān)聯(lián)字段 a 值,去 t2 中進(jìn)行查找,滿足條件的行取出;
3. 重復(fù)1、2步驟,直到表 t1 最后一行循環(huán)結(jié)束。
這就是一個嵌套循環(huán)的過程,如果在被驅(qū)動表上查找數(shù)據(jù)時可以使用索引,總的對比計(jì)算次數(shù)等于驅(qū)動表滿足 where 條件的行數(shù)。假設(shè)這里 t1、t2都是1萬行,則只需要 1萬次計(jì)算,這里用到的是Index Nested-Loops Join(INLJ,基于索引的嵌套循環(huán)聯(lián)接)。
如果 t1、t2 的 a 字段都沒有索引,還按照上述的嵌套循環(huán)流程查找數(shù)據(jù)呢?每次在被驅(qū)動表上查找數(shù)據(jù)時都是一次全表掃描,要做1萬次全表掃描,掃描行數(shù)等于 1萬+1萬*1萬,這個效率很低,如果表行數(shù)更多,掃描行數(shù)動輒幾百億,所以優(yōu)化器肯定不會使用這樣的算法,而是選擇 BNL 算法;
BNLJ(Block Nested Loop Join)塊嵌套循環(huán)算法;
1. 把 t1 表(假設(shè)這里 t1 被選為驅(qū)動表)滿足條件的數(shù)據(jù)全部取出放到線程的 join buffer 中;
2. 每次取 t2 表一行數(shù)據(jù),去 joinbuffer 中進(jìn)行查找,滿足條件的行取出,直到表 t2 最后一行循環(huán)結(jié)束。
這個算法下,執(zhí)行計(jì)劃的 Extra 中會出現(xiàn) Using join buffer(Block Nested Loop),t1、t2 都做了一次全表掃描,總的掃描行數(shù)等于 1萬+1萬。但是由于 joinbuffer 維護(hù)的是一個無序數(shù)組,每次在 joinbuffer 中查找都要遍歷所有行,總的內(nèi)存計(jì)算次數(shù)等于1萬*1萬。另外如果 joinbuffer 不夠大放不下驅(qū)動表的數(shù)據(jù),則要分多次執(zhí)行上面的流程,會導(dǎo)致被驅(qū)動表也做多次全表掃描。
BNLJ相對于NLJ的優(yōu)點(diǎn)在于,驅(qū)動層可以先將部分?jǐn)?shù)據(jù)加載進(jìn)buffer,這種方法的直接影響就是將大大減少內(nèi)層循環(huán)的次數(shù),提高join的效率。
例如:
如果內(nèi)層循環(huán)有100條記錄,外層循環(huán)也有100條記錄,這樣的話,每次外層循環(huán)先將10條記錄放到buffer中,內(nèi)層循環(huán)的100條記錄每條與這個buffer中的10條記錄進(jìn)行匹配,只需要匹配內(nèi)層循環(huán)總記錄數(shù)次即可結(jié)束一次循環(huán)(在這里,即只需要匹配100次即可結(jié)束),然后將匹配成功的記錄連接后放入結(jié)果集中,接著,外層循環(huán)繼續(xù)向buffer中放入10條記錄,同理進(jìn)行匹配,并將成功的記錄連接后放入結(jié)果集。后續(xù)循環(huán)以此類推,直到循環(huán)結(jié)束,將結(jié)果集發(fā)給client為止。
可以發(fā)現(xiàn),若用NLJ,則需要100 * 100次才可結(jié)束,BNLJ則需要100 / block_size * 100 = 10 * 100次就可結(jié)束,大大減少了循環(huán)次數(shù)。
JOIN 按照功能大致分為如下三類:
JOIN、STRAIGHT_JOIN、INNER JOIN(內(nèi)連接,或等值連接):取得兩個表中存在連接匹配關(guān)系的記錄。
LEFT JOIN(左連接):取得左表(table1)完全記錄,即是右表(table2)并無對應(yīng)匹配記錄。
RIGHT JOIN(右連接):與 LEFT JOIN 相反,取得右表(table2)完全記錄,即是左表(table1)并無匹配對應(yīng)記錄。
注意:mysql不支持Full join,不過可以通過UNION 關(guān)鍵字來合并 LEFT JOIN 與 RIGHT JOIN來模擬FULL join。
mysql 多表連接查詢方式,因?yàn)閙ysql只支持NLJ算法,所以如果是小表驅(qū)動大表則效率更高;反之則效率下降;因此mysql對內(nèi)連接或等值連接的方式做了一個優(yōu)化,會去判斷join表的數(shù)據(jù)行大小,然后取數(shù)據(jù)行小的表為驅(qū)動表。
INNER JOIN、JOIN、WHERE等值連接和STRAIGHT_JOIN都能表示內(nèi)連接,那平時如何選擇呢?一般情況下用INNER JOIN、JOIN或者WHERE等值連接,因?yàn)镸ySQL 會按照"小表驅(qū)動大表的策略"進(jìn)行優(yōu)化。當(dāng)出現(xiàn)需要排序時,才考慮用STRAIGHT_JOIN指定某張表為驅(qū)動表。
兩表JOIN優(yōu)化
a.當(dāng)無order by條件時,根據(jù)實(shí)際情況,使用left/right/inner join即可,根據(jù)explain優(yōu)化 ;
b.當(dāng)有order by條件時,如select * from a inner join b where 1=1 and other condition order by a.col;使用explain解釋語句;
1)如果第一行的驅(qū)動表為a,則效率會非常高,無需優(yōu)化;
2)否則,因?yàn)橹荒軐︱?qū)動表字段直接排序的緣故,會出現(xiàn)using temporary,所以此時需要使用STRAIGHT_JOIN明確a為驅(qū)動表,來達(dá)到使用a.col上index的優(yōu)化目的;或者使用left join且Where條件中不含b的過濾條件,此時的結(jié)果集為a的全集,而STRAIGHT_JOIN為inner join且使用a作為驅(qū)動表。注:使用STRAIGHT_JOIN雖然不會using temporary,但也不是一定就能提高效率,如果a表數(shù)據(jù)遠(yuǎn)遠(yuǎn)超過b表,那么有可能使用STRAIGHT_JOIN時比原來的sql效率更低,所以怎么使用STRAIGHT_JOIN,還是要視情況而定。
在使用left join(或right join)時,應(yīng)該清楚的知道以下幾點(diǎn):
(1). on與 where的執(zhí)行順序
ON 條件(“A LEFT JOIN B ON 條件表達(dá)式”中的ON)用來決定如何從 B 表中檢索數(shù)據(jù)行。如果 B 表中沒有任何一行數(shù)據(jù)匹配 ON 的條件,將會額外生成一行所有列為 NULL 的數(shù)據(jù),在匹配階段 WHERE 子句的條件都不會被使用。僅在匹配階段完成以后,WHERE 子句條件才會被使用。它將從匹配階段產(chǎn)生的數(shù)據(jù)中檢索過濾。
所以我們要注意:在使用Left (right) join的時候,一定要在先給出盡可能多的匹配滿足條件,減少Where的執(zhí)行。
(2).注意ON 子句和 WHERE 子句的不同
即使右表的數(shù)據(jù)不滿足ON后面的條件,也會在結(jié)果集拼接一條為NULL的數(shù)據(jù)行,但WHERE后面的條件不一樣,右表不滿足WHERE的條件,左表關(guān)聯(lián)的數(shù)據(jù)也會被過濾掉。
(3).盡量避免子查詢,而用join
往往性能這玩意兒,更多時候體現(xiàn)在數(shù)據(jù)量比較大的時候,此時,我們應(yīng)該避免復(fù)雜的子查詢。
(1)in 和 not in 要慎用,如:select id from t where num in(1,2,3)對于連續(xù)的數(shù)值,能用 between 就不要用 in:select id from t where num between 1 and 3很多時候用 exists 代替 in 是一個好的選擇:select num from a where num in(select num from b)用下面的語句替換:select num from a where exists(select 1 from b where num=a.num)
(2)Update 語句,如果只更改1、2個字段,不要Update全部字段,否則頻繁調(diào)用會引起明顯的性能消耗,同時帶來大量日志。
(3)join語句,MySQL里面的join是用小表去驅(qū)動大表,而由于MySQL join實(shí)現(xiàn)的原理就是做循環(huán),比如left join就是對左邊的數(shù)據(jù)進(jìn)行循環(huán)去驅(qū)動右邊的表,左邊有m條記錄匹配,右邊有n條記錄那么就是做m次循環(huán),每次掃描n行數(shù)據(jù),總掃面行數(shù)是m*n行數(shù)據(jù)。左邊返回的結(jié)果集的大小就決定了循環(huán)的次數(shù),故單純的用小表去驅(qū)動大表不一定的正確的,小表的結(jié)果集可能也大于大表的結(jié)果集,所以寫join的時候盡可能的先估計(jì)兩張表的可能結(jié)果集,用小結(jié)果集去驅(qū)動大結(jié)果集.值得注意的是在使用left/right join的時候,從表的條件應(yīng)寫在on之后,主表應(yīng)寫在where之后.否則MySQL會當(dāng)作普通的連表查詢;
(4)select count(*) from table;這樣不帶任何條件的count會引起全表掃描,并且沒有任何業(yè)務(wù)意義,是一定要杜絕的;
(5)select * from t 這種語句要盡量避免,使用具體的字段代替*,更有實(shí)際意義,需要什么字段就返回什么字段;
(6)數(shù)據(jù)量大的情況下,limit要慎用,因?yàn)槭褂胠imit m,n方式分頁時,mysql每次都是查詢前m+n條,然后舍棄前m條,所以m越大,偏移量越大,性能就越差。比如:select * from A limit 1000000,20這鐘,查詢效率就會非常低,當(dāng)分頁的頁數(shù)大于一定的數(shù)量之后,就可以換種方式來分頁:select * from A a join (select id from A limit 1000000,20) b on a.id=b.id;
鎖是計(jì)算機(jī)協(xié)調(diào)多個進(jìn)程或線程并發(fā)訪問某一資源的機(jī)制,在數(shù)據(jù)庫中,除傳統(tǒng)的計(jì)算資源(CPU、RAM、I/O)爭用外,數(shù)據(jù)也是一種供許多用戶共享的資源,如何保證數(shù)據(jù)并發(fā)訪問的一致性,有效性是所有數(shù)據(jù)庫必須解決的一個問題,鎖沖突也是影響數(shù)據(jù)庫并發(fā)訪問性能的一個重要因素,從這個角度來說,鎖對數(shù)據(jù)庫而言是尤其重要,也更加復(fù)雜。MySQL中的鎖,按照鎖的粒度分為:1、全局鎖,就鎖定數(shù)據(jù)庫中的所有表。2、表級鎖,每次操作鎖住整張表。3、行級鎖,每次操作鎖住對應(yīng)的行數(shù)據(jù)。
全局鎖就是對整個數(shù)據(jù)庫實(shí)例加鎖,加鎖后整個實(shí)例就處于只讀狀態(tài),后續(xù)的DML的寫語句,DDL語句,已經(jīng)更新操作的事務(wù)提交語句都將阻塞。其典型的使用場景就是做全庫的邏輯備份,對所有的表進(jìn)行鎖定,從而獲取一致性視圖,保證數(shù)據(jù)的完整性。但是對數(shù)據(jù)庫加全局鎖是有弊端的,如在主庫上備份,那么在備份期間都不能執(zhí)行更新,業(yè)務(wù)會受影響,第二如果是在從庫上備份,那么在備份期間從庫不能執(zhí)行主庫同步過來的二進(jìn)制日志,會導(dǎo)致主從延遲。
解決辦法是在innodb引擎中,備份時加上--single-transaction參數(shù)來完成不加鎖的一致性數(shù)據(jù)備份。
添加全局鎖: flush tables with read lock; 解鎖 unlock tables。
表級鎖,每次操作會鎖住整張表.鎖定粒度大,發(fā)送鎖沖突的概率最高,并發(fā)讀最低,應(yīng)用在myisam、innodb、BOB等存儲引擎中。表級鎖分為: 表鎖、元數(shù)據(jù)鎖(meta data lock, MDL)和意向鎖。
表鎖又分為: 表共享讀鎖 read lock、表獨(dú)占寫鎖write lock
語法: 1、加鎖 lock tables 表名 ... read/write
2、釋放鎖 unlock tables 或者關(guān)閉客戶端連接
注意: 讀鎖不會阻塞其它客戶端的讀,但是會阻塞其它客戶端的寫,寫鎖既會阻塞其它客戶端的讀,又會阻塞其它客戶端的寫。大家可以拿一張表來測試看看。
元數(shù)據(jù)鎖,在加鎖過程中是系統(tǒng)自動控制的,無需顯示使用,在訪問一張表的時候會自動加上,MDL鎖主要作用是維護(hù)表元數(shù)據(jù)的數(shù)據(jù)一致性,在表上有活動事務(wù)的時候,不可以對元數(shù)據(jù)進(jìn)行寫入操作。為了避免DML和DDL沖突,保證讀寫的正確性。
在MySQL5.5中引入了MDL,當(dāng)對一張表進(jìn)行增刪改查的時候,加MDL讀鎖(共享);當(dāng)對表結(jié)構(gòu)進(jìn)行變更操作時,加MDL寫鎖(排他).
查看元數(shù)據(jù)鎖:
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema_metadata_locks;
意向鎖,為了避免DML在執(zhí)行時,加的行鎖與表鎖的沖突,在innodb中引入了意向鎖,使得表鎖不用檢查每行數(shù)據(jù)是否加鎖,使用意向鎖來減少表鎖的檢查。意向鎖分為,意向共享鎖is由語句select ... lock in share mode添加。意向排他鎖ix,由insert,update,delete,select。。。for update 添加。
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_lock;
行級鎖,每次操作鎖住對應(yīng)的行數(shù)據(jù),鎖定粒度最小,發(fā)生鎖沖突的概率最高,并發(fā)讀最高,應(yīng)用在innodb存儲引擎中。
innodb的數(shù)據(jù)是基于索引組織的,行鎖是通過對索引上的索引項(xiàng)加鎖來實(shí)現(xiàn)的,而不是對記錄加的鎖,對于行級鎖,主要分為以下三類:
1、行鎖或者叫record lock記錄鎖,鎖定單個行記錄的鎖,防止其他事物對次行進(jìn)行update和delete操作,在RC,RR隔離級別下都支持。
2、間隙鎖Gap lock,鎖定索引記錄間隙(不含該記錄),確保索引記錄間隙不變,防止其他事物在這個間隙進(jìn)行insert操作,產(chǎn)生幻讀,在RR隔離級別下都支持。
3、臨鍵鎖Next-key-lock,行鎖和間隙鎖組合,同時鎖住數(shù)據(jù),并鎖住數(shù)據(jù)前面的間隙Gap,在RR隔離級別下支持。
innodb實(shí)現(xiàn)了以下兩種類型的行鎖
1、共享鎖 S: 允許一個事務(wù)去讀一行,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排他鎖。
2、排他鎖 X: 允許獲取排他鎖的事務(wù)更新數(shù)據(jù),阻止其他事務(wù)獲得相同數(shù)據(jù)集的共享鎖和排他鎖。
insert 語句 排他鎖 自動添加的
update語句 排他鎖 自動添加
delete 語句 排他鎖 自動添加
select 正常查詢語句 不加鎖 。。。
select 。。。lock in share mode 共享鎖 需要手動在select 之后加lock in share mode
select 。。。for update 排他鎖 需要手動在select之后添加for update
默認(rèn)情況下,innodb在repeatable read事務(wù)隔離級別運(yùn)行,innodb使用next-key鎖進(jìn)行搜索和索引掃描,以防止幻讀。
間隙鎖唯一目的是防止其它事務(wù)插入間隙,間隙鎖可以共存,一個事務(wù)采用的間隙鎖不會阻止另一個事務(wù)在同一間隙上采用的間隙鎖。
MySQL8.x 中新增了一個輕量級的備份鎖,它允許在 online 備份的時候進(jìn)行 DML 操作,同時可防止快照不一致。這個鎖禁止的操作很少,它禁止的操作包括:
文件的創(chuàng)建、刪除、改名
賬戶的管理
REPAIR TABLE
TRUNCATE TABLE
OPTIMIZE TABLE
備份鎖由?lock instance for backup?和?unlock instance?語法組成。使用這些語句需要 BACKUP_ADMIN 權(quán)限。