Reference: https://www.imooc.com/article/17290mysql
能夠很負責人的跟你們說,MySQL 中的此隔離級別不僅僅是經過加鎖實現的,實際上還有repeatable read 隔離級別,其實這兩個隔離級別效果的實現還須要一個輔助,這個輔助就是MVCC-多版本併發控制,但其實它又不是嚴格意義上的多版本併發控制,是否是很懵,不要緊,咱們一一剖析。算法
目錄sql
1.單純加鎖是怎麼實現 read committed 的?
2.真實的演示狀況是什麼樣子的?
3.MVCC 實現原理?
4.對於InnoDB MVCC 實現原理的反思數據庫
1.單純加鎖是怎麼實現 read committed 的?併發
今後隔離級別效果入手:事務只能讀其餘事務已提交的的記錄。
數據庫事務隔離級別的實現,InnoDB 支持行級鎖,寫時加的是行級排他鎖(X lock),那麼當其餘事務訪問另外一個事務正在update (除select操做外其餘操做本質上都是寫操做)的同一條記錄時,事務的讀操做會被阻塞。因此只能等到記錄(實際上是索引上的鎖)上的排他鎖釋放後才能進行訪問,也就是事務提交的時候。這樣確實能實現read commited隔離級別效果。
數據庫這樣作確實能夠實現 事務只能讀其餘事務已提交的的記錄 的效果,可是這是很低效的一種作法,爲何呢?由於對於大部分應用來講,讀操做是多於寫操做的,當寫操做加鎖時,那麼讀操做所有被阻塞,這樣會致使應用的相應能力受數據庫的牽制。分佈式
2.真實的演示狀況是什麼樣子的?函數
看以下操做:
1.開啓兩個客戶端實例,設置事務隔離級別爲read committed,並各自開啓事務。性能
set transaction isolation level read committed; set autocommit = 0; begin;
2.客戶端1作更新操做:測試
update test set name = '測試' where id =32;
結果以下圖所示:
3d
3.客戶端2作查詢操做:
select name from test where id = 32;
結果以下所示:
這時估計你有疑問了,正在 被客戶端1 upate 的記錄,客戶端2還能無阻塞的讀到,並且讀到的是未更改以前的數據。
那就是 InnoDB 的輔助打得好,由於內部使用了 MVCC 機制,實現了一致性非阻塞讀,大大提升了併發讀寫效率,寫不影響讀,且讀到的事記錄的鏡像版本。
下面開始介紹 MVCC 原理。
3.MVCC 實現原理
網上對 MVCC 實現原理 的講述五花八門,參差不齊。
包括《高性能MySQL》對 MVCC 的講解只是停留在表象,並無結合源碼去分析。固然絕大多數人仍是相信這本書的,歷來沒有進行深剖,思考。
以下是 《高性能MySQL》對 MVCC實現原理 的描述:
"InnoDB的 MVCC ,是經過在每行記錄的後面保存兩個隱藏的列來實現的。這兩個列, 一個保存了行的建立時間,一個保存了行的過時時間, 固然存儲的並非實際的時間值,而是系統版本號。"
就是這本書,矇蔽了真理,貽害不淺。
咱們仍是看源碼吧:
1.記錄的隱藏列
其實有三列
在Mysql中MVCC是在Innodb存儲引擎中獲得支持的,Innodb爲每行記錄都實現了三個隱藏字段: 6字節的事務ID(DB_TRX_ID) 7字節的回滾指針(DB_ROLL_PTR) 隱藏的ID 6字節的事物ID用來標識該行所述的事務,7字節的回滾指針須要瞭解下Innodb的事務模型。
2.MVCC 實現的依賴項
MVCC 在mysql 中的實現依賴的是 undo log 與 read view。
1.undo log: undo log中記錄的是數據表記錄行的多個版本,也就是事務執行過程當中的回滾段,其實就是MVCC 中的一行原始數據的多個版本鏡像數據。 2.read view: 主要用來判斷當前版本數據的可見性。
3.undo log
undo log是爲回滾而用,具體內容就是copy事務前的數據庫內容(行)到undo buffer,在適合的時間把undo buffer中的內容刷新到磁盤。undo buffer與redo buffer同樣,也是環形緩衝,但當緩衝滿的時候,undo buffer中的內容會也會被刷新到磁盤;與redo log不一樣的是,磁盤上不存在單獨的undo log文件,全部的undo log均存放在主ibd數據文件中(表空間),即便客戶端設置了每表一個數據文件也是如此。
咱們經過行的更新過程來看下undo log 是如何造成的?
3.1 行的更新過程
下面演示下事務對某行記錄的更新過程:
4.read view 判斷當前版本數據項是否可見
在innodb中,建立一個新事務的時候,innodb會將當前系統中的活躍事務列表(trx_sys->trx_list)建立一個副本(read view),副本中保存的是系統當前不該該被本事務看到的其餘事務id列表。當用戶在這個事務中要讀取該行記錄的時候,innodb會將該行當前的版本號與該read view進行比較。
具體的算法以下:
- 設該行的當前事務id爲trx_id_0,read view中最先的事務id爲trx_id_1, 最遲的事務id爲trx_id_2。
- 若是trx_id_0< trx_id_1的話,那麼代表該行記錄所在的事務已經在本次新事務建立以前就提交了,因此該行記錄的當前值是可見的。跳到步驟6.
- 若是trx_id_0>trx_id_2的話,那麼代表該行記錄所在的事務在本次新事務建立以後纔開啓,因此該行記錄的當前值不可見.跳到步驟5。
- 若是trx_id_1<=trx_id_0<=trx_id_2, 那麼代表該行記錄所在事務在本次新事務建立的時候處於活動狀態,從trx_id_1到trx_id_2進行遍歷,若是trx_id_0等於他們之中的某個事務id的話,那麼不可見。跳到步驟5.
- 從該行記錄的DB_ROLL_PTR指針所指向的回滾段中取出最新的undo-log的版本號,將它賦值該trx_id_0,而後跳到步驟2.
- 將該可見行的值返回。
須要注意的是,新建事務(當前事務)與正在內存中commit 的事務不在活躍事務鏈表中。
對應代碼以下:
函數:read_view_sees_trx_id。
read_view中保存了當前全局的事務的範圍:
【low_limit_id, up_limit_id】
1. 當行記錄的事務ID小於當前系統的最小活動id,就是可見的。
if (trx_id < view->up_limit_id) {
return(TRUE);
}
2. 當行記錄的事務ID大於當前系統的最大活動id,就是不可見的。
if (trx_id >= view->low_limit_id) {
return(FALSE);
}
3. 當行記錄的事務ID在活動範圍之中時,判斷是否在活動鏈表中,若是在就不可見,若是不在就是可見的。
for (i = 0; i < n_ids; i++) {
trx_id_t view_trx_id
= read_view_get_nth_trx_id(view, n_ids - i - 1);
if (trx_id <= view_trx_id) {
return(trx_id != view_trx_id);
}
}
5 事務隔離級別的影響
可是:對於兩張不一樣的事務隔離級別
tx_isolation='READ-COMMITTED': 語句級別的一致性:只要當前語句執行前已經提交的數據都是可見的。
tx_isolation='REPEATABLE-READ'; 語句級別的一致性:只要是當前事務執行前已經提交的數據都是可見的。
針對這兩張事務的隔離級別,使用相同的可見性判斷邏輯是如何作到不一樣的可見性的呢?
6.不一樣隔離級別下read view的生成原則
這裏就要看看read_view的生成機制:
1. read-commited:
函數:ha_innobase::external_lock
if (trx->isolation_level <= TRX_ISO_READ_COMMITTED
&& trx->global_read_view) {
/ At low transaction isolation levels we let
each consistent read set its own snapshot /
read_view_close_for_mysql(trx);
即:在每次語句執行的過程當中,都關閉read_view, 從新在row_search_for_mysql函數中建立當前的一份read_view。
這樣就能夠根據當前的全局事務鏈表建立read_view的事務區間,實現read committed隔離級別。
2. repeatable read:
在repeatable read的隔離級別下,建立事務trx結構的時候,就生成了當前的global read view。
使用trx_assign_read_view函數建立,一直維持到事務結束,這樣就實現了repeatable read隔離級別。
正是由於6中的read view 生成原則,致使在不一樣隔離級別()下,read committed 老是讀最新一份快照數據,而repeatable read 讀事務開始時的行數據版本。
4.InnoDB MVCC 實現原理的深入反思
上述更新前創建undo log,根據各類策略讀取時非阻塞就是MVCC,undo log中的行就是MVCC中的多版本,這個可能與咱們所理解的MVCC有較大的出入。
通常咱們認爲MVCC有下面幾個特色:
每行數據都存在一個版本,每次數據更新時都更新該版本
修改時Copy出當前版本隨意修改,個事務之間無干擾
保存時比較版本號,若是成功(commit),則覆蓋原記錄;失敗則放棄copy(rollback)
就是每行都有版本號,保存時根據版本號決定是否成功,聽起來含有樂觀鎖的味道。。。,而
Innodb的實現方式是:
事務以排他鎖的形式修改原始數據
把修改前的數據存放於undo log,經過回滾指針與主數據關聯
修改爲功(commit)啥都不作,失敗則恢復undo log中的數據(rollback)
兩者最本質的區別是,當修改數據時是否要排他鎖定,若是鎖定了還算不算是MVCC?
Innodb的實現真算不上MVCC,由於並無實現核心的多版本共存,undo
log中的內容只是串行化的結果,記錄了多個事務的過程,不屬於多版本共存。但理想的MVCC是難以實現的,當事務僅修改一行記錄使用理想的MVCC模式
是沒有問題的,能夠經過比較版本號進行回滾;但當事務影響到多行數據時,理想的MVCC據無能爲力了。好比,若是Transaciton1執行理想的MVCC,修改Row1成功,而修改Row2失敗,此時須要回滾Row1,但由於Row1沒有被
鎖定,其數據可能又被Transaction2所修改,若是此時回滾Row1的內容,則會破壞Transaction2的修改結果,致使
Transaction2違反ACID。理想MVCC難以實現的根本緣由在於企圖經過樂觀鎖代替二段提交。修改兩行數據,但爲了保證其一致性,與修改兩個分佈式系統中的數據並沒有區別,
而二提交是目前這種場景保證一致性的惟一手段。二段提交的本質是鎖定,樂觀鎖的本質是消除鎖定,兩者矛盾,故理想的MVCC難以真正在實際中被應
用,Innodb只是借了MVCC這個名字,提供了讀的非阻塞而已。
做者:mark_fork連接:https://www.imooc.com/article/17290來源:慕課網