天天看點

mysql innodb mvcc 讀一緻性(Repeatable Read)通俗筆記

InnoDB 的 MVCC 和oracle 還是有差別,沒有oracle那麼純粹,很簡單可以展現在oracle 我可以直接flashback查詢,但是InnoDB不行。 oracle 是怎麼做MVCC 就沒有具體了解了,快2年沒有用oracle了,肯定是undo log ,redo log 等結構更強大。

InnoDB MVCC提供了兩個關鍵功能,一:寫不阻塞讀 。 二:讀一緻性。一下主要介紹一下InnoDB實作讀一緻性需要達到的效果大家容易了解,要實作Repeatable Read 事務隔離級别,就是InnoDB實作到什麼程度了,我感覺開發人員比較容易糊塗,我也是。

附:寫不阻塞讀,個人感覺隻要是MVCC的,肯定就是寫不阻塞讀了,MVCC給我們程式員提供的最好東西也就是寫不阻塞讀,不過Mysql InnoDB 在某些情況下寫會阻塞讀,下文會寫到。

InnoDB engine 有一個全局的 Transaction ID (事務ID,下文用trx_id 表示,即目前版本) 使用show engine innodb status 可以看到。

寫在前面:以下描述過于繁雜,我表述也成問題,如果把 事務ID 當版本ID 來讀就更好了解。

每開啟一個事務的時候 trx_id 就會自增保證每個事務都有自己的trx_id,事務内多步操作不會每個操作都增長(廢話) 。當 autocommit = 1 的時候 每個select 都會讓trx_id 漲一,這時 trx_id 的增長當會受查詢緩存影響,如查詢兩次沒有漲那肯定就是查詢緩存起作用了,連續兩個select 查詢結果一樣,後一個是讀得查詢緩存。

我得了解:每個條資料都記錄了一個事務id,就是該資料最後被一次修改的事務ID。

再稍微提下一undo log,redo log。網上一找一堆,我把自己認為合理的說一下,這個undo log和讀一緻性有關,有必要提一下:

來個找到的定義,寫得比較好就直接貼出來:

redo log:重做日志,就是每次mysql在執行寫入資料前先把要寫的資訊儲存在重寫日志中,但出現斷電,奔潰,重新開機等等導緻資料不能正常寫入期望資料時,伺服器可以通過redo_log中的資訊重新寫入資料。

undo log:撤銷日志,與redo log恰恰相反,當一些更改在執行一半時,發生意外,而無法完成,則可以根據撤消日志恢複到更改之前的壯态。

以一個update 操作為例子:

首先記錄undo-log,把本次修改的字段原始值記錄下來(包括舊版本的事務id,即修改前的事務id,讀一緻性裡面這個比較重要)

然後在本條記錄上進行修改(具體看參考文獻)(映像oracle 是複制一條新的記錄,标記為update操作的版本号,可能這就是oracle的MVCC那麼純粹,可以flashbak 查詢而mysql InnoDB 不行的原因,oracle查詢帶着版本号就行,我猜mysql 就得一層層的查redo-log,性能上面肯定就不行,這個功能也就做不了了)

修改後寫redo-log(包括有新版本事務id)

接着就commit;

undo redo log 說了就說 關于InnoDB如何實作MVCC 讀一緻性的:

每個select 都會産生一個 read view

在事務開啟的時候建立一個記錄之前已經是活躍的事務(還沒有送出的事務)trx_id 清單,這個就是 read view,在事務結束前是不會變的,代表着目前的版本!

設其中最早的事務id為trx_id_low,最遲的事務id為trx_id_up

本操作所在的事務id(版本号)w為 trx_id_cur

首先:這個trx_id_cur 肯定大于trx_id_up ,版本号是唯一的,遞增的。

查詢的時候,查到當行的記錄的trx_id 為 trx_id_row 。

首先這個trx_id_row 可能比trx_id_cur大,那就比trx_id_up 也大了,場景:

讀的時候其他線程又新起了一個事務,插入了一條資料還commit了。版本号就比目前操作大了,這種資料肯定不應該可見,不然讀到後面版本送出的資料那何來MVCC。

以上就是很多部落格直接說的

條件1 trx_id_row > trx_id_up(不是很好了解),但是算法就是這樣的(由于不可見到條件5去查undo log)

條件2 如果trx_id_row < trx_id_low 由于trx_id_low < trx_id_up < trx_id_cur 是以說明該行資料在本次事務開始已經送出了,是以可見,直接傳回到結果集。

條件3 如果 trx_id_low <= trx_id_row <= trx_id_up

就應該周遊對目前 read view 的 list

如果包含trx_id_row,說明目前事物開始的時候,這個事物還沒有送出,現在送出了肯定對與目前事物來說不可見,畢竟目前事物開始的時候它還沒有被送出(到條件5去查undo log)。

如果不包含就沒有問題了,因為trx_id_row < trx_id_up < trx_id_cur , 比目前版本小,又是送出了的,就是可見的了(這個可以了解吧),直接加入傳回結果集了。

條件5 對于所有不可見的 trx_id_row 就通過 該行主鍵(應該是oracle ROW_ID 那種)去查undo_log ,找到undo log 中最新一個版本,把它的trx_id 指派給目前的trx_id_row 再跑一次判斷,直到滿足條件就傳回。為什麼需要循環跑?就是條件1的極端情況,本事務啟動後,先後有兩個事務修改過目前行的資料,最新一條redo log 的事務id 都 > trx_id_cur > trx_id_up 肯定需要再循環判斷,找到具體需要的版本 ,這時一個類似鍊式的過程(當然具體資料結構我不知道,再次驗證innoDB 不能帶版本查詢的原因了)

我覺得有點需要提(由于這個在源碼剖析的方法外面,别人沒有提):trx_id_row = trx_id_cur 的時候是本事務内的操作,對本事務肯定可見了。

完。

關于一直說mysql 不能按版本查詢 ,一般oracle 的flashback 查詢都是程式員犯錯的一個補救措施,復原資料。不直接,說兩個oracle 可以但是mysql不行的查詢場景更直覺:

1、寫操作依賴本表資料,造2個例子(不恰當再換) :

a例子:update table_a a set a_col1 = (select a_col2 from table_a aa where a.col3 = aa.col3 + 1 )(oracle 就可以,這才是真MVCC)

b例子:insert into a select * from a;

2、a 表有寫操作未送出,使用a表被操作加鎖的行去更新b表會被鎖(又不算真MVCC,oracle又可以):例子:

事務1:

update a set col1 = ? where fk =1;

事務2

update b set cols = (select sum(col1) from a where a.fk = b.id)

事務1沒有送出,事務2會阻塞,這其實就是寫阻塞讀了。

當然這些缺點我們可以在應用層(java)中自己處理,無傷大雅。

具體參考了别人的源碼剖析:http://hi.baidu.com/gao_dennis/item/1f133311f50a94423a176ef5

我就是用一個更直接的話表述出來了,當然我得表述總是不清楚,輕拍!

繼續閱讀