在講解InnoDB的MVCC機制之前,我們應該了解MySQL所支持的事務,以及各個事務級別的區別和每一個事務級別所存在的問題。
1. 事務
事務必須保證ACID,而ACID表示原子性、一致性、隔離性和持久性
1.1 事務的隔離級別
事務可以通過start transaction
語句開始一個事務,然后要么使用commit
提交事務將所修改的數據持久保存,要么使用rollback
撤銷所有修改
1.1.2 READ UNCOMMITTED (未提交讀 RU)
在READ UNCOMMITTED級別,事務中的修改,即使沒有提交,對其他事務也都是可見的。事務可以讀取未提交的數據,這也被稱為臟讀。
1.1.3 READ COMMITTED (提交讀 RC)
大多數的數據庫系統的默認隔離級別都是READ COMMITTED(MySQL 不是)。READ COMMITTED滿足前面提到的隔離級別的簡單定義:一個事務開始時,只能“看見” 已提交的事務所做的修改。換句話說,一個事務從開始知道提交之前,所做的任何修改對其他事務都是不可見的。這個級別也叫不可重復讀,因為在同一事務內執行兩次相同的查詢,可能會得到不一樣的結果。
例子: 當事務的隔離級別在RC級別的時候,事務A和事務B同時對數據D操作,當事務A開始的時候,讀取的數據D保存下來了,這是事務B也在修改數據D,并且先于事務A提交。這是事務A再讀數據D的時候,就會出現前后不一致情況,這就是所謂的不可重復讀。
1.1.4 REPEATABLE READ (可重復讀 RR)
這是MySQL的默認事務隔離級別,它確保同一事務的多個實例在并發讀取數據時,會看到同樣的數據行。不過理論上,這會導致另一個棘手的問題:幻讀 (Phantom Read)。簡單的說,幻讀指當用戶讀取某一范圍的數據行時,另一個事務又在該范圍內插入了新行,當用戶再讀取該范圍的數據行時,會發現有新的“幻影” 行。InnoDB和Falcon存儲引擎通過多版本并發控制(MVCC,Multiversion Concurrency Control)機制解決了該問題。
例子:mysql的默認事務隔離級別是RR級別的,同樣是上述例子,當時不同的是當事務A和事務B開始的時候,都保存一份自己的快照,每一份快照中都有數據D的值,所以這樣在同一事務中,無論重讀讀多少次都是正確的。
例子:在RR級別中,可能出現幻讀。同樣是上述例子,事務A和事務B同時查詢數據D,事務A發現數據D為空,就想插入數據,但是這是事務B已經插入了數據D并且已經提交。這時事務A的提交就會出錯。這是因為事務A的寫操作是當前讀操作。
1.1.5 SERIALIZABLE (可串行化 S)
這是最高的隔離級別,它通過強制事務排序,使之不可能相互沖突,從而解決幻讀問題。簡言之,它是在每個讀的數據行上加上共享鎖。在這個級別,可能導致大量的超時現象和鎖競爭。
隔離級別 | 臟讀可能性 | 不可重復可能性 | 幻讀可能性 | 加鎖讀 |
---|---|---|---|---|
READ UNCOMMITTED | Yes | Yes | Yes | No |
READ COMMITTED | No | Yes | Yes | No |
REPEATABLE READ | No | No | Yes | No |
SERIALIZABLE | No | No | No | Yes |
2. MVCC機制
InnoDB的一致性的非鎖定讀就是通過在MVCC實現的,Mysql的大多數事務型存儲引擎實現的都不是簡單的行級鎖。基于提升并發性能的考慮,它們一般都同時實現了多版本并發控制(MVCC)。MVCC的實現,是通過保存數據在某一個時間點的快照來實現的。因此每一個事務無論執行多長時間看到的數據,都是一樣的。所以MVCC實現可重復讀。
- 快照讀:select語句默認,不加鎖,MVCC實現可重復讀,使用的是MVCC機制讀取undo中的已經提交的數據。所以它的讀取是非阻塞的
- 當前讀:select語句加S鎖或X鎖;所有的修改操作加X鎖,在select for update 的時候,才是當地前讀。
RR隔離級別下的快照讀,不是以begin開始的時間點作為snapshot建立時間點,而是以第一條select語句的時間點作為snapshot建立的時間點。
2.1. MVCC依賴數據
行記錄隱藏字段
- db_row_id,行ID,用來生成默認聚簇索引(聚簇索引,保存的數據在物理磁盤中按順序保存,這樣相關數據保存在一起,提高查詢速度)
- db_trx_id,事務ID,新開始一個事務時生成,實例內全局唯一
- db_roll_ptr,undo log指針,指向對應記錄當前的undo log
- deleted_bit,刪除標記位,刪除時設置
undo log
-
用于行記錄回滾,同時用于實現MVCC
圖片1.png
2.2 操作方式
- update
- 行記錄數據寫入undo log,事務的回滾操作就需要undo log
- 更新行記錄數據,當前事務ID寫入db_trx_id,undo log指針寫入db_roll_ptr
- delete
- 和update一樣,只增加deleted_bit設置
- insert
- 生成undo log
- 插入行記錄數據,當前事務ID寫入db_trx_id, db_roll_ptr為空
這樣設計使得讀操作很簡單,性能很好,并且也能保證只會讀到符合標準的行,不足之處是每行記錄都需要額外的儲存空間,需要做更多的行檢查工作,以及額外的維護工作
2.3 MVCC如何實現RR
- RR定義:在一個事務內同一快照讀執行任意次數,得到的數據一致;且只能讀到第一次執行前已經提交的數據或本事務內更改的數據
- 原理:對符合查詢條件的記錄進行可見性判斷(就是那些數據本事務可以看見,那些數據看不見)
- read view:記錄當前處于活動狀態的所有事務ID,RR級別下,第一次快照讀時創建,RC級別下,每次快照讀均會創建新的
- 缺點: 可能出現幻讀
3 總結
在事務隔離級別為RC和RR級別下, InnnoDB存儲引擎使用的才是多版本并發控制。然而,對于快照數據的定義卻不相同。在RC事務隔離級別下,對于快照數據(undo端數據),總是讀取被鎖定行的最新的一份快照數據。而在RR事務隔離級別下,對于快照數據,多版本并發控制總是讀取事務開始時的行數據。