首先說一下?這個(gè)加鎖是個(gè)啥子過程呢
我們拿一條記錄舉例,這個(gè)記錄就放在這,沒人操作它,他就沒生成鎖結(jié)構(gòu),?直到有個(gè)事務(wù)操作它了,然后給它才生成了個(gè)鎖結(jié)構(gòu),鎖結(jié)構(gòu)兩個(gè)參數(shù)?trx(生成該鎖的事務(wù))?is_waiting(正在等待就是:true?沒在等待就是?false) (鎖里面很多參數(shù)?這里這是為了方便理解例舉了兩個(gè)),然后下一個(gè)事務(wù)試圖操作這條記錄,就會(huì)發(fā)現(xiàn)本記錄已經(jīng)加上鎖了,就要再生成一個(gè)鎖(本次鎖的?等待狀態(tài)就是:true)
舉個(gè)例子
有個(gè)記錄?我們稱為?record
然后事務(wù)A?對(duì)record進(jìn)行操作?事務(wù)開始?record加了一個(gè)鎖?參數(shù)? trx:A? is_waiting:false
然后來了個(gè)byd事務(wù)B?又想對(duì)record進(jìn)行操作?事務(wù)開始?發(fā)現(xiàn)?哎?這有個(gè)鎖了?然后它也生成一個(gè)鎖
鎖參數(shù)?trx:B? is_waiting:true?? is_waiting:true就代表它正在等待?事務(wù)B就停在這里了?直到變成false才能繼續(xù)執(zhí)行
然后事務(wù)A提交?釋放鎖?然后事務(wù)B獲得鎖?參數(shù)變成trx:B? is_waiting:false?
表級(jí)鎖
S鎖
共享鎖(英語原文是share什么玩意?反正S是share?查了一下是Share Locks)? 共享鎖就是咋的呢?一個(gè)事務(wù)A對(duì)此表加了個(gè)S鎖?然后又來個(gè)事務(wù)B試圖加了個(gè)S鎖?這次不會(huì)像正常流程一樣阻塞?而是會(huì)加鎖成功?此條記錄同時(shí)有兩個(gè)鎖(都是is_waiting:false的)
X鎖
獨(dú)占鎖(Exclusive Locks)?與其他鎖沖突(包括S鎖)?
這里就得具體描述了
事務(wù)A加了個(gè) X鎖?事務(wù)B加了個(gè)S鎖?會(huì)阻塞
事務(wù)A加了個(gè) S鎖? 事務(wù)B加了個(gè)X鎖?會(huì)阻塞?懂了吧?就是不管X鎖先加后加?都會(huì)阻塞
事務(wù)A加了個(gè) X鎖? 事務(wù)B加了個(gè)X鎖?阻塞
事務(wù)A加了個(gè)S鎖?事務(wù)B加了個(gè)S鎖?不阻塞?
IS鎖:意向共享鎖?
IX鎖:意向獨(dú)占鎖
這里是啥意思呢?就是說我們的表級(jí)鎖和行級(jí)鎖之間也是沖突的?就比如我們行級(jí)加了一個(gè)X鎖
這時(shí)我們想在表上加一個(gè)X鎖?會(huì)阻塞?但是我們?cè)趺慈タ催@個(gè)表里面有沒有行級(jí)X鎖嗎?遍歷每條記錄嗎?那老MYSQL一整幾百萬條記錄?遍歷累死
所以提出了?意向鎖?IS鎖就是說明當(dāng)前表內(nèi)有S鎖?IX鎖就是說明當(dāng)前表內(nèi)有X鎖
那么這個(gè)阻塞的問題呢
IX鎖和IX鎖沖不沖突??不沖突?我不同記錄加了X鎖?這個(gè)表也被加入了多次IX鎖
那IX和IS鎖呢??也不沖突?不同記錄加了X鎖 S鎖?表上加了IX?IS?可以
那X和IX呢? (這里X指表級(jí)的)? 沖突?IX就說明當(dāng)前表內(nèi)有X鎖了?而X表級(jí)鎖和行級(jí)鎖沖突 (可以把表級(jí)鎖當(dāng)做?所有記錄都加了行鎖呢)
同理 S和IX X和IS....
AUTO-INC鎖
眼熟不?像不像某個(gè)字段(AUTO_INCREMENT?自增列)?
我們?cè)诓迦胗涗浀臅r(shí)候?自增列是不需要我們自己指定值的?那么如果多個(gè)事務(wù)同時(shí)插入的時(shí)候
就會(huì)亂套?所以MYSQL提供了兩種解決方法
1.AUTO-INC鎖:行插入語句時(shí)就在表級(jí)別加一個(gè) AUTO-INC 鎖,然后為每條待插入記錄的 AUTO_INCREMENT 修飾的列分配遞增的值,在該語句執(zhí)行結(jié)束后,再把 AUTO-INC 鎖釋放掉。這樣一個(gè)事務(wù)在持有 AUTO-INC 鎖的過程中,其他事務(wù)的插入語句都要被阻塞,可以保證一個(gè)語句中分配的遞增值是連續(xù)的。
2.輕量級(jí)鎖?如果因?yàn)橐涣芯妥枞胁迦氩僮魑疵庥悬c(diǎn)太耽誤時(shí)間了?所以就提出了個(gè)這么個(gè)玩意
在為插入語句生成 AUTO_INCREMENT 修飾的列的值時(shí)獲取一下這個(gè)輕量級(jí)鎖,然后生成本次插入語句需要用到的 AUTO_INCREMENT 列的值之后,就把該輕量級(jí)鎖釋放掉,并不需要等到整個(gè)插入語句執(zhí)行完才釋放鎖。
如果我們的插入語句在執(zhí)行前就可以確定具體要插入多少條記錄,比方說我們上邊舉的關(guān)于表 t 的例子中,在語句執(zhí)行前就可以確定要插入2條記錄,那么一般采用輕量級(jí)鎖的方式對(duì) AUTO_INCREMENT 修飾的列進(jìn)行賦值。這種方式可以避免鎖定表,可以提升插入性能。
對(duì)吧對(duì)吧?提前知道插入多少記錄了?就可以提前準(zhǔn)備了
可以通過innodb_autoinc_lock_mode的系統(tǒng)變量來控制到底使用上述兩種方式中的哪種來為AUTO_INCREMENT修飾的列進(jìn)行賦值,當(dāng)innodb_autoinc_lock_mode值為0時(shí),一律采用AUTO-INC鎖;當(dāng)innodb_autoinc_lock_mode值為2時(shí),一律采用輕量級(jí)鎖;當(dāng)innodb_autoinc_lock_mode值為1時(shí),兩種方式混著來(也就是在插入記錄數(shù)量確定時(shí)采用輕量級(jí)鎖,不確定時(shí)使用AUTO-INC鎖)。不過當(dāng)innodb_autoinc_lock_mode值為2時(shí),可能會(huì)造成不同事務(wù)中的插入語句為AUTO_INCREMENT修飾的列生成的值是交叉的,在有主從復(fù)制的場景是不安全的.(性能的代價(jià))
順便說下:表級(jí)的X鎖 S鎖基本用不上?鎖住全表實(shí)在太重了?耽誤事?對(duì)應(yīng)的IX?IS鎖也不常用
行級(jí)鎖
重點(diǎn)來了啊重點(diǎn)來了
記錄鎖(Record Locks)
記錄鎖分x鎖和s鎖不多贅述
間隙鎖(Gap Locks)
鎖如其名?鎖住間隙的
假如說有 1? 2? 3??4這幾條記錄在?記錄3上加間隙鎖就是? 鎖住了2和3之間的間隙?讓其他事務(wù)無法在這個(gè)間隙上面加記錄? 假如說有一條記錄來了?想在2和3之間插入?看一眼它后面的記錄?我超!!!!鎖!!!!
額?然后就阻塞了
這個(gè) gap鎖 的提出僅僅是為了防止插入幻影記錄而提出的,雖然有 共享gap鎖 和 獨(dú)占gap鎖 這樣的說法,但是它們起到的作用都是相同的。而且如果你對(duì)一條記錄加了 gap鎖 (不論是 共享gap鎖 還是 獨(dú)占gap鎖 ),并不會(huì)限制其他事務(wù)對(duì)這條記錄加 正經(jīng)記錄鎖 或者繼續(xù)加 gap鎖 ,再強(qiáng)調(diào)一遍, gap鎖 的作用僅僅是為了防止插入幻影記錄的而已。
啊?你會(huì)不會(huì)想?那我要是想在最后一條記錄前那個(gè)卵間隙加記錄?那么我缺失的這個(gè)鎖這一塊誰來給我補(bǔ)呢?
每個(gè)數(shù)據(jù)頁中有?兩個(gè)偽記錄
Infimum 記錄,表示該頁面中最小的記錄。Supremum 記錄,表示該頁面中最大的記錄。
假如說我在那個(gè)(4,正無窮)的這個(gè)間隙上面加鎖?就直接在這個(gè)Supremum 記錄就可以了
Next-Key Locks?
我又想鎖住記錄?又想鎖住記錄前面的間隙?就使用Next-Key Locks吧!
Next-key?Locks本質(zhì)上等于倆鎖? 記錄鎖+間隙鎖
Insert Intention Locks
插入意向鎖?當(dāng)一條記錄想插入這個(gè)間隙的時(shí)候?發(fā)現(xiàn)這個(gè)位置被間隙鎖鎖住了?于是它在這個(gè)時(shí)候開始等待?等待的過程生成一個(gè)插入意向鎖?表示有記錄想在這個(gè)間隙里面插入記錄?但是還在等待
插入意向鎖是一種特殊的間隙鎖
有個(gè)事務(wù)A對(duì)這個(gè)間隙加鎖了?然后來了一個(gè)事務(wù)B?事務(wù)C想在這個(gè)間隙里面加記錄 B C來一看?好嘛
這有個(gè)間隙鎖?然后他倆生成了插入意向鎖?is_wait是false? 然后A的間隙鎖釋放后,他倆就可以獲得到插入意向鎖(實(shí)質(zhì)上只是is_waiting調(diào)整成了true)?B和C之間也不會(huì)阻塞?可以同時(shí)獲取到鎖?所以有啥用呢
只有當(dāng)被間隙鎖阻塞的時(shí)候才會(huì)生成插入意向鎖
隱式鎖
我們前邊說一個(gè)事務(wù)在執(zhí)行 INSERT 操作時(shí),如果即將插入的 間隙 已經(jīng)被其他事務(wù)加了 gap鎖 ,那么本次INSERT 操作會(huì)阻塞,并且當(dāng)前事務(wù)會(huì)在該間隙上加一個(gè) 插入意向鎖 ,否則一般情況下 INSERT 操作是不加鎖的。那如果一個(gè)事務(wù)首先插入了一條記錄(此時(shí)并沒有與該記錄關(guān)聯(lián)的鎖結(jié)構(gòu))
如果此時(shí)對(duì)它進(jìn)行讀操作(可以看下面的部分)? SELECT ... LOCK IN SHARE MODE 語句讀取這條事務(wù),也就是在要獲取這條記錄的 S鎖 ,或者使用 SELECT ... FOR UPDATE 語句讀取這條事務(wù)或者直接修改這條記錄,也就是要獲取這條記錄的 X鎖,閣下如果不生成任何鎖應(yīng)對(duì)的話?就可能產(chǎn)生臟讀
立即修改這條記錄,也就是要獲取這條記錄的 X鎖 ,該咋辦?如果允許這種情況的發(fā)生,那么可能產(chǎn)生 臟寫 問題。
我們分兩種情況討論解決方案
對(duì)于聚簇索引記錄來說,有一個(gè) trx_id 隱藏列,該隱藏列記錄著最后改動(dòng)該記錄的 事務(wù)id 。那么如果在當(dāng)前事務(wù)中新插入一條聚簇索引記錄后,該記錄的 trx_id 隱藏列代表的的就是當(dāng)前事務(wù)的事務(wù)id ,如果其他事務(wù)此時(shí)想對(duì)該記錄添加 S鎖 或者 X鎖 時(shí),首先會(huì)看一下該記錄的 trx_id 隱藏列代表的事務(wù)是否是當(dāng)前的活躍事務(wù),如果是的話,就代表著有一個(gè)插入操作正在進(jìn)行 ,那么就幫助當(dāng)前事務(wù)創(chuàng)建一個(gè) X鎖 (也就是為當(dāng)前事務(wù)創(chuàng)建一個(gè)鎖結(jié)構(gòu), is_waiting 屬性是 false ),然后自己進(jìn)入等待狀態(tài)(也就是為自己也創(chuàng)建一個(gè)鎖結(jié)構(gòu), is_waiting 屬性是 true )
先發(fā)現(xiàn)鎖沖突?然后再加鎖?所以是隱式鎖
對(duì)于二級(jí)索引記錄來說,本身并沒有 trx_id 隱藏列,但是在二級(jí)索引頁面的 Page Header 部分有一個(gè) PAGE_MAX_TRX_ID 屬性,該屬性代表對(duì)該頁面做改動(dòng)的最大的 事務(wù)id ,如果PAGE_MAX_TRX_ID 屬性值小于當(dāng)前最小的活躍 事務(wù)id ,那么說明對(duì)該頁面做修改的事務(wù)都已經(jīng)提交了,(如果當(dāng)前事務(wù)正在活躍?那么這個(gè)PAGE_MAX_TRX_ID 屬性值要么等于這個(gè)修改事務(wù)?要么是比這個(gè)修改事務(wù)的id更大?說明后面又新來了一個(gè)修改操作?而比它還小?只能說明修改操作都已經(jīng)提交了)否則就需要在頁面中定位到對(duì)應(yīng)的二級(jí)索引記錄,然后回表找到它對(duì)應(yīng)的聚簇索引記錄,然后再重復(fù) 情景一 的做法。
Insert的隱式鎖
當(dāng)事務(wù)需要加鎖的時(shí),如果這個(gè)鎖不可能發(fā)生沖突,InnoDB會(huì)跳過加鎖環(huán)節(jié),這種機(jī)制稱為隱式鎖。隱式鎖是 InnoDB 實(shí)現(xiàn)的一種延遲加鎖機(jī)制,其特點(diǎn)是只有在可能發(fā)生沖突時(shí)才加鎖,從而減少了鎖的數(shù)量,提高了系統(tǒng)整體性能。
隱式鎖就是在 Insert 過程中不加鎖,只有在特殊情況下,才會(huì)將隱式鎖轉(zhuǎn)換為顯示鎖,這里我們列舉兩個(gè)場景。
- 如果記錄之間加有間隙鎖,為了避免幻讀,此時(shí)是不能插入記錄的;
- 如果 Insert 的記錄和已有記錄存在唯一鍵沖突,此時(shí)也不能插入記錄;
?間隙鎖情況
每插入一條新記錄,都需要看一下待插入記錄的下一條記錄上是否已經(jīng)被加了間隙鎖,如果已加間隙鎖,此時(shí)會(huì)生成一個(gè)插入意向鎖,然后鎖的狀態(tài)設(shè)置為等待狀態(tài)(PS:MySQL 加鎖時(shí),是先生成鎖結(jié)構(gòu),然后設(shè)置鎖的狀態(tài),如果鎖狀態(tài)是等待狀態(tài),并不是意味著事務(wù)成功獲取到了鎖,只有當(dāng)鎖狀態(tài)為正常狀態(tài)時(shí),才代表事務(wù)成功獲取到了鎖),現(xiàn)象就是 Insert 語句會(huì)被阻塞。
?索引重復(fù)
如果在插入新記錄時(shí),插入了一個(gè)與「已有的記錄的主鍵或者唯一二級(jí)索引列值相同」的記錄(不過可以有多條記錄的唯一二級(jí)索引列的值同時(shí)為NULL,這里不考慮這種情況),此時(shí)插入就會(huì)失敗,然后對(duì)于這條記錄加上了?S 型的鎖。
如果主鍵索引重復(fù),插入新記錄的事務(wù)會(huì)給已存在的主鍵值重復(fù)的聚簇索引記錄添加 S 型記錄鎖。
如果唯一二級(jí)索引重復(fù),插入新記錄的事務(wù)都會(huì)給已存在的二級(jí)索引列值重復(fù)的二級(jí)索引記錄添加 S 型 next-key 鎖。
總之就是先發(fā)現(xiàn)沖突然后再加鎖?如果沒有沖突就不加鎖 (比喻就是有一個(gè)隱形的鎖?一旦碰到?jīng)_突才會(huì)顯現(xiàn)出來)
?讀時(shí)的鎖操作
我們前邊說在采用 加鎖 方式解決 臟讀 、 不可重復(fù)讀 、 幻讀 這些問題時(shí),讀取一條記錄時(shí)需要獲取一下該記錄的 S鎖 ,其實(shí)這是不嚴(yán)謹(jǐn)?shù)?,有時(shí)候想在讀取記錄時(shí)就獲取記錄的 X鎖 ,來禁止別的事務(wù)讀寫該記錄
對(duì)讀取的記錄加 S鎖 : SELECT ... LOCK IN SHARE MODE;
對(duì)讀取的記錄加 X鎖 : SELECT ... FOR UPDATE;
這里的X鎖仍與S鎖沖突(會(huì)阻塞)
當(dāng)前讀時(shí)(update、insert、delete,這些語句執(zhí)行前都會(huì)查詢最新版本的數(shù)據(jù),然后再做進(jìn)一步的操作)? 會(huì)在讀的范圍上加一個(gè)間隙鎖
寫時(shí)的鎖操作
DELETE :對(duì)一條記錄做 DELETE 操作的過程其實(shí)是先在 B+ 樹中定位到這條記錄的位置,然后獲取一下這條記錄的 X鎖 ,然后再執(zhí)行 delete mark 操作(先標(biāo)記?在事務(wù)提交后才由專門的線程做purge操作,把它加入到垃圾鏈表
中)。我們也可以把這個(gè)定位待刪除記錄在 B+ 樹中位置的過程看成是一個(gè)獲取 X鎖 的 鎖定讀?
UPDATE:
如果未修改該記錄的鍵值并且被更新的列占用的存儲(chǔ)空間在修改前后未發(fā)生變化 則先在 B+ 樹中定位到這條記錄的位置,然后再獲取一下記錄的 X鎖 ,最后在原記錄的位置進(jìn)行修改操作。其實(shí)我們也可以把這個(gè)定位待修改記錄在 B+ 樹中位置的過程看成是一個(gè)獲取 X鎖 的 鎖定讀?
如果未修改該記錄的鍵值并且至少有一個(gè)被更新的列占用的存儲(chǔ)空間在修改前后發(fā)生變化,則先在B+ 樹中定位到這條記錄的位置,然后獲取一下記錄的 X鎖 ,將該記錄徹底刪除掉(就是把記錄徹底移入垃圾鏈表),最后再插入一條新記錄。這個(gè)定位待修改記錄在 B+ 樹中位置的過程看成是一個(gè)獲取 X鎖 的 鎖定讀 ,新插入的記錄由 INSERT 操作提供的 隱式鎖 進(jìn)行保護(hù)。
如果修改了該記錄的鍵值,則相當(dāng)于在原記錄上做 DELETE 操作之后再來一次 INSERT 操作,加鎖操作就需要按照 DELETE 和 INSERT 的規(guī)則進(jìn)行了。文章來源:http://www.zghlxwxcb.cn/news/detail-579434.html
注意后兩種的區(qū)別?記錄移入垃圾鏈表和DELETE操作是不同的文章來源地址http://www.zghlxwxcb.cn/news/detail-579434.html
到了這里,關(guān)于MYSQL中的鎖(面試難點(diǎn)重點(diǎn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!