MySQL知識點總結(重點分析事務)

範式

數據庫三種範式以下。算法

範式 描述 反例
第一範式 每一個字段都是原子的,不能再分解 某個字段是JSON串,或者是數組
第二範式 1) 表必須有主鍵,能夠是多個順序屬性的組合。2) 非主屬性必須徹底依賴主屬性(這裏指的是組合主鍵),而不能部分依賴。 好友關係表中,主鍵是關注人ID和被關注人ID,表中存儲的姓名等字段只依賴主鍵中的一個屬性,不徹底依賴主鍵
第三範式 沒有傳遞依賴(非主屬性必須直接依賴主屬性,不能間接依賴主屬性) 在員工表中,有部門ID和部門名稱等,部門名稱直接依賴於部門ID,而不是員工ID

通常工程中,對於數據庫的設計要求達到第三範式,但這不是必定要遵照的,因此在開發中,爲了性能或便於開發,出現了不少違背範式的設計。如冗餘字段、字段中存一個JSON串,分庫分表以後數據多維度冗餘存儲、寬表等。數據庫

B+樹

關於B樹和B+樹的概念,請見漫畫算法:什麼是 B+ 樹?數組

B+樹的邏輯結構

下面來看一下數據庫中主鍵索引對應的B+樹的邏輯結構。併發

image.png

該結構有幾個關鍵特徵(與通常的B+樹有點不一樣)異步

  • 在葉子節點一層,全部記錄的主鍵從小到大的順序排列,而且造成了一個雙向鏈表,葉子節點的每個Key指向一條記錄。
  • 在非葉子節點一層,每一個非葉子節點都指向葉子節點中值最小的Key(但非葉子節點不存儲記錄),同層的非葉子節點也造成一個雙向鏈表。

基於B+樹的這幾個特徵,就能夠很容易的實現範圍查詢、前綴匹配模糊查詢、排序和分頁(查詢條件應是索引)。這裏有幾個要注意的問題。性能

  • 模糊查詢不該該使用後綴匹配或者中間匹配,由於索引的排序是按照從小到大排序的,只有前綴相同的纔會被排列在一塊兒,不然就用不上索引了,只能逐個遍歷。
  • 對於ofset這種特性,實際上是用不到索引的。好比select *** where *** limit 100, 10,數據庫須要遍歷前面100條數據才知道offset=100的位置在哪。合理的分頁方法就是不使用offset,把offset變成條件來查詢。好比變成select *** where *** and id > 100 limit 10,這樣才能利用索引的遍歷,快速定位id=100的位置。

事務與鎖

事務的四大特性ACID。設計

  • 原子性(A):事務要麼不執行,要麼徹底執行。(若是執行到一半機器宕機了,已執行的部分須要回滾回去)。
  • 一致性(C):各類約束條件,好比主鍵不能爲空,參照完整性等。
  • 隔離性(I):只要事務不是串行的,就須要隔離(通常都是並行的,效率更高嘛)。
  • 持久性(D):一旦事務提交了,數據不能丟失。

事務與事務併發地操做數據庫的表記錄,可能會致使下面幾類問題。日誌

問題 描述
髒讀 一個事務A讀取了另外一個未提交的事務B的數據,可是事務A提交以前,事務B又回滾了,致使事務A剛剛讀到的就是一個髒數據(RC隔離級別可解決)。
不可重複讀 同一個事務兩次查詢同一行記錄,獲得的結果不同。由於另外一個事務對該行記錄進行了修改操做(行排它鎖可解決)。
幻讀 同一個事務兩次查詢某一範圍,獲得的記錄數不同,由於另外一個事務在這個範圍內進行了增長或刪除操做(臨鍵鎖可解決)。
丟失更新 兩個事務同時修改同一行記錄,事務A的修改被後面的事務B覆蓋了(須要本身加鎖來解決)。

下面來看一下InnoDB的事務隔離級別。可解決上面的三個問題,最後一個問題只能在業務代碼中解決。code

名稱 描述
READ_UNCOMMITTED(RU) 跟沒有同樣,幾乎不使用。
READ_COMMITTED(RC) 只能讀取另外一個事務已提交的事務,能防止髒讀。
REPEATABLE_READ(RR) 可重複讀(在一個事務內屢次查詢的結果相同,其它事務不可修改該查詢條件範圍內的數據,會根據條件上Gap 鎖)
SERIALIZABLE 全部的事務依次逐個執行,至關於串行化了,效率過低,通常也不使用。

關於數據庫的鎖,請詳見這篇。MySQL中的「鎖」事cdn

事務實現原理1(Redo Log)

Write-Ahead

爲了保證數據的持久性,須要每提交一個事務就刷一次磁盤,可是這樣效率過低了,因此就有了Write-Ahead。
Write-Ahead:先在內存中提交事務,而後寫日誌(在InnoDB中就是redo log,日誌是爲了防止宕機致使內存數據丟失),而後再後臺任務中把內存中的數據異步刷到磁盤。

Redo Log的邏輯與物理結構

從邏輯上來說,日誌就是一個無限延長的字節流,從數據庫啓動開始,日誌就一直在增長,直到數據庫關閉。
在邏輯上,日誌是按照時間順序從小到大用LSN(是一個64位的數)來編號的,由於事務有大有小,因此日誌是個變長記錄(每一段數據量都不同)。

image.png

從物理上來說,日誌不多是一個無限延長的字節流,由於每一個文件有大小限制。在物理上是整塊的讀取和寫入(這裏就是Redo Log 塊,一個塊就是512字節),而不是按字節流來處理的。並且日誌是能夠被覆寫的,由於當數據被刷到磁盤上後,這些日誌也就沒有用了,因此他們是能夠被覆蓋的。可循環使用,一個固定大小的文件,每512字節一個塊。

image.png

Physiological Logging

在InnoDB中,Redo Log採用先以Page爲單位記錄日誌(物理記法),每一個Page裏再採用邏輯記法(記錄Page裏的哪一行修改了)。這種記法就叫作Physiological Logging。
之因此採用這種記法,是邏輯日誌和物理日誌的對應關係決定的。

  • 一條邏輯日誌可能會產生多個Page的物理日誌。由於一個表可能有多個索引,每一個索引都是一個B+樹,更新一條記錄(一個邏輯日誌),但這可能會同時更新多個索引,致使產生了多個Page的物理日誌。
  • 就算一條邏輯日誌對應一個Page,也可能會修改這個Page的多個位置(在中間插入一條記錄,須要修改Page的多個位置)。

事務崩潰恢復分析

未提交的事務日誌也在Redo Log中

由於不一樣事務的日誌在Redo Log中是交叉存在的,因此無法把未提交的事務與已提交的事務分開。ARIES算法的作法就是,無論事務有沒有提交,它的日誌都會被記錄到Redo Log上並刷到磁盤中。當崩潰恢復的時候,會把Redo Log所有重放一遍(不論是提交的仍是未提交都都重作了,也就是徹底恢復到崩潰以前的狀態),而後再把未提交的事務給找出來,作回滾處理。

Rollback轉化爲Commit

其實事務的回滾都是反向提交。也就是根據事務中的SQL語句生成反向對應的SQL語句執行,而後Commit(這種逆向的SQL語句也會被記錄到Redo Log中,防止恢復中宕機,可是會與正常的日誌區分開),因此回滾是邏輯層面上的回滾,在物理層面實際上是個提交。

image.png

ARIES算法

以下圖,有六個事務,每一個事務所在的線段表示事務在Redo Log中的起始位置和結束位置。發生宕機時,須要回滾事務T三、T四、T5。
在圖中,綠線表示兩個Checkpoint點和Crash(宕機)點。藍線表示三個階段工做的起始位置。

image.png

階段1:分析階段

在分析階段,要解決兩個問題。
1)肯定哪些數據頁是髒頁,爲階段2的Redo作準備(找出從最近的Checkpoint到Crash之間全部未刷盤的Page)。
2)肯定哪些事務未提交,未階段3的Undo作準備(由於未提交的事務也寫進了Redo Log中,須要將這些事務找出來,並作回滾)。

ARIES的Checkpoint機制,通常使用的是Fuzzy Checkpoint,它在內存中維護了兩個表,活躍事務表和髒頁表。
1)活躍事務表:當前全部未提交事務的集合,每一個事務維護了一個關鍵變量lastLSN(該事務產生的日誌中最後一條日誌的LSN)。 2)髒頁表:當前全部未刷到磁盤上得Page的集合(包括未提交事務和已提交事務),recoveryLSN是致使該頁爲髒頁的最先LSN(最近一次刷盤後最先開始的事務產生的日誌的LSN)。

每次Fuzzy Checkpoint,就是把這兩個表的數據生成一個快照,造成一條checkponit日誌,記入Redo Log中。
下圖展現了事務的開始標誌(S表示Start transaction),結束標誌(C表示Commit)以及Checkpoint在Redo Log中的位置(這裏只展現了活躍事務表,髒頁表也是相似的,惟一不一樣的就是髒頁集合只會增長,不會減小,髒頁集合中可能有些頁是乾淨的,但因爲Redo Log是冪等的,因此不影響)。

image.png

階段2:進行Redo

階段1中已經準備好了髒頁集合,取集合中髒頁的recoveryLSN的最小值(也就是最先開始髒的那一頁),獲得firstLSN,在Redo Log中從firstLSN開始遍歷到末尾,把每條Redo Log對應的Page所有從新刷到磁盤中。可是這些髒頁中可能有些頁並非髒的,因此這裏要作冪等。也就是利用Page中的一個PageLSN字段(它記錄了當前Page刷盤時最後一次修改它的日誌對應的LSN),在Redo重放的時候,判斷若是日誌的LSN比磁盤中得PageLSN要小,那就直接略過(這點很是相似TCP的超時重發中的判重機制)。在Redo完成後,保證了全部的髒頁都刷到了磁盤中,而且未提交事務也寫入了磁盤中,這時須要對未提交事務進行回滾,也就是階段3。

階段3:進行Undo

階段1中已經準備好了未提交事務集合,從最後一條日誌逆向遍歷(每條日誌都有一個preLSN字段,指向前一條日誌),直到未提交事務中的第一條日誌。
從後往前開始回滾,每遇到一條屬於未提交事務集合中事務的日誌,就生成一條對應的逆向SQL(這裏須要用到對應的歷史版本數據)執行,這條逆向SQL也會被記錄到Redo Log中,但與通常的日誌有所不一樣,稱爲Compensation Log Record(CLR),逆向執行完事務後(遇到事務的開始標誌)就提交,這也就完成了回滾。

事務實現原理2(Undo Log)

Undo Log功能

上面在宕機回滾中,提到了生成逆向SQL,這個是須要使用到歷史版本數據的。Undo Log就是用於記錄和維護歷史版本數據的(事務的每一次修改,就是一個版本)。其實這是用到了CopyOnWrite的思想,每次事務在修改記錄以前,都會把該記錄拷貝一份出來(將它備份在Undo Log中)再進行修改操做(這個思想相似JDK中CopyOnWriteArratList)。事務的RC、RR隔離級別就是經過CopyOnWrite實現的。

併發的事務,要同時讀寫同一行數據,只能讀取數據的歷史版本,而不能讀取當前正在被修改的數據(因此這樣就有了丟失更新的問題,固然這個能夠經過加鎖等方式解決)。這種機制稱爲Multiversion concurrency control 多版本併發控制(MVCC)。基於MVCC的這種特性,一般select語句都是不加鎖的,由於他們讀到的都是歷史版本的數據,這種讀,叫作「快照讀」。

Undo Log結構

Undo Log並非log,而是數據(因此Undo Log也會被記錄到Redo Log中,在宕機後用Redo Log來恢復Undo Log),它記錄的不是事務執行的日誌,而是數據的歷史版本。一旦事務提交後,就不須要Undo Log了(它只在事務提交過程當中有用)。
因此Undo Log應該叫作記錄的備份數據,也就是在事務提交以前的備份數據(由於可能有其它事務還在引用歷史版本數據),事務提交後它就沒有用了。
Undo Log的結構除了主鍵ID和數據外,還有兩個字段。一個是修改該記錄的事務ID,一個是rollback_ptr(指向以前的一個版本,因此它用來串聯全部的歷史版本)。

image.png
相關文章
相關標籤/搜索