1. 程式人生 > >超乾貨!為了讓你徹底弄懂MySQL事務日誌,我通宵肝出了這份圖解!

超乾貨!為了讓你徹底弄懂MySQL事務日誌,我通宵肝出了這份圖解!

還記得剛上研究生的時候,導師常掛在嘴邊的一句話,“科研的基礎不過就是資料而已。”如今看來,無論是人文社科,還是自然科學,或許都可在一定程度上看作是資料的科學。 倘若剝開研究領域的外衣,將人的操作抽象出來,那麼科研的過程大概就是根據資料流動探索其中的未知資訊吧。當然科學研究的範疇涵蓋甚廣,也不是一兩句話能夠拎得清的。不過從這個角度上的闡述,也只是為了引出資料的重要性。 在當今社會,充斥著大量的資料。從眾多APP上的賬戶資料到銀行信用體系等個人檔案,都離不開對大量資料的組織、儲存和管理。而這,便是資料庫存在的目的和價值。 目前資料庫的型別主要分為兩種,一種是關係型資料庫,另一種是非關係型資料庫(NoSQL)。而我們今天的主角MySQL就是關係型資料庫中的一種。 ![本文結構](https://imgkr.cn-bj.ufileos.com/86155a95-3c83-4238-9b2b-e0b031409124.png) ## 1 關係型資料庫與NoSQL 關係型資料庫,顧名思義,是指儲存的資料之間具有關係。這種所謂的關係通常用二維表格中的行列來表示,即一個二維表的邏輯結構能夠反映表中資料的儲存關係。 概念總是拗口難懂的。那麼簡單來說,關係型資料庫的儲存就是按照表格進行的。資料的儲存實際上就是對一個或者多個表格的儲存。通過對這些表格進行分類、合併、連線或者選取等運算來實現對資料庫的管理。常見的關係型資料庫有MySQL、Oracle、DB2和SqlServer等。 非關係型資料庫(NoSQL)是相對於關係型資料庫的一種泛指,它的特點是去掉了關係型資料庫中的關係特性,從而可獲得更好的擴充套件性。NoSQL並沒有嚴格的儲存方式,但採用不同的儲存結構都是為了獲得更高的效能和更高的併發。NoSQL根據儲存方式可分為四大類,鍵值儲存資料庫、列儲存資料庫、文件型資料庫和圖形資料庫。這四種資料的儲存原理不盡相同,因而在應用場景上也有些許的差異。一般常用的有作為資料快取的redis和分散式系統的HBase。目前常見的資料庫排名可見網站: > https://db-engines.com/en/ranking ![四種NoSQL的特點比較](https://imgkr.cn-bj.ufileos.com/e3b1c056-9870-4823-83f5-73f9aeaa37bc.png) 關係型資料庫與非關係型資料庫本質上的區別就在於儲存的資料是否具有一定的邏輯關係,由此產生的兩類資料庫看的效能和優劣勢上也有一定的區別。二者對比可見下圖。 ![關係型資料庫與NoSQL的優缺點對比 ](https://imgkr.cn-bj.ufileos.com/1d65db8e-8205-4122-a556-7882edd924ad.png) ## 2 MySQL簡介 #### 介紹 在關係型資料庫中,MySQL可以說是其中的王者。它是目前最流行的資料庫之一,由瑞典 MySQL AB 公司開發,目前屬於 Oracle 公司。MySQL資料庫具有以下幾個方面的優勢: >- 體積小、速度快; >- 程式碼開源,採用了 GPL 協議,可以修改原始碼來開發自己的 MySQL 系統; >- 支援大型的資料庫,可以處理擁有上千萬條記錄的大型資料庫; >- 使用標準的 SQL 資料語言形式,並採用優化的 SQL 查詢演算法,有效地提高查詢速度; >- 使用 C 和 C++ 編寫,並使用多種編譯器進行測試,保證原始碼的可移植性; >- 可執行在多個系統上,並且支援多種語言; >- 核心程式採用完全的多執行緒程式設計,可以靈活地為使用者提供服務,充分利用CPU資源。 #### 邏輯架構 MySQL的邏輯架構可分為四層,包括連線層、服務層、引擎層和儲存層,各層的介面互動及作用如下圖所示。需要注意的是,由於本文將主要講解事務的實現原理,因此下文針對的都是InnoDB引擎下的情況。 > **連線層:**負責處理客戶端的連線以及許可權的認證。 > > **服務層:**定義有許多不同的模組,包括許可權判斷,SQL介面,SQL解析,SQL分析優化, 快取查詢的處理以及部分內建函式執行等。MySQL的查詢語句在服務層內進行解析、優化、快取以及內建函式的實現和儲存。 > > **引擎層:**負責MySQL中資料的儲存和提取。MySQL中的伺服器層不管理事務,事務是由儲存引擎實現的。其中使用最為廣泛的儲存引擎為InnoDB,其它的引擎都不支援事務。 > > **儲存層:**負責將資料儲存與裝置的檔案系統中。 ![MySQL的邏輯架構](https://imgkr.cn-bj.ufileos.com/f321e033-9e9c-457f-a6ab-fca1c97d2d92.png) ## 3 MySQL事務 事務是MySQL區別於NoSQL的重要特徵,是保證關係型資料庫資料一致性的關鍵技術。事務可看作是對資料庫操作的基本執行單元,可能包含一個或者多個SQL語句。這些語句在執行時,要麼都執行,要麼都不執行。 事務的執行主要包括兩個操作,提交和回滾。 > 提交:commit,將事務執行結果寫入資料庫。 > 回滾:rollback,回滾所有已經執行的語句,返回修改之前的資料。 MySQL事務包含四個特性,號稱ACID四大天王。 > **原子性(Atomicity)** :語句要麼全執行,要麼全不執行,是事務最核心的特性,事務本身就是以原子性來定義的;實現主要基於undo log日誌實現的。 > > **永續性(Durability** :保證事務提交後不會因為宕機等原因導致資料丟失;實現主要基於redo log日誌。 > > **隔離性(Isolation)** :保證事務執行儘可能不受其他事務影響;InnoDB預設的隔離級別是RR,RR的實現主要基於鎖機制、資料的隱藏列、undo log和類next-key lock機制。 > > **一致性(Consistency)** :事務追求的最終目標,一致性的實現既需要資料庫層面的保障,也需要應用層面的保障。 #### 原子性 事務的原子性就如原子操作一般,表示事務不可再分,其中的操作要麼都做,要麼都不做;如果事務中一個SQL語句執行失敗,則已執行的語句也必須回滾,資料庫退回到事務前的狀態。只有0和1,沒有其它值。 事務的原子性表明事務就是一個整體,當事務無法成功執行的時候,需要將事務中已經執行過的語句全部回滾,使得資料庫迴歸到最初未開始事務的狀態。 事務的原子性就是通過undo log日誌進行實現的。當事務需要進行回滾時,InnoDB引擎就會呼叫undo log日誌進行SQL語句的撤銷,實現資料的回滾。 #### 永續性 事務的永續性是指當事務提交之後,資料庫的改變就應該是永久性的,而不是暫時的。這也就是說,當事務提交之後,任何其它操作甚至是系統的宕機故障都不會對原來事務的執行結果產生影響。 事務的永續性是通過InnoDB儲存引擎中的redo log日誌來實現的,具體實現思路見下文。 #### 隔離性 原子性和永續性是單個事務本身層面的性質,而隔離性是指事務之間應該保持的關係。隔離性要求不同事務之間的影響是互不干擾的,一個事務的操作與其它事務是相互隔離的。 由於事務可能並不只包含一條SQL語句,所以在事務的執行期間很有可能會有其它事務開始執行。因此多事務的併發性就要求事務之間的操作是相互隔離的。這一點跟多執行緒之間資料同步的概念有些類似。 **鎖機制** 事務之間的隔離,是通過鎖機制實現的。當一個事務需要對資料庫中的某行資料進行修改時,需要先給資料加鎖;加了鎖的資料,其它事務是不執行操作的,只能等待當前事務提交或回滾將鎖釋放。 鎖機制並不是一個陌生的概念,在許多場景中都會利用到不同實現的鎖對資料進行保護和同步。而在MySQL中,根據不同的劃分標準,還可將鎖分為不同的種類。 > 按照粒度劃分:行鎖、表鎖、頁鎖 > > 按照使用方式劃分:共享鎖、排它鎖 > > 按照思想劃分:悲觀鎖、樂觀鎖 鎖機制的知識點很多,由於篇幅不好全部展開講。這裡對按照粒度劃分的鎖進行簡單介紹。 > **粒度**:指資料倉庫的資料單位中儲存資料的細化或綜合程度的級別。細化程度越高,粒度級就越小;相反,細化程度越低,粒度級就越大。 MySQL按照鎖的粒度劃分可以分為行鎖、表鎖和頁鎖。 > 行鎖:粒度最小的鎖,表示只針對當前操作的行進行加鎖; > 表鎖:粒度最大的鎖,表示當前的操作對整張表加鎖; > 頁鎖:粒度介於行級鎖和表級鎖中間的一種鎖,表示對頁進行加鎖。 ![資料庫的粒度劃分](https://imgkr.cn-bj.ufileos.com/70d52eda-6e3f-4650-a375-95a36cb43172.png) 這三種鎖是在不同層次上對資料進行鎖定,由於粒度的不同,其帶來的好處和劣勢也不一而同。 > 表鎖在操作資料時會鎖定整張表,因而併發效能較差; > > 行鎖則只鎖定需要操作的資料,併發效能好。但是由於加鎖本身需要消耗資源(獲得鎖、檢查鎖、釋放鎖等都需要消耗資源),因此在鎖定資料較多情況下使用表鎖可以節省大量資源。 MySQL中不同的儲存引擎能夠支援的鎖也是不一樣的。MyIsam只支援表鎖,而InnoDB同時支援表鎖和行鎖,且出於效能考慮,絕大多數情況下使用的都是行鎖。 **併發讀寫問題** 在併發情況下,MySQL的同時讀寫可能會導致三類問題,髒讀、不可重複度和幻讀。 (1)髒讀:當前事務中讀到其他事務未提交的資料,也就是髒資料。 ![](https://imgkr.cn-bj.ufileos.com/4a9d0f74-2e0c-4758-9087-6d50427d0bfb.png) 以上圖為例,事務A在讀取文章的閱讀量時,讀取到了事務B為提交的資料。如果事務B最後沒有順利提交,導致事務回滾,那麼實際上閱讀量並沒有修改成功,而事務A卻是讀到的修改後的值,顯然不合情理。 (2)不可重複讀:在事務A中先後兩次讀取同一個資料,但是兩次讀取的結果不一樣。髒讀與不可重複讀的區別在於:前者讀到的是其他事務未提交的資料,後者讀到的是其他事務已提交的資料。 ![](https://imgkr.cn-bj.ufileos.com/d431cccb-57f8-4b92-b63c-0cd7c71064f9.png) 以上圖為例,事務A在先後讀取文章閱讀量的資料時,結果卻不一樣。說明事務A在執行的過程中,閱讀量的值被其它事務給修改了。這樣使得資料的查詢結果不再可靠,同樣也不合實際。 (3)幻讀:在事務A中按照某個條件先後兩次查詢資料庫,兩次查詢結果的行數不同,這種現象稱為幻讀。不可重複讀與幻讀的區別可以通俗的理解為:前者是資料變了,後者是資料的行數變了。 ![](https://imgkr.cn-bj.ufileos.com/d5c4c6ec-8fed-4d56-abd4-4de35c819daf.png) 以上圖為例,當對0<閱讀量<100的文章進行查詢時,先查到了一個結果,後來查詢到了兩個結果。這表明同一個事務的查詢結果數不一,行數不一致。這樣的問題使得在根據某些條件對資料篩選的時候,前後篩選結果不具有可靠性。 **隔離級別** 根據上面這三種問題,產生了四種隔離級別,表明資料庫不同程度的隔離性質。 ![](https://imgkr.cn-bj.ufileos.com/8918284c-e88b-4af5-873d-74350e259933.png) 在實際的資料庫設計中,隔離級別越高,導致資料庫的併發效率會越低;而隔離級別太低,又會導致資料庫在讀寫過程中會遇到各種亂七八糟的問題。 因此在大多數資料庫系統中,預設的隔離級別時讀已提交(如Oracle)或者可重複讀RR(MySQL的InnoDB引擎)。 **MVCC** 又是一個難嚼的大塊頭。MVCC就是用來實現上面的第三個隔離級別,可重複讀RR。 > MVCC:Multi-Version Concurrency Control,即多版本的併發控制協議。 MVCC的特點就是在同一時刻,不同事務可以讀取到不同版本的資料,從而可以解決髒讀和不可重複讀的問題。 MVCC實際上就是通過資料的隱藏列和回滾日誌(undo log),實現多個版本資料的共存。這樣的好處是,使用MVCC進行讀資料的時候,不用加鎖,從而避免了同時讀寫的衝突。 在實現MVCC時,每一行的資料中會額外儲存幾個隱藏的列,比如當前行建立時的版本號和刪除時間和指向undo log的回滾指標。這裡的版本號並不是實際的時間值,而是系統版本號。每開始新的事務,系統版本號都會自動遞增。事務開始時的系統版本號會作為事務的版本號,用來和查詢每行記錄的版本號進行比較。 每個事務又有自己的版本號,這樣事務內執行資料操作時,就通過版本號的比較來達到資料版本控制的目的。 另外,InnoDB實現的隔離級別RR時可以避免幻讀現象的,這是通過`next-key lock`機制實現的。這裡簡單講講吧。 `next-key lock`實際上就是行鎖的一種,只不過它不只是會鎖住當前行記錄的本身,還會鎖定一個範圍。比如上面幻讀的例子,開始查詢0<閱讀量<100的文章時,只查到了一個結果。`next-key lock`會將查詢出的這一行進行鎖定,同時還會對0<閱讀量<100這個範圍進行加鎖,這實際上是一種間隙鎖。間隙鎖能夠防止其他事務在這個間隙修改或者插入記錄。這樣一來,就保證了在0<閱讀量<100這個間隙中,只存在原來的一行資料,從而避免了幻讀。 > 間隙鎖:封鎖索引記錄中的間隔 雖然InnoDB使用`next-key lock`能夠避免幻讀問題,但卻並不是真正的可序列化隔離。再來看一個例子吧。 ![](https://imgkr.cn-bj.ufileos.com/bf6113ed-9532-4601-96e4-65f677e0677f.png) 首先提一個問題,在T6事務A提交事務之後,猜一猜文章A和文章B的閱讀量為多少? 答案是,文章AB的閱讀量都被修改成了10000。這代表著事務B的提交實際上對事務A的執行產生了影響,表明兩個事務之間並不是完全隔離的。雖然能夠避免幻讀現象,但是卻沒有達到可序列化的級別。 這還說明,避免髒讀、不可重複讀和幻讀,是達到可序列化的隔離級別的必要不充分條件。可序列化是都能夠避免髒讀、不可重複讀和幻讀,但是避免髒讀、不可重複讀和幻讀卻不一定達到了可序列化。 #### 一致性 一致性是指事務執行結束後,資料庫的完整性約束沒有被破壞,事務執行的前後都是合法的資料狀態。 一致性是事務追求的最終目標:前面提到的原子性、永續性和隔離性,都是為了保證資料庫狀態的一致性。 這就不多說了吧。你細品。 ## 4 MySQL日誌系統 瞭解完MySQL的基本架構,大體上能夠對MySQL的執行流程有了比較清晰的認知。接下來我將在講述MySQL事務之前,先為大家介紹以下日誌系統,以方便之後更好的理解事務的特性和實現。 MySQL日誌系統是資料庫的重要元件,用於記錄資料庫的更新和修改。若資料庫發生故障,可通過不同日誌記錄恢復資料庫的原來資料。因此實際上日誌系統直接決定著MySQL執行的魯棒性和穩健性。 MySQL的日誌有很多種,如二進位制日誌(binlog)、錯誤日誌、查詢日誌、慢查詢日誌等,此外InnoDB儲存引擎還提供了兩種日誌:redo log(重做日誌)和undo log(回滾日誌)。這裡將重點針對InnoDB引擎,對重做日誌、回滾日誌和二進位制日誌這三種進行分析。 #### 重做日誌(redo log) 重做日誌(redo log)是InnoDB引擎層的日誌,用來記錄事務操作引起資料的變化,記錄的是資料頁的物理修改。 重做日記的作用其實很好理解,我打個比方。資料庫中資料的修改就好比你寫的論文,萬一哪天論文丟了怎麼呢?以防這種不幸的發生,我們可以在寫論文的時候,每一次修改都拿個小本本記錄一下,記錄什麼時間對某一頁進行了怎麼樣的修改。這就是重做日誌。 InnoDB引擎對資料的更新,是先將更新記錄寫入redo log日誌,然後會在系統空閒的時候或者是按照設定的更新策略再將日誌中的內容更新到磁碟之中。這就是所謂的**預寫式技術(Write Ahead logging)**。這種技術可以大大減少IO操作的頻率,提升資料重新整理的效率。 **髒資料刷盤** 值得注意的是,redo log日誌的大小是固定的,為了能夠持續不斷的對更新記錄進行寫入,在redo log日誌中設定了兩個標誌位置,`checkpoint`和`write_pos`,分別表示記錄擦除的位置和記錄寫入的位置。redo log日誌的資料寫入示意圖可見下圖。 ![](https://imgkr.cn-bj.ufileos.com/a2dc4d16-b6ac-495f-8266-35174cadcbe0.png) 當`write_pos`標誌到了日誌結尾時,會從結尾跳至日誌頭部進行重新迴圈寫入。所以redo log的邏輯結構並不是線性的,而是可看作一個圓周運動。`write_pos`與`checkpoint`中間的空間可用於寫入新資料,寫入和擦除都是往後推移,迴圈往復的。 ![](https://imgkr.cn-bj.ufileos.com/78ea1bd7-a5b6-4163-a433-9ffd90e376ea.png) 當`write_pos`追上`checkpoint`時,表示redo log日誌已經寫滿。這時不能繼續執行新的資料庫更新語句,需要停下來先刪除一些記錄,執行`checkpoint`規則騰出可寫空間。 > checkpoint規則:checkpoint觸發後,將buffer中髒資料頁和髒日誌頁都刷到磁碟。 > 髒資料:指記憶體中未刷到磁碟的資料。 redo log中最重要的概念就是緩衝池`buffer pool`,這是在記憶體中分配的一個區域,包含了磁碟中部分資料頁的對映,作為訪問資料庫的緩衝。 > 當請求讀取資料時,會先判斷是否在緩衝池命中,如果未命中才會在磁碟上進行檢索後放入緩衝池; > 當請求寫入資料時,會先寫入緩衝池,緩衝池中修改的資料會定期重新整理到磁碟中。這一過程也被稱之為**刷髒** 。 因此,當資料修改時,除了修改`buffer pool`中的資料,還會在redo log中記錄這次操作;當事務提交時,會根據redo log的記錄對資料進行刷盤。如果MySQL宕機,重啟時可以讀取redo log中的資料,對資料庫進行恢復,從而保證了事務的永續性,使得資料庫獲得`crash-safe`能力。 **髒日誌刷盤** 除了上面提到的對於髒資料的刷盤,實際上redo log日誌在記錄時,為了保證日誌檔案的持久化,也需要經歷將日誌記錄從記憶體寫入到磁碟的過程。redo log日誌可分為兩個部分,一是存在易失性記憶體中的快取日誌`redo log buff`,二是儲存在磁碟上的redo log日誌檔案`redo log file`。 為了確保每次記錄都能夠寫入到磁碟中的日誌中,每次將`redo log buffer`中的日誌寫入`redo log file`的過程中都會呼叫一次作業系統的`fsync`操作。 > fsync函式:包含在UNIX系統標頭檔案#