溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

MySQL中InnoDB的一致性非鎖定讀是怎么樣的

發(fā)布時(shí)間:2021-10-25 09:12:51 來源:億速云 閱讀:151 作者:柒染 欄目:大數(shù)據(jù)

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)碛嘘P(guān)MySQL中InnoDB的一致性非鎖定讀是怎么樣的,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

?一致性非鎖定讀(consistent nonlocking read)是指InnoDB存儲(chǔ)引擎通過多版本控制(MVVC)讀取當(dāng)前數(shù)據(jù)庫(kù)中行數(shù)據(jù)的方式。如果讀取的行正在執(zhí)行DELETE或UPDATE操作,這時(shí)讀取操作不會(huì)因此去等待行上鎖的釋放。相反地,InnoDB會(huì)去讀取行的一個(gè)快照。

MySQL中InnoDB的一致性非鎖定讀是怎么樣的  
一致性非鎖定讀示意圖

?上圖直觀地展現(xiàn)了InnoDB一致性非鎖定讀的機(jī)制。之所以稱其為非鎖定讀,是因?yàn)椴恍枰却猩吓潘i的釋放??煺諗?shù)據(jù)是指該行的之前版本的數(shù)據(jù),每行記錄可能有多個(gè)版本,一般稱這種技術(shù)為行多版本技術(shù)。

    由此帶來的并發(fā)控制,稱之為多版本并發(fā)控制(Multi Version Concurrency Control, MVVC)。InnoDB是通過undo log來實(shí)現(xiàn)MVVC。undo log本身用來在事務(wù)中回滾數(shù)據(jù),因此快照數(shù)據(jù)本身是沒有額外開銷。此外,讀取快照數(shù)據(jù)是不需要上鎖的,因?yàn)闆]有事務(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ù)開始時(shí)的行數(shù)據(jù)版本。

?我們下面舉個(gè)例子來詳細(xì)說明一下上述的情況。

# session A
mysql> BEGIN;
mysql> SELECT * FROM test WHERE id = 1;
 

?我們首先在會(huì)話A中顯示地開啟一個(gè)事務(wù),然后讀取test表中的id為1的數(shù)據(jù),但是事務(wù)并沒有結(jié)束。于此同時(shí),用戶在開啟另一個(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ù)同樣也沒有提交,這樣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í)別沒有任何區(qū)別。

MySQL中InnoDB的一致性非鎖定讀是怎么樣的  
會(huì)話A和會(huì)話B示意圖

?如上圖所示,當(dāng)會(huì)話B提交事務(wù)后,會(huì)話A再次運(yùn)行SELECT * FROM test WHERE id = 1的SQL語句時(shí),兩個(gè)事務(wù)隔離級(jí)別下得到的結(jié)果就不一樣了。
?對(duì)于READ COMMITTED的事務(wù)隔離級(jí)別,它總是讀取行的最新版本,如果行被鎖定了,則讀取該行版本的最新一個(gè)快照。因?yàn)闀?huì)話B的事務(wù)已經(jīng)提交,所以在該隔離級(jí)別下上述SQL語句的結(jié)果集是空的。
?對(duì)于REPEATABLE READ的事務(wù)隔離級(jí)別,總是讀取事務(wù)開始時(shí)的行數(shù)據(jù),因此,在該隔離級(jí)別下,上述SQL語句仍然會(huì)獲得相同的數(shù)據(jù)。

 
MVVC

?我們首先來看一下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ā)訪問控制的并發(fā)控制技術(shù)。
數(shù)據(jù)庫(kù)的并發(fā)控制機(jī)制有很多,最為常見的就是鎖機(jī)制。鎖機(jī)制一般會(huì)給競(jìng)爭(zhēng)資源加鎖,阻塞讀或者寫操作來解決事務(wù)之間的競(jìng)爭(zhēng)條件,最終保證事務(wù)的可串行化。

    而MVVC則引入了另外一種并發(fā)控制,它讓讀寫操作互不阻塞,每一個(gè)寫操作都會(huì)創(chuàng)建一個(gè)新版本的數(shù)據(jù),讀操作會(huì)從有限多個(gè)版本的數(shù)據(jù)中挑選一個(gè)最合適的結(jié)果直接返回,由此解決了事務(wù)的競(jìng)爭(zhēng)條件。

?考慮一個(gè)現(xiàn)實(shí)場(chǎng)景。管理者要查詢所有用戶的存款總額,假設(shè)除了用戶A和用戶B之外,其他用戶的存款總額都為0,A、B用戶各有存款1000,所以所有用戶的存款總額為2000。但是在查詢過程中,用戶A會(huì)向用戶B進(jìn)行轉(zhuǎn)賬操作。轉(zhuǎn)賬操作和查詢總額操作的時(shí)序圖如下圖所示。

MySQL中InnoDB的一致性非鎖定讀是怎么樣的  
轉(zhuǎn)賬和查詢的時(shí)序圖

?如果沒有任何的并發(fā)控制機(jī)制,查詢總額事務(wù)先讀取了用戶A的賬戶存款,然后轉(zhuǎn)賬事務(wù)改變了用戶A和用戶B的賬戶存款,最后查詢總額事務(wù)繼續(xù)讀取了轉(zhuǎn)賬后的用戶B的賬號(hào)存款,導(dǎo)致最終統(tǒng)計(jì)的存款總額多了100元,發(fā)生錯(cuò)誤。

?使用鎖機(jī)制可以解決上述的問題。查詢總額事務(wù)會(huì)對(duì)讀取的行加鎖,等到操作結(jié)束后再釋放所有行上的鎖。因?yàn)橛脩鬉的存款被鎖,導(dǎo)致轉(zhuǎn)賬操作被阻塞,直到查詢總額事務(wù)提交并將所有鎖都釋放。

MySQL中InnoDB的一致性非鎖定讀是怎么樣的  
使用鎖機(jī)制

?但是這時(shí)可能會(huì)引入新的問題,當(dāng)轉(zhuǎn)賬操作是從用戶B向用戶A進(jìn)行轉(zhuǎn)賬時(shí)會(huì)導(dǎo)致死鎖。轉(zhuǎn)賬事務(wù)會(huì)先鎖住用戶B的數(shù)據(jù),等待用戶A數(shù)據(jù)上的鎖,但是查詢總額的事務(wù)卻先鎖住了用戶A數(shù)據(jù),等待用戶B的數(shù)據(jù)上的鎖。
    使用MVVC機(jī)制也可以解決這個(gè)問題。查詢總額事務(wù)先讀取了用戶A的賬戶存款,然后轉(zhuǎn)賬事務(wù)會(huì)修改用戶A和用戶B賬戶存款,查詢總額事務(wù)讀取用戶B存款時(shí)不會(huì)讀取轉(zhuǎn)賬事務(wù)修改后的數(shù)據(jù),而是讀取本事務(wù)開始時(shí)的數(shù)據(jù)副本(在REPEATABLE READ隔離等級(jí)下)。  
MySQL中InnoDB的一致性非鎖定讀是怎么樣的  
使用MVVC機(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í)別,用戶可以查看當(dāng)前數(shù)據(jù)的前一個(gè)或者前幾個(gè)歷史版本,保證了ACID中的I特性(隔離性)

 
InnoDB的MVVC實(shí)現(xiàn)

?多版本并發(fā)控制僅僅是一種技術(shù)概念,并沒有統(tǒng)一的實(shí)現(xiàn)標(biāo)準(zhǔn), 其的核心理念就是數(shù)據(jù)快照,不同的事務(wù)訪問不同版本的數(shù)據(jù)快照,從而實(shí)現(xiàn)不同的事務(wù)隔離級(jí)別。雖然字面上是說具有多個(gè)版本的數(shù)據(jù)快照,但這并不意味著數(shù)據(jù)庫(kù)必須拷貝數(shù)據(jù),保存多份數(shù)據(jù)文件,這樣會(huì)浪費(fèi)大量的存儲(chǔ)空間。InnoDB通過事務(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ù)本身可見,對(duì)于其它事務(wù)此記錄是不可見的,所以 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í),在寫各自的undo log時(shí)不產(chǎn)生沖突,InnoDB采用回滾段的方式來維護(hù)undo log的并發(fā)寫入和持久化?;貪L段實(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。如下圖所示。

MySQL中InnoDB的一致性非鎖定讀是怎么樣的  
初始狀態(tài)

?當(dāng)事務(wù)2使用UPDATE語句修改該行數(shù)據(jù)時(shí),會(huì)首先使用排他鎖鎖定改行,將該行當(dāng)前的值復(fù)制到undo log中,然后再真正地修改當(dāng)前行的值,最后填寫事務(wù)ID,使用回滾指針指向undo log中修改前的行。如下圖所示。

MySQL中InnoDB的一致性非鎖定讀是怎么樣的  
第一次修改

?當(dāng)事務(wù)3進(jìn)行修改與事務(wù)2的處理過程類似,如下圖所示。

MySQL中InnoDB的一致性非鎖定讀是怎么樣的  
第二次修改

?REPEATABLE READ隔離級(jí)別下事務(wù)開始后使用MVVC機(jī)制進(jìn)行讀取時(shí),會(huì)將當(dāng)時(shí)活動(dòng)的事務(wù)id記錄下來,記錄到Read View中。READ COMMITTED隔離級(jí)別下則是每次讀取時(shí)都創(chuàng)建一個(gè)新的Read View。
?Read View是InnoDB中用于判斷記錄可見性的數(shù)據(jù)結(jié)構(gòu),記錄了一些用于判斷可見性的屬性。

  • low_limit_id:某行記錄的db_trx_id < 該值,則該行對(duì)于當(dāng)前Read View是一定可見的

  • up_limit_id:某行記錄的db_trx_id >= 該值,則該行對(duì)于當(dāng)前read view是一定不可見的

  • low_limit_no:用于purge操作的判斷

  • rw_trx_ids:讀寫事務(wù)數(shù)組

?Read View創(chuàng)建后,事務(wù)再次進(jìn)行讀操作時(shí)比較記錄的db_trx_id和Read View中的low_limit_id,up_limit_id和讀寫事務(wù)數(shù)組來判斷可見性。

?如果該行中的db_trx_id等于當(dāng)前事務(wù)id,說明是事務(wù)內(nèi)部發(fā)生的更改,直接返回該行數(shù)據(jù)。否則的話,如果db_trx_id小于up_limit_id,說明是事務(wù)開始前的修改,則該記錄對(duì)當(dāng)前Read View是可見的,直接返回該行數(shù)據(jù)。

?如果db_trx_id大于或者等于low_limit_id,則該記錄對(duì)于該Read View一定是不可見的。如果db_trx_id位于[up_limit_id, low_limit_id)范圍內(nèi),需要在活躍讀寫事務(wù)數(shù)組(rw_trx_ids)中查找db_trx_id是否存在,如果存在,記錄對(duì)于當(dāng)前Read View是不可見的。
?如果記錄對(duì)于Read View不可見,需要通過記錄的DB_ROLL_PTR指針遍歷undo log,構(gòu)造對(duì)當(dāng)前Read View可見版本數(shù)據(jù)。
?簡(jiǎn)單來說,Read View記錄讀開始時(shí)及其之后,所有的活動(dòng)事務(wù),這些事務(wù)所做的修改對(duì)于Read View是不可見的。除此之外,所有其他的小于創(chuàng)建Read View的事務(wù)號(hào)的所有記錄均可見。

上述就是小編為大家分享的MySQL中InnoDB的一致性非鎖定讀是怎么樣的了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(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)容。

AI