- mysql中的RR隔離級別不會出現幻讀。
- MVCC控制過程當中,mysql會往每條數據附加三個列,分別是DB_TRX_ID(最後更新(insert/update/delete)數據的事務id與一個標誌位標識該記錄是否爲刪除記錄)、DB_ROLL_PTR(回滾指針)與DB_ROW_ID(自增,用於標識數據的更新時間).
- 每一個事務開始時系統會分配一個事務號,該事務號遞增。
- MVCC(RR級別)中的讀操做分爲快照讀(極可能是歷史數據)以及當前讀,其中當前讀包括select * ... for update;insert;delete等,它讀的是最新數據(由鎖來實現),RR隔離級別實現的效果是對兩種讀都成立,即快照讀中不會有幻讀以及不可重複讀,在當前讀也不會出現幻讀或不可重複讀。
- 快照讀實現RR效果
快照讀便是select * from table where condition;沒有for update等語句。
在InnoDB中經過一個ReadView的數據結構來實現快照。該數據結構中有如下數據,max_tx_id、min_tx_id、exclude_tx_ids.該數據結構在一個事務中的第一次SnapShot調用中建立,並在該事務中再也不改變。其中max_tx_id通常取快照讀時,系統的事務號,全部大於該事務號的事務數據,都是在當前快照後建立的,對於當前快照應該是不可見的。min_tx_id,指示了全部的可用數據的事務id的最大值,其通常取活動事務(未提交)的最小值,exclude_tx_ids指向全部的不可見事務id,通常是建立SnapShot時的全部活動事務。如下對update/delete/insert分別介紹其執行規則,說明爲何ReadView能知足RR效果。
//如下操做都會在得到鎖後當即執行,沒必要等到事務提交。
//insert:插入一條數據,將DB_TRX_ID設置爲當前事務id,DB_ROLL_PTR指向上一條數據。
//delete:將DB_TRX_ID設置爲當前事務id並標記爲刪除(新增一條),並設置DB_ROLL_PTR。
//update:先把本來數據的DB_TRX_ID設置爲當前事務id並標記爲刪除(新增一條),而後執行一個insert操做。
//select,經過ReadView能夠很快判斷出一條數據的DB_TRX_ID所對應的事務對於當前快照是否可見,而後利用DB_ROW_ID,往回查找全部可見數據中的最新數據。
eg:假設事務t2在事務t1快照以後啓動,因此
|val1|val2|DB_TRX_ID|DB_ROW_ID|
初始數據:|-|-|1|1|
t1執行SnapShot,標誌t2_id不可見。
t2更新數據,數據記錄變動爲如下
|-|-|10|3|
|-|-|10d|2|
|-|-|1|1|
t1再次讀取時,因爲知道DB_TRX_ID爲10的事務數據是不可見的,因此直接略過兩條,讀取了|-|-|1|1|即此時只有該數據是有效數據。固然若是讀到的最新有效數據是一條刪除數據(d),則這次讀取無結果。因爲insert使用了不可見事務的id,因此會被忽略,因此無幻讀。
- 當前讀的RR效果實現則由鎖進行保證,詳細可見該博客。
- InnoDB的RR以及RC級別,差異就在於RR中存在着GAP鎖,這在阻止某些insert語句的同時,也阻止了update與delete語句(update本質上也是insert).
- InnoDB的鎖操做決定了,其在RC/RR隔離等級下都不可能出現第一類更新丟失。可是都存在第二類更新丟失(除非使用當前讀(for update)讀取數據),但更好的方法應該是從程序中使用樂觀版本控制(添加version列)。