mysql取消mvvc机制_MySQL探秘(六):InnoDB一致性非锁定读
一致性非鎖定讀(consistent nonlocking read)是指InnoDB存儲(chǔ)引擎通過(guò)多版本控制(MVVC)讀取當(dāng)前數(shù)據(jù)庫(kù)中行數(shù)據(jù)的方式。如果讀取的行正在執(zhí)行DELETE或UPDATE操作,這時(shí)讀取操作不會(huì)因此去等待行上鎖的釋放。相反地,InnoDB會(huì)去讀取行的一個(gè)快照。
上圖直觀地展現(xiàn)了InnoDB一致性非鎖定讀的機(jī)制。之所以稱(chēng)其為非鎖定讀,是因?yàn)椴恍枰却猩吓潘i的釋放。快照數(shù)據(jù)是指該行的之前版本的數(shù)據(jù),每行記錄可能有多個(gè)版本,一般稱(chēng)這種技術(shù)為行多版本技術(shù)。由此帶來(lái)的并發(fā)控制,稱(chēng)之為多版本并發(fā)控制(Multi Version Concurrency Control, MVVC)。InnoDB是通過(guò)undo log來(lái)實(shí)現(xiàn)MVVC。undo log本身用來(lái)在事務(wù)中回滾數(shù)據(jù),因此快照數(shù)據(jù)本身是沒(méi)有額外開(kāi)銷(xiāo)。此外,讀取快照數(shù)據(jù)是不需要上鎖的,因?yàn)闆](méi)有事務(wù)需要對(duì)歷史的數(shù)據(jù)進(jìn)行修改操作。
一致性非鎖定讀是InnoDB默認(rèn)的讀取方式,即讀取不會(huì)占用和等待行上的鎖。但是并不是在每個(gè)事務(wù)隔離級(jí)別下都是采用此種方式。此外,即使都是使用一致性非鎖定讀,但是對(duì)于快照數(shù)據(jù)的定義也各不相同。
在事務(wù)隔離級(jí)別READ COMMITTED和REPEATABLE READ下,InnoDB使用一致性非鎖定讀。然而,對(duì)于快照數(shù)據(jù)的定義卻不同。在READ COMMITTED事務(wù)隔離級(jí)別下,一致性非鎖定讀總是讀取被鎖定行的最新一份快照數(shù)據(jù)。而在REPEATABLE READ事務(wù)隔離級(jí)別下,則讀取事務(wù)開(kāi)始時(shí)的行數(shù)據(jù)版本。
我們下面舉個(gè)例子來(lái)詳細(xì)說(shuō)明一下上述的情況。
# session A
mysql> BEGIN;
mysql> SELECT * FROM test WHERE id = 1;
我們首先在會(huì)話A中顯示地開(kāi)啟一個(gè)事務(wù),然后讀取test表中的id為1的數(shù)據(jù),但是事務(wù)并沒(méi)有結(jié)束。于此同時(shí),用戶(hù)在開(kāi)啟另一個(gè)會(huì)話B,這樣可以模擬并發(fā)的操作,然后對(duì)會(huì)話B做出如下的操作:
# session B
mysql> BEGIN;
mysql> UPDATE test SET id = 3 WHERE id = 1;
在會(huì)話B的事務(wù)中,將test表中id為1的記錄修改為id=3,但是事務(wù)同樣也沒(méi)有提交,這樣id=1的行其實(shí)加了一個(gè)排他鎖。由于InnoDB在READ COMMITTED和REPEATABLE READ事務(wù)隔離級(jí)別下使用一致性非鎖定讀,這時(shí)如果會(huì)話A再次讀取id為1的記錄,仍然能夠讀取到相同的數(shù)據(jù)。此時(shí),READ COMMITTED和REPEATABLE READ事務(wù)隔離級(jí)別沒(méi)有任何區(qū)別。
如上圖所示,當(dāng)會(huì)話B提交事務(wù)后,會(huì)話A再次運(yùn)行SELECT * FROM test WHERE id = 1的SQL語(yǔ)句時(shí),兩個(gè)事務(wù)隔離級(jí)別下得到的結(jié)果就不一樣了。
對(duì)于READ COMMITTED的事務(wù)隔離級(jí)別,它總是讀取行的最新版本,如果行被鎖定了,則讀取該行版本的最新一個(gè)快照。因?yàn)闀?huì)話B的事務(wù)已經(jīng)提交,所以在該隔離級(jí)別下上述SQL語(yǔ)句的結(jié)果集是空的。
對(duì)于REPEATABLE READ的事務(wù)隔離級(jí)別,總是讀取事務(wù)開(kāi)始時(shí)的行數(shù)據(jù),因此,在該隔離級(jí)別下,上述SQL語(yǔ)句仍然會(huì)獲得相同的數(shù)據(jù)。
MVVC
我們首先來(lái)看一下wiki上對(duì)MVVC的定義:
Multiversion concurrency control (MCC or MVCC), is a concurrency control
method commonly used by database management systems to provide
concurrent access to the database and in programming languages to
implement transactional memory.
由定義可知,MVVC是用于數(shù)據(jù)庫(kù)提供并發(fā)訪問(wèn)控制的并發(fā)控制技術(shù)。
數(shù)據(jù)庫(kù)的并發(fā)控制機(jī)制有很多,最為常見(jiàn)的就是鎖機(jī)制。鎖機(jī)制一般會(huì)給競(jìng)爭(zhēng)資源加鎖,阻塞讀或者寫(xiě)操作來(lái)解決事務(wù)之間的競(jìng)爭(zhēng)條件,最終保證事務(wù)的可串行化。而MVVC則引入了另外一種并發(fā)控制,它讓讀寫(xiě)操作互不阻塞,每一個(gè)寫(xiě)操作都會(huì)創(chuàng)建一個(gè)新版本的數(shù)據(jù),讀操作會(huì)從有限多個(gè)版本的數(shù)據(jù)中挑選一個(gè)最合適的結(jié)果直接返回,由此解決了事務(wù)的競(jìng)爭(zhēng)條件。
考慮一個(gè)現(xiàn)實(shí)場(chǎng)景。管理者要查詢(xún)所有用戶(hù)的存款總額,假設(shè)除了用戶(hù)A和用戶(hù)B之外,其他用戶(hù)的存款總額都為0,A、B用戶(hù)各有存款1000,所以所有用戶(hù)的存款總額為2000。但是在查詢(xún)過(guò)程中,用戶(hù)A會(huì)向用戶(hù)B進(jìn)行轉(zhuǎn)賬操作。轉(zhuǎn)賬操作和查詢(xún)總額操作的時(shí)序圖如下圖所示。
如果沒(méi)有任何的并發(fā)控制機(jī)制,查詢(xún)總額事務(wù)先讀取了用戶(hù)A的賬戶(hù)存款,然后轉(zhuǎn)賬事務(wù)改變了用戶(hù)A和用戶(hù)B的賬戶(hù)存款,最后查詢(xún)總額事務(wù)繼續(xù)讀取了轉(zhuǎn)賬后的用戶(hù)B的賬號(hào)存款,導(dǎo)致最終統(tǒng)計(jì)的存款總額多了100元,發(fā)生錯(cuò)誤。
使用鎖機(jī)制可以解決上述的問(wèn)題。查詢(xún)總額事務(wù)會(huì)對(duì)讀取的行加鎖,等到操作結(jié)束后再釋放所有行上的鎖。因?yàn)橛脩?hù)A的存款被鎖,導(dǎo)致轉(zhuǎn)賬操作被阻塞,直到查詢(xún)總額事務(wù)提交并將所有鎖都釋放。
但是這時(shí)可能會(huì)引入新的問(wèn)題,當(dāng)轉(zhuǎn)賬操作是從用戶(hù)B向用戶(hù)A進(jìn)行轉(zhuǎn)賬時(shí)會(huì)導(dǎo)致死鎖。轉(zhuǎn)賬事務(wù)會(huì)先鎖住用戶(hù)B的數(shù)據(jù),等待用戶(hù)A數(shù)據(jù)上的鎖,但是查詢(xún)總額的事務(wù)卻先鎖住了用戶(hù)A數(shù)據(jù),等待用戶(hù)B的數(shù)據(jù)上的鎖。
使用MVVC機(jī)制也可以解決這個(gè)問(wèn)題。查詢(xún)總額事務(wù)先讀取了用戶(hù)A的賬戶(hù)存款,然后轉(zhuǎn)賬事務(wù)會(huì)修改用戶(hù)A和用戶(hù)B賬戶(hù)存款,查詢(xún)總額事務(wù)讀取用戶(hù)B存款時(shí)不會(huì)讀取轉(zhuǎn)賬事務(wù)修改后的數(shù)據(jù),而是讀取本事務(wù)開(kāi)始時(shí)的數(shù)據(jù)副本(在REPEATABLE READ隔離等級(jí)下)。
MVCC使得數(shù)據(jù)庫(kù)讀不會(huì)對(duì)數(shù)據(jù)加鎖,普通的SELECT請(qǐng)求不會(huì)加鎖,提高了數(shù)據(jù)庫(kù)的并發(fā)處理能力。借助MVCC,數(shù)據(jù)庫(kù)可以實(shí)現(xiàn)READ COMMITTED,REPEATABLE READ等隔離級(jí)別,用戶(hù)可以查看當(dāng)前數(shù)據(jù)的前一個(gè)或者前幾個(gè)歷史版本,保證了ACID中的I特性(隔離性)
InnoDB的MVVC實(shí)現(xiàn)
多版本并發(fā)控制僅僅是一種技術(shù)概念,并沒(méi)有統(tǒng)一的實(shí)現(xiàn)標(biāo)準(zhǔn), 其的核心理念就是數(shù)據(jù)快照,不同的事務(wù)訪問(wèn)不同版本的數(shù)據(jù)快照,從而實(shí)現(xiàn)不同的事務(wù)隔離級(jí)別。雖然字面上是說(shuō)具有多個(gè)版本的數(shù)據(jù)快照,但這并不意味著數(shù)據(jù)庫(kù)必須拷貝數(shù)據(jù),保存多份數(shù)據(jù)文件,這樣會(huì)浪費(fèi)大量的存儲(chǔ)空間。InnoDB通過(guò)事務(wù)的undo日志巧妙地實(shí)現(xiàn)了多版本的數(shù)據(jù)快照。
數(shù)據(jù)庫(kù)的事務(wù)有時(shí)需要進(jìn)行回滾操作,這時(shí)就需要對(duì)之前的操作進(jìn)行undo。因此,在對(duì)數(shù)據(jù)進(jìn)行修改時(shí),InnoDB會(huì)產(chǎn)生undo log。當(dāng)事務(wù)需要進(jìn)行回滾時(shí),InnoDB可以利用這些undo log將數(shù)據(jù)回滾到修改之前的樣子。
根據(jù)行為的不同 undo log 分為兩種 insert undo log和update undo log。
insert undo log 是在 insert 操作中產(chǎn)生的 undo log。因?yàn)?insert 操作的記錄只對(duì)事務(wù)本身可見(jiàn),對(duì)于其它事務(wù)此記錄是不可見(jiàn)的,所以 insert undo log 可以在事務(wù)提交后直接刪除而不需要進(jìn)行 purge 操作。
update undo log 是 update 或 delete 操作中產(chǎn)生的 undo log,因?yàn)闀?huì)對(duì)已經(jīng)存在的記錄產(chǎn)生影響,為了提供 MVCC機(jī)制,因此 update undo log 不能在事務(wù)提交時(shí)就進(jìn)行刪除,而是將事務(wù)提交時(shí)放到入 history list 上,等待 purge 線程進(jìn)行最后的刪除操作。
為了保證事務(wù)并發(fā)操作時(shí),在寫(xiě)各自的undo log時(shí)不產(chǎn)生沖突,InnoDB采用回滾段的方式來(lái)維護(hù)undo log的并發(fā)寫(xiě)入和持久化。回滾段實(shí)際上是一種 Undo 文件組織方式。
InnoDB行記錄有三個(gè)隱藏字段:分別對(duì)應(yīng)該行的rowid、事務(wù)號(hào)db_trx_id和回滾指針db_roll_ptr,其中db_trx_id表示最近修改的事務(wù)的id,db_roll_ptr指向回滾段中的undo log。如下圖所示。
當(dāng)事務(wù)2使用UPDATE語(yǔ)句修改該行數(shù)據(jù)時(shí),會(huì)首先使用排他鎖鎖定改行,將該行當(dāng)前的值復(fù)制到undo log中,然后再真正地修改當(dāng)前行的值,最后填寫(xiě)事務(wù)ID,使用回滾指針指向undo log中修改前的行。如下圖所示。
當(dāng)事務(wù)3進(jìn)行修改與事務(wù)2的處理過(guò)程類(lèi)似,如下圖所示。
REPEATABLE READ隔離級(jí)別下事務(wù)開(kāi)始后使用MVVC機(jī)制進(jìn)行讀取時(shí),會(huì)將當(dāng)時(shí)活動(dòng)的事務(wù)id記錄下來(lái),記錄到Read View中。READ COMMITTED隔離級(jí)別下則是每次讀取時(shí)都創(chuàng)建一個(gè)新的Read View。
Read View是InnoDB中用于判斷記錄可見(jiàn)性的數(shù)據(jù)結(jié)構(gòu),記錄了一些用于判斷可見(jiàn)性的屬性。
low_limit_id:某行記錄的db_trx_id < 該值,則該行對(duì)于當(dāng)前Read View是一定可見(jiàn)的
up_limit_id:某行記錄的db_trx_id >= 該值,則該行對(duì)于當(dāng)前read view是一定不可見(jiàn)的
low_limit_no:用于purge操作的判斷
rw_trx_ids:讀寫(xiě)事務(wù)數(shù)組
Read View創(chuàng)建后,事務(wù)再次進(jìn)行讀操作時(shí)比較記錄的db_trx_id和Read View中的low_limit_id,up_limit_id和讀寫(xiě)事務(wù)數(shù)組來(lái)判斷可見(jiàn)性。
如果該行中的db_trx_id等于當(dāng)前事務(wù)id,說(shuō)明是事務(wù)內(nèi)部發(fā)生的更改,直接返回該行數(shù)據(jù)。否則的話,如果db_trx_id小于up_limit_id,說(shuō)明是事務(wù)開(kāi)始前的修改,則該記錄對(duì)當(dāng)前Read View是可見(jiàn)的,直接返回該行數(shù)據(jù)。
如果db_trx_id大于或者等于low_limit_id,則該記錄對(duì)于該Read View一定是不可見(jiàn)的。如果db_trx_id位于[up_limit_id, low_limit_id)范圍內(nèi),需要在活躍讀寫(xiě)事務(wù)數(shù)組(rw_trx_ids)中查找db_trx_id是否存在,如果存在,記錄對(duì)于當(dāng)前Read View是不可見(jiàn)的。
如果記錄對(duì)于Read View不可見(jiàn),需要通過(guò)記錄的DB_ROLL_PTR指針遍歷undo log,構(gòu)造對(duì)當(dāng)前Read View可見(jiàn)版本數(shù)據(jù)。
簡(jiǎn)單來(lái)說(shuō),Read View記錄讀開(kāi)始時(shí)及其之后,所有的活動(dòng)事務(wù),這些事務(wù)所做的修改對(duì)于Read View是不可見(jiàn)的。除此之外,所有其他的小于創(chuàng)建Read View的事務(wù)號(hào)的所有記錄均可見(jiàn)。
后記
我們后續(xù)還會(huì)學(xué)習(xí)InnoDB的鎖的相關(guān)的知識(shí),請(qǐng)大家持續(xù)關(guān)注。
參考文章
總結(jié)
以上是生活随笔為你收集整理的mysql取消mvvc机制_MySQL探秘(六):InnoDB一致性非锁定读的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java 面试题 由浅入深_面试官由浅入
- 下一篇: 电信笔试C语言,2021中国电信考试试题