事務的四個特性
ACID
- 原子性:事務中的操作要麼全部成功,要麼全部失敗。通過 undo log 實現
- 一致性:資料庫在事務執行前後都處於一個正確的狀態。
- 隔離性:事務執行過程中,不應該收到其他事務的打擾,併發的事務要隔離。通過鎖、MVCC實現
- 永續性:事務執行完成之後,資料將永遠儲存在資料庫中,即使出現意外當機的情況,也不應該對這部分資料造成任何影響。通過 redo log 實現
事務的四個隔離級別
- 讀取未提交:事務的修改,即使沒有提交,對其他事務也都是可見的。這種現象叫髒讀。
- 讀取已提交:事務讀取已提交的資料,大多數資料庫的預設隔離級別。當一個事務在執行過程中,前後讀取的資訊不一樣,這種情況稱為不可重複讀。
- 可重複讀:mysql 的預設隔離級別。它解決了髒讀、不可重複讀問題、存在幻讀問題。幻讀:當一個事務A讀取某一個範圍的資料時,另一個事務B在這個範圍插入行,A事務再次讀取這個範圍的資料時,會產生幻讀。
- 可序列化:所有事務依次逐個執行,互相之間沒有干擾。
redo log & bin log
資料是存放在磁碟中的,如果每次讀寫資料都需做磁碟IO操作,併發場景下效能就會很差。為此 Mysql 引入快取 Buffer Pool 來緩解資料庫的磁碟壓力。
當從資料庫讀資料時,首先從快取中讀取,如果快取中沒有,則從磁碟讀取後放入快取;當向資料庫寫入資料時,先向快取寫入,此時快取中的資料頁資料變更,這個資料頁稱為髒頁;Buffer Pool 中的資料會定期刷到磁碟中,這個過程稱為刷髒頁。
Mysql 當機,如果刷髒頁還未完成,更新就會丟失,無法保證事務的持久化。
為了解決這個問題引入了 redo log
redo log 是屬於 InnoDB 的事務日誌,它是物理日誌,記錄的是資料庫中每個頁的修改,而不是某一行或某幾行修改成怎樣,可以用來恢復提交後的物理資料頁,且只能恢復到最後一次提交的位置。
redo log 使用了 write ahead logging 技術,即先寫日誌,再修改 Buffer Pool 中的資料,至於快取何時刷盤則有後臺執行緒非同步處理了。如果寫的順序反過來,可能存在日誌和資料不一致的情況。
說了 redo log 就不得不說 bin log
bin log 是 Mysql Server 層面的,記錄的所有的 DDL、DML 操作,用來備份資料和主備同步資料。想要做到主備資料一致就得保證 redo log 和 binlog 的一致性,因此 redo log 的寫入使用兩階段提交,如下圖,prepare階段 redo log 寫入磁碟,然後 binlog 寫入磁碟,最後事務提交。
在 BC 兩個點會出現資料不一致的問題。伺服器重啟後發現 redo log 中處於 prepare 狀態的記錄,再根據事務ID 檢查 binlog 是否包含此條 redo log 的更新內容,如果不包含,redo log 丟棄此次變更,如果包含,事務會提交。
undo log
屬於 InnoDB 的事務日誌,屬於邏輯日誌,其回滾作用,是保障事務原子性的關鍵。
記錄的是資料修改前的狀態。
- 比如事務執行一條更新語句時,會先在 undo log 中寫入一條相反操作的邏輯日誌。
- 同一個事務中對同一條記錄的修改不會記錄多條日誌,undo log 只儲存了資料的原始版本。
MVCC
MVCC,即多版本併發控制,在 InnoDB 中的實現主要是為了提高資料庫併發效能,用更好的方式去處理讀寫衝突,做到即使有讀寫衝突時,也能做到不加鎖,非阻塞併發讀。
當前讀與快照讀:
- 當前讀:select for update、update、insert、delete 這些操作都是當前讀,什麼叫當前讀?讀取的是記錄的最新版本,且會對讀取的記錄加鎖,保證其他併發事務不能修改當前記錄
- 快照讀:快照讀是 Mysql 為 MVCC 實現的一個非阻塞讀併發功能,讀取的不一定是最新記錄,也有可能是某個歷史記錄。
MVCC解決的問題
資料庫併發場景有三種:
- 讀讀:不存在任何問題,也不需要併發控制
- 讀寫:有執行緒安全問題,可能會造成事務隔離性問題,可能遇到髒讀、幻讀、不可重複讀
- 寫寫:有執行緒安全問題,可能存在更新丟失問題
MVCC 解決的問題
- 在併發讀寫資料庫時,可以做到在讀操作時不用阻塞寫操作,寫操作也不用阻塞讀操作,提高了資料庫併發讀寫的效能
- 解決髒讀、幻讀、不可重複讀等事務隔離問題,但是不能解決更新丟失問題(加鎖解決)
MVCC的實現原理
MVCC 由三個隱式欄位、undo log、read view 三個元件實現。
三個隱式欄位:每行記錄除了我們自定義的欄位外,還有資料庫定義的一些隱式欄位欄位
- DB_TRX_ID:最近修改事務ID,記錄建立這條記錄或者最後一次修改該記錄的事務ID
- DB_ROLL_PTR:回滾指標,指向 undolog 中上一個資料版本
- DB_ROW_JD:隱藏的主鍵,如果資料表沒有主鍵,那麼 innodb 會自動生成一個6位元組的row_id
undo log:
undolog 會用連結串列有序儲存一條記錄的所有歷史版本,鏈首是最新版本,鏈尾是最舊版本。
Read View:
Read View 是事務進行快照讀的時候生成的檢視,在事務執行快照讀的那一刻,會生成一個資料系統當前的快照,記錄並維護系統當前活躍事務的id,事務的id值是遞增的。
Read View 的最大作用是用來做可見性判斷的,有三個全域性屬性:
- trx_list:一個數值列表,儲存活躍的事務ID
- up_limit_id:記錄 trx_list 列表中事務ID最小的ID
- low_limit_id:記錄 Read View 生成時刻系統尚未分配的下一個事務ID
具體的比較規則如下:
- 首先比較 DB_TRX_ID < up_limit_id,是:當前事務能看到 DB_TRX_ID 所在的記錄,否:進入下一個判斷
- 判斷 DB_TRX_ID >= low_limit_id,是:代表 DB_TRX_ID 所在的記錄在 Read View 生成後才出現的,對於當前事務肯定不可見,否:進入下一步判斷
- 判斷 DB_TRX_ID 是否在活躍事務中,是:當前事務是看不到活躍事務的,否:說明這個事務在 Read View 生成之前就已經提交了,那麼修改的結果是能夠看見的。
RC、RR級別下快照讀有什麼不同
- 在 RC 隔離級別下,每個快照讀都會生成並獲取最新的 Read View
- 在 RR 隔離級別下,同一個事務中的第一個快照讀才會建立 Read View,之後的快照讀獲取的都是同一個 Read View
如何解決幻讀問題
- 如果事務中都是用快照讀,那麼不會產生幻讀的問題
- 快照讀和當前讀一起使用的時候就會產生幻讀
- 如果都是當前讀的話,通過間隙鎖來解決幻讀問題