MySQL 事務特性 ACID 實現原理

MySQL 使用 B+Tree 的數據結構,儘可能少的層級或 IO 讀取量的情況下,能夠緩存或存儲更多數據量且達到快速查詢的效果。

在進行索引存儲的時候,索引的數據要放到磁盤裏面,不可能將磁盤裏面的所有文件一口氣讀到內存裏面,因此要使用分塊讀取的方式,而操作系統本身進行內存和磁盤交互的時候是以頁爲單位的,按照頁的整數倍作爲某一個磁盤塊來進行數據的讀取。

在進行數據讀取的時候,還要確認一個事情:索引是要有 key-value 值的,key 是指定的索引列,value 是具體的行數據。

索引系統在具體實現的時候,需要採用 key-value 格式的數據,那需要考慮使用什麼樣的數據結構?

支持 key-value 存儲的數據結構有很多種:Hash 表、二叉樹、紅黑樹、AVL 樹、B 樹、B + 樹。

Hash 表需要比較優良的 Hash 算法,但不支持範圍查詢,因爲 Hash 表裏面的數據都是均勻散列的,是無序的。

當進行某一個範圍查詢的時候,只能挨個對比,這樣效率是比較低的。

MySQL Memory 存儲引擎支持 Hash 索引,但 INNODB 和 MYISAM 並不支持。

二叉樹、AVL 樹、紅黑樹這幾個樹都是可以存儲 k-v 格式數據的,共同的特點:最多有兩個分支。

如果想存儲更多數據的話,就會導致樹的高度更高,樹的高度更高會增加 IO 讀取的次數,這樣數據的訪問效率是較低的。

因此基於這樣的考慮把二叉樹變成多叉樹同時要具備有序和平衡的特點,因此 B 樹應用而生。

在 B 樹裏面,每個數據節點可以存儲更多的數據,同時數據和 key 值均勻的放在葉子節點和非葉子節點裏面。

若存滿三層 B 樹的話,可以支持幾千條或上萬條的數據存儲,這樣是沒有辦法滿足業務要求的,基於這樣的場景再做一個延伸和考慮,將所有的 data 存儲在葉子節點中,非葉子節點中不要存儲數據,因此有了 B + 樹,在 B + 樹實際存儲的時候,葉子節點存放了所有數據,而非葉子節點中並不存儲實際的數據同時在葉子節點中有相應的指針可以連起來,可以通過頭指針直接訪問到當前葉子節點的最後一個數據,這樣就完成了 MySQL 索引數據的存儲。

3-4 層的 B + 樹足以支撐千萬級數據量的存儲。

一個原則是 key 所佔用的存儲空間要儘可能小,這樣可以保證存儲更多的數據。

MySQL 事務

原子性、一致性、隔離性、持久性,

原子性的實現

原子性通過 undolog,保留數據的歷史狀態,如果沒有提交,歷史狀態依然保存,當需要回滾的時候,把歷史數據拿過來,重新放入進去。

保存某一行記錄的歷史狀態或歷史版本,在提交失敗之後,能恢復到上一個歷史版本的狀態。

在並行運行的各個事務之間相互獨立,互不干擾。

一個事務在運行中,另一個事務不能去影響當前正在執行的事務。

這裏會涉及到併發相關的基本操作:鎖,但給數據加鎖之後的效率低,所以通過 MVCC 多版本併發控制的方式來實現。

持久性的實現

通過 redolog 實現持久性,很多數據存儲在磁盤,當更新某一條記錄的時候,這個更新的操作執行效率很低。

這裏涉及到對磁盤數據的一個尋址過程,每次在進行更新操作的時候,需要把數據從磁盤讀取到內存裏面去,

在內存裏面先更新完成,更新完成之後,變成髒頁,需要把內存裏面的數據刷新到磁盤裏面,來保證數據的一致性。

在進行尋址的過程中,MySQL 突然掛了,這條數據沒有來得及寫磁盤,

重啓之後,怎麼保證把數據恢復出來?

通過 redolog,redolog 也是存在磁盤上的一個日誌文件,每次往磁盤寫入數據之前,先往日誌文件裏面寫入一份,然後再往對應磁盤的數據文件中寫。

既然真實數據在磁盤,redolog也在磁盤,爲什麼還要寫一份redolog?

這裏也涉及到了 io 的問題,在進行讀寫操作的時候,IO 有兩種方式,隨機 IO 和順序 IO。

一個龐大的文件,裏面有 10 億條記錄,在這個文件的某一行或某一個位置進行修改,先找到這個位置,才能去寫,而找的過程就會浪費很多時間,這就是隨機 IO。

順序 IO 是不管當前文件存儲了多少數據,所有新增的數據都直接往後面進行 append 操作,先順序寫到 redolog,然後再慢慢進行讀寫,不需要實時性,不需要花費大量的時間去找數據來進行數據的更新。

redolog 不會整理數據,有一個機制叫循環 log,這是一個固定空間的文件,一直往裏面追加,假如滿了,會把之前的數據幹掉,如果 redolog 沒有寫成功,數據丟了,就真的丟了。

先寫redolog還是先寫binlog?

記錄了 redolog 之後,binlog 中也會記錄,同時記錄會有什麼問題麼?

不管先寫誰都有問題,這裏會涉及到二階段提交,這裏不是分佈式事務中的二階段提交。

binlog 和 redolog 要保證二階段提交,即 2 個日誌數據,要麼都寫,要麼都不寫。

如果一個寫了一個沒寫,要判斷下能否生效或者數據最終是否是一致。

什麼是二階段提交?如何保證宕機時數據的一致性?

redolog 爲了記錄或爲了阻止 crash 的存在,比如斷電。

binlog 是爲了主從複製或數據恢復的。

在操作的時候,2 個日誌文件會同時寫,在數據恢復的時候,要根據 2 個日誌來判斷。

數據更新的流程(看到底先寫誰,後寫誰?

先獲取數據,判斷當前數據是否在內存中,如果沒有的話,需要把磁盤中的數據加載到內存中。

然後修改數據,同時要寫入新的數據,先寫內存,寫完內存之後,纔會往磁盤中投放。

寫這 2 個日誌文件的時候,分爲 3 個步驟:

爲什麼要這麼設計?有什麼好處?

寫完 redolog,還沒有來得及寫 binlog 的時候,突然斷電了,binlog 是不具備對應信息的。

假設 2 臺機器,操作 A 數據庫,B 數據是通過 A 數據庫數據同步的。

A 和 B 2 個數據庫若不一致了,主從同步就失敗了,反過來也是一樣。

當寫完 binlog,還沒有寫完 redolog 的時候,突然斷電。

A 數據庫中的 binlog 記錄了對應的數據,B 數據庫已經將數據同步,但是 A 數據庫的 redolog 中沒有,出現斷電之後,不能通過 binlog 把數據恢復回去,A 機器少一條,B 機器已經同步了,此時還是不一致性。

因此才誕生了誕生了二階段提交,寫 redolog 是 prepare 狀態,當 binlog 寫完之後,事務提交了,再把 redolog 設置爲 commit 狀態。

整個過程中,任何步驟都有可能出現斷電情況。

如果此處斷電,再恢復的時候,先檢查 redolog,如果發現當前 redolog 是 prepare 狀態,再去 binlog 中找對應的數據。

如果有,則把 redolog 的 prepare 狀態修改成 commit 狀態。

如果沒有的話,就把 prepare 狀態直接設置爲失效。

這樣 2 個文件可以保持一致。

這裏發生斷電也是一樣的,先找 redolog,如果有 prepare 數據再找 binlog,binlog 有對應的記錄,把剛剛的 prepare 數據改成 commit 狀態 ,這樣 2 個文件都保持一致了。

binlog 沒有 parpare 狀態。

斷電丟失只是丟失內存數據,磁盤中的數據是不會發生丟失的。

事務的一致性沒有具體的實現,而是由其他 3 個特徵共同保證了一致性問題。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/7pnHVLf3Mqh2-NEwJMnnjQ