MySQL實戰(zhàn)45講閱讀筆記-MVCC

系列
MySQL實戰(zhàn)45講閱讀筆記-MySQL入門
MySQL實戰(zhàn)45講閱讀筆記-日志
MySQL實戰(zhàn)45講閱讀筆記-鎖
MySQL實戰(zhàn)45講閱讀筆記-索引
MySQL實戰(zhàn)45講閱讀筆記-MVCC

MySQL的CrashSafe指在服務器宕機重啟后,能夠保證所有已經(jīng)提交的事務的數(shù)據(jù)仍然存在,所有沒有提交的事務的數(shù)據(jù)自動回滾;Innodb通過redolog和binlog實現(xiàn)crash safe功能,在執(zhí)行一條update語句的流程大致如下;


為了保證server層日志(binlog)和引擎層日志(redolog)的一致性,MySQL使用了兩階段提交,即是把寫日志拆分為preparecommit兩個階段;
因為是XA(分布式事務)事務所以會為每一個事務分配一個唯一的Xid,就像上面binlog日志里面記錄的一樣,一個完整的事務后面會跟著一個Xid;

  • 為什么兩階段提交能保證crash safe
    因為保證了redolog里面存在的日志一定會在binlog里面存在;
    1. 假如寫binlog之前mysql發(fā)生crash,即在上圖的redolog prepare階段崩潰,由于此時的binlog還沒有寫,重啟恢復時發(fā)現(xiàn)binlog缺少該事務,則回滾redolog的這條事務,因為沒有寫binlog所以也不會同步給備庫;
    2. 如果在寫完binlog時mysql發(fā)生crash,但是還沒到redolog commit階段崩潰,由于redolog已經(jīng)存在prepare且對應事務的binlog是完整的,所以恢復后會自動提交該事務;
崩潰恢復的規(guī)則
  1. redolog的事務是完整的,也就是有了commit標識,則直接提交該事務;
  2. redolog只有完整的prepare,則判斷對于binlog事務是否完整,如果完整則提交事務,如果不完整則回滾事務;
  • 如何判斷binlog是否完整
    statement格式的binlog最后面存在COMMIT標識,row格式的binlog最后面有Xid event;
  • redolog和binlog如何關聯(lián)
    有一個共同的Xid,因為是唯一的所以可以根據(jù)這個字段找到對應的事務;
  • 處于prepare階段的redolog加上完整的binlog在重啟后為什么就能恢復
    當寫完binlog后發(fā)生crash,這時備庫已經(jīng)收到了主庫發(fā)來的binlog日志并執(zhí)行該事務,所以為了保持主備一致,主庫在恢復的過程需要重新提交這條事務;

MVCC

多版本并發(fā)控制(Multiversion Currency Control)是一種提高并發(fā)的技術,遠比使用行鎖效率要高的多, MVCC的原理大概是同一行記錄可能會有多個版本的視圖(Read-View),從而擺脫鎖實現(xiàn)并發(fā)讀(基于快照),實現(xiàn)事務之間的讀寫分離;

事務隔離級別問題
  • 臟讀
    事務A讀取到了事務B未提交的數(shù)據(jù);
事務A 事務B
開啟事務A
開啟事務B
查詢某行得到數(shù)據(jù)0
更新該行值為10
查詢某行得到數(shù)據(jù)10
回滾
  • 不可重復讀
    在事務A執(zhí)行的過程中因為其他事務的update導致事務A查詢同一個數(shù)據(jù)出現(xiàn)不一致的結果;
事務A 事務B
開啟事務A
開啟事務B
查詢某行得到數(shù)據(jù)0
更新該行值為10
查詢某行得到數(shù)據(jù)0
提交
查詢某行得到數(shù)據(jù)10
  • 幻讀
    指事務A在執(zhí)行的過程中因為讀取到了其他事務insert導致事務原本的結果集出現(xiàn)偏差的情況;比如下面例子在RR隔離級別下發(fā)生的幻讀情況,事務A在事務執(zhí)行過程中查找id=2得到兩種結果,幻讀‘讀’的東西專指‘新增行’;
事務A 事務B
開啟事務A
開啟事務B
查詢id=2得到0行
插入id=2
commit
插入id=2(Duplicate entry '2' for key 'PRIMARY')
select * from t where id = 2 for update; (1 rows)
事務的隔離級別
  • 讀未提交(Read uncommitted)
    即一個事務做出的改變還未提交就能被其他事務所看到
  • 讀提交(Read committed)
    即一個事務做出的改變需要提交后才能被其他事務看到
  • 可重復讀(Repeatable read)
    即一個事務執(zhí)行的過程中看到的數(shù)據(jù)總是和這個事務啟動時看到數(shù)據(jù)是一致的;
  • 串行化(Serializable)
    對同一行數(shù)據(jù),寫會加寫鎖,讀會加讀鎖,當讀寫鎖出現(xiàn)沖突時后訪問的事務會等待占用鎖事務執(zhí)行完成才會繼續(xù)完成;

MVCC只會在讀提交可重復讀這兩個隔離級別下才會存在,讀未提交是直接返回該行最新的數(shù)據(jù),而串行化是直接采用加鎖的方式避免并行訪問;

事務隔離的實現(xiàn)

可重復讀隔離級別下事務啟動時會創(chuàng)建一個視圖,在讀提交下視圖會在每個SQL執(zhí)行的時候才創(chuàng)建,視圖可以把它當作當前數(shù)據(jù)的一個快照或副本;在MVCC中,讀操作可以分為兩種:當前讀快照讀,普通的select語句,只要不涉及加鎖操作就屬于快照讀,快照讀讀的是當前事務的可見版本(基于某個版本的副本),而當前讀會給讀取的行加上鎖,比如select...for update,讀取的一定是該行最新的版本;

Innodb中每個事務有一個唯一的事務IDtransaction id,每行數(shù)據(jù)也是存在多個版本,每次更新數(shù)據(jù)的時候,都會生成一個新的數(shù)據(jù)版本,并把transaction id賦值給這個版本的row trx_id,且舊版本數(shù)據(jù)要保留;也就是說數(shù)據(jù)表里面每一行記錄都有可能存在多個版本(row),每個版本都有自己的trx_id;

圖1

如圖這行被多個事務修改了3次,所以會留下4個版本的數(shù)據(jù),U1、U2和U3組成了Undolog(回滾日志),v1、v2、v3版本并不是真實存在的,而是在需要的時候通過undo log計算出來的;

InnoDB每個行上有額外的幾個隱含字段,rowid表示對應的行,db_trx_id表示事務的id,db_roll_pt則是回滾指針,指向undolog里面被修改前的行,delete bit表示是否刪除;

Innodb為每個事務都構造了一個數(shù)組用來保存在這個事務啟動的瞬間,正在活躍事務id,活躍事務是指啟動了但是還未提交的事務;
數(shù)組里面事務id的最小值記為低水位,當前系統(tǒng)里面已經(jīng)創(chuàng)建過的事務id的最大值+1記為高水位;這是數(shù)組和高水位就組成了該事務的一致性視圖;

圖2

事務之間數(shù)據(jù)版本的可見性就是基于數(shù)據(jù)行的trx_id和這個一致性視圖對比的結果;
一個數(shù)據(jù)版本的trx_id存在以下幾個可能

  • 如果trx_id在綠色的部分則表示這個版本是屬于已提交的事務或者自己生成的,這個數(shù)據(jù)是可見的;
  • 如果在紅色的區(qū)域則是不可見的;
  • 如果在黃色的區(qū)域,則包括兩種情況
    • 如果trx_id在數(shù)組中,則表示這個版本是由還沒有提交的事務生成的,不可見;
    • 如果trx_id不在數(shù)組中,則表示這個版本是已經(jīng)提交的事務生成的,可見;

有了這些規(guī)則之后,每一個事物都可以知道哪一個數(shù)據(jù)版本對于其他事物來說是可見的還是不可見的;

執(zhí)行更新或刪除語句時,會把該行修改前的狀態(tài)記錄到Undo log里面,使回滾指針指向它,同時把更新語句和undo log的更新都記錄到redolog中(崩潰恢復時先回復redolog再通過redolog構造undolog回滾未提交的事務);查找之前的版本只需要通過回滾指針找到之前的版本即可,刪除操作也是可以通過Undolog回滾的,因為刪除操作只是在commit階段才執(zhí)行刪除操作;

來看一個實例,隔離級別是RR

mysql> select * from t; 比如在表t有以下兩行數(shù)據(jù)
+----+------+
| id | k    |
+----+------+
|  1 |    1 |
|  2 |    2 |
+----+------+
事務A(trx_id=100) 事務B(trx_id=101) 事務C(trx_id=102)
start transaction with consistent snapshot
start transaction with consistent snapshot
update t set k=k+1 where id=1
update t set k=k+1 where id=1
select k from t where id=1
select k from t where id=1; commit
commit

start transaction并不是馬上開啟事務而是在對innodb表進行操作的第一條語句才啟動的,而一致性視圖是在執(zhí)行第一次select語句才建立;
start transaction with consistent snapshot在可重復讀隔離級別下則是立馬啟動一個事務并創(chuàng)建視圖;

假如事務A開啟的時候系統(tǒng)里面只存在一個活躍的事務id99,且在這三個事務開啟前(1,1)這一行數(shù)據(jù)的row trx_id是90;

所以各個事務的視圖數(shù)據(jù)是
A:[99, 100]
B:[99, 100, 101]
C:[99, 100, 101, 102]

事務C是第一個有效事務,更新k到2時,這一行的最新版本的trx_id是102;
事務B再次更新k值,這時事務C已經(jīng)提交了,且在更新數(shù)據(jù)的時候都是先讀后寫,這個讀只能是當前讀,不然事務C更新的數(shù)據(jù)就丟失了,所以更新后k=3,這時最新版本的trx_id是101;
事務A查詢k的時候事務B還未提交,所以k=3(trx_id:101)這個版本對于事務A來說是不可見的,于是根據(jù)undolog把這一行的數(shù)據(jù)取之前的版本k=2(trx_id:102),但是事務A的視圖是[99,100],所以這個版本的數(shù)據(jù)對于A來說也是不可見的,再一次回退之前的版本k=1(trx_id:90),這個版本對于A來說是小于事務A的trx_id,即可見的,所以事務A讀到的k是1;

在事務A期間雖然k被更新過,但是對于A來說看到這行的數(shù)據(jù)是這個事務啟動時的值,這個被稱為一致性讀;但是在更新的時候,讀取的值是最新的,不然的話會造成數(shù)據(jù)丟失,這個讀就是當前讀;

總結出來就是對于一個事務來說除了自己更新的總是可見之外還有三種情況

  • 事務未提交,不可見
  • 事務已提交,但在本事務的視圖創(chuàng)建后提交的,不可見
  • 事務已提交,而且是在本事務的視圖前提交的,可見

事務的可重復讀就是依賴于一致性讀實現(xiàn)的,在更新數(shù)據(jù)時使用當前讀;
可重復讀是在事務開始的時候創(chuàng)建一致性視圖的,之后的更新都是使用這個視圖,而讀提交是每一個語句執(zhí)行前都會計算出一個視圖;

假如上面的例子發(fā)生在RC隔離級別下,分析上面那個例子

事務A(trx_id=100) 事務B(trx_id=101) 事務C(trx_id=102)
start transaction with consistent snapshot
start transaction with consistent snapshot
update t set k=k+1 where id=1
update t set k=k+1 where id=1
select k from t where id=1
select k from t where id=1; commit
commit

start transaction with consistent snapshot在RR下是創(chuàng)建一個持續(xù)整個事務的一致性快照,但是在RX下面沒有效果,所以只是個普通的開啟事務,即begin transaction;

事務C執(zhí)行set k后假設k=1,事務B執(zhí)行set k后k=2未提交,所以等事務A執(zhí)行select的時候創(chuàng)建一個視圖,此時視圖數(shù)組是[100, 101],事務C是屬于已經(jīng)提交的事務,所以事務A可以查詢到事務C已經(jīng)提交的版本,但是事務B對于事務A來說同屬活躍事務,且在視圖數(shù)組中,按照規(guī)則如果trx_id在數(shù)組中,則表示這個版本是由還沒有提交的事務生成的,不可見,所以事務A查詢到的k=1;

參考

[圖解MySQL]MySQL組提交(group commit)
數(shù)據(jù)庫內(nèi)核月報 - 2017 / 12
mysql 幻讀的詳解、實例及解決辦法

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容