1. MVCC機制詳解
在Read Uncommitted級別下,事務總是讀取到最新的數據,因此根本用不到歷史版本,所以MVCC不在該級別下工作。
在Serializable級別下,事務總是順序執行。寫會加寫鎖,讀會加讀鎖,完全用不到MVCC,所以MVCC也不在該級別下工作。
Mysql在讀已提交和可重復讀隔離級別下都實現了MVCC機制。
Mysql在可重復讀隔離級別下如何保證事務較高的隔離性,同樣的sql查詢語句在一個事務里多次執行查詢結果相同,就算其它事務對數據有修改也不會影響當前事務sql語句的查詢結果。這個隔離性就是靠MVCC(Multi-Version Concurrency Control)機制來保證的,對一行數據的讀和寫兩個操作默認是不會通過加鎖互斥來保證隔離性,避免了頻繁加鎖互斥,而在串行化隔離級別為了保證較高的隔離性是通過將所有操作加鎖互斥來實現的。
2. undo日志版本鏈
undo日志版本鏈是指一行數據被多個事務依次修改過后,在每個事務修改完后,Mysql會保留修改前的數據undo回滾日志,并且用兩個隱藏字段trx_id和roll_pointer把這些undo日志串聯起來形成一個歷史記錄版本鏈。
InnoDB存儲引擎在數據庫每行數據的后面添加了三個字段
-
6字節的事務ID(DB_TRX_ID)字段:當一個事務開始執前,mysql會為這個事務分配一個全局自增的事務id。之后該事務對當前行進行的增、刪、改操作時,都會將自己的事務id記錄到DB_TRX_ID中。
-
7字節的回滾指針(DB_ROLL_PTR)字段:事務對當前行進行改動時,會將舊數據寫入進undo log中,再將新數據寫入當前行,且當前行的roll_pointer指向剛才那個undo log,因此可以通過roll_pointer找到該行的前一個版本。
-
6字節的DB_ROW_ID字段:如果當前表有整數類型的主鍵,則row_id就是主鍵的值。如果沒有整數類型的主鍵,則mysql會按照字段順序選擇一個非空的整數類型的唯一索引作為row_id。如果mysql沒有找到,則會自動生成一個自動增長的整數作為row_id。
當一直有事務對該行改動時,就會一直生成undo log,最終將會形成undo log版本鏈。
3. Read View機制詳解
在可重復讀隔離級別,當事務開啟,執行任何查詢sql時會生成當前事務的一致性視圖read-view,該視圖在事務結束之前都不會變化(如果是讀已提交隔離級別在每次執行查詢sql時都會重新生成),這個視圖由執行查詢時所有未提交事務m_ids數組(數組里最小的id為m_up_limit_id)和已創建的最大事務id(m_low_limit_id)組成,事務里的任何sql查詢結果需要從對應版本鏈里的最新數據開始逐條跟read-view做比對從而得到最終的快照結果。
在RC級別下,當前事務總是希望讀取到別的事務已經提交的數據,因此當前事務事務會在執行每一次快照讀的情況下都會去生成ReadView,實時更新m_ids,及時發現那些已經提交的事務。
在RR級別下,當前事務當然也能夠讀取到別的事務已經提交的數據,但為了避免不可重復讀,因此只會在執行第一次快照讀的情況下去生成ReadView,之后的快照讀會一直沿用該ReadView。
字段 | 含義 |
---|---|
m_ids | 在創建ReadView的那一刻,mysql中所有未提交的事務id集合 |
m_up_limit_id | m_ids中的最小值,如果當前無活躍事務,為m_low_limit_id值 |
m_low_limit_id | mysql即將為下一個事務分配的事務id,并不是m_ids中的最大值 |
m_creator_trx_id | 即創建此ReadView的事務id |
4. undo日志版本鏈比對規則
1.當【版本鏈中記錄的 trx_id 等于當前事務id(trx_id = creator_trx_id)】時,說明版本鏈中的這個版本是當前事務修改的,所以該快照記錄對當前事務可見。
2.如果 row 的 trx_id 落在綠色部分( trx_id < m_up_limit_id),表示這個版本是已提交的事務生成的,這個數據是可見的;
3.如果 row 的 trx_id 落在紅色部分( trx_id > m_low_limit_id),表示這個版本是由將來啟動的事務生成的,是不可見的(若row 的 trx_id 就是當前自己的事務是可見的);
4.如果 row 的 trx_id 落在黃色部分(m_up_limit_id<= trx_id < m_low_limit_id),那就包括兩種情況
- 若 row 的 trx_id 在視圖數組中,表示這個版本是由還沒提交的事務生成的,不可見(若 row 的 trx_id 就是當前自
己的事務是可見的); - 若 row 的 trx_id 不在視圖數組中,表示這個版本是已經提交了的事務生成的,可見。
對于刪除的情況可以認為是update的特殊情況,會將版本鏈上最新的數據復制一份,然后將trx_id修改成刪除操作的
trx_id,同時在該條記錄的頭信息(record header)里的(deleted_flag)標記位寫上true,來表示當前記錄已經被
刪除,在查詢時按照上面的規則查到對應的記錄如果delete_flag標記位為true,意味著記錄已被刪除,則不返回數
據。
注意:begin/start transaction 命令并不是一個事務的起點,在執行到它們之后的第一個修改操作InnoDB表的語句,
事務才真正啟動,才會向mysql申請事務id,mysql內部是嚴格按照事務的啟動順序來分配事務id的。