天天看點

MySQL5.7 核心技術揭秘:MySQL Group Commit

一、大綱

  • 一階段送出
  • 二階段送出
  • 三階段送出
  • 組送出總結

二、一階段送出

2.1 什麼是一階段送出

先了解下含義,其實官方并沒有定義啥是一階段,這裡隻是我為了上下文和好了解,自己定義的一階段commit流程。

好了,這裡的一階段,其實是針對MySQL沒有開啟binlog為前提的,因為沒有binlog,是以MySQL commit的時候就相對簡單很多。

MySQL5.7 核心技術揭秘:MySQL Group Commit

解釋幾個概念:

  • execution state做什麼事情呢
在記憶體修改相關資料,比如:DML的修改           
  • prepare stage做什麼事情呢
1. write() redo  日志
    1.1 最重要的操作,記住這個時候就開始重新整理redo了(依賴作業系統sync到磁盤),很多同學在這個地方都不太清楚,以為flush redo在最後commit階段才開始
    1.2 這一步可以進行多事務的prepare,也就意味着可以多redo的flush,sync到磁盤,這裡是redo的組送出. 在此說明MySQL5.6+ redo是可以進行組送出的,之後我們讨論的重點是binlog,就不在提及redo的組送出了
2. 更新undo狀态
3. 等等
           
  • innodb commit stage做什麼事情呢
1. 更新undo的狀态
2. fsync redo & undo(強制sync到磁盤)
3. 寫入最終的commit log,代表事務結束
4. 等等           

由于這裡面隻對應到redo日志,是以我們稱之為一階段commit

2.2 為什麼要有一階段送出

一階段送出,主要是為了crash safe。

  • 如果在 execution stage mysql crash
當MySQL重新開機後,因為沒有記錄redo,此事務復原
           
當MySQL重新開機後,因為沒有記錄redo,此事務復原
           
  • 如果 prepare stage
1. redo log write()了,但是還沒有fsync()到磁盤前,mysqld crash了
    此時:事務復原

2. redo log write()了,fsync()也落盤了,mysqld crash了
    此時:事務還是復原
           
  • 如果 commit stage
1. commit log fsync到磁盤了
    此時:事務送出成功,否則事務復原
           

2.3 一階段送出的弊端

缺點也很明顯:

  • 缺點一
1. 為什麼redo fsync到磁盤後,還是要復原呢?
           
  • 缺點二
1. 沒有開啟binlog,性能非常高,但是binlog是用來搭建slave的,否則就是單節點,不适合生産環境
           

三、二階段送出

3.1 什麼是二階段送出

MySQL5.7 核心技術揭秘:MySQL Group Commit

繼續解釋幾個概念:

在記憶體修改相關資料,比如:DML的修改           
1. write() redo  日志  --最重要的操作,記住這個時候就開始重新整理redo了(依賴作業系統sync到磁盤),很多同學在這個地方都不太清楚,以為flush redo在最後commit階段才開始
2. 更新undo狀态
3. 等等
           
  • binlog stage做什麼事情呢
1. write binlog
    flush binlog 記憶體日志到磁盤緩存
2. fsync binlog
    sync磁盤緩存的binlog日志到磁盤持久化           
1. 更新undo的狀态
2. fsync redo & undo(強制sync到磁盤)
3. 寫入最終的commit log,代表事務結束
4. 等等           

由于這裡的流程中包含了binlog和redo日志重新整理的協調一緻性,我們稱之為二階段

3.2 為什麼要有二階段送出

當binlog開啟的情況下,我們需要引入另一套流程來保證redo和binlog的一緻性 , 以及crash safe,是以我們用這套二階段來實作

  • 在prepare階段,如果mysqld crash,由于事務未寫入binlog且innodb 存儲引擎未送出,是以将該事務復原掉
  • 當binlog階段
1. binlog flush 到磁盤緩存,但是沒有永久fsync到磁盤
    如果mysqld crash,此事務復原

2. binlog永久fsync到磁盤,但是innodb commit log還未送出
    如果mysqld crash,MySQL 進行recover,從binlog的xid提取送出的事務進行重做并commit,來保證binlog和redo保持一緻
           
  • 當commit階段
如果innodb commit log已經送出,事務成功結束
           

那為什麼要保證redo和binlog的一緻性呢?

  • 實體熱備的問題
  1. 多事務中,如果無法保證多事務的redo和binlog一緻性,則會有如下問題
commit送出階段包含的事情:
    1. prepare
    2. write binlog  & fsync binlog
    3. commit


T1 (---prepare-----write 100[pos1]-------fsync 100--------------------------------------online-backup[pos3:因為熱備取的是最近的送出事務位置]-------commit)

T2 (------prepare------write 200[pos2]---------fsync 200------commit)

T3 (-----------prepare-------write 300[pos3]--------fsync 300--------commit)


解析:
    事務的開始順序: T1 -》T2 -》T3
    事務的送出結束順序:  T2 -》T3 -》T1
    binlog的寫入順序: T1 -》 T2 -》T3

結論:
    T2 , T3 引擎層送出結束,T1 fsync binlog 100 也已經結束,但是T1引擎成沒有送出成功,是以這時候online-backup記錄的binlog位置是pos3(也就是T3送出後的位置)
    如果拿着備份重新恢複slave,由于熱備是不會備份binlog的,是以事務T1會復原掉,那麼change master to pos3的時候,因為T1的位置是pos1(在pos3之前),是以T1事務被slave完美的漏掉了           
  1. 多事務中,可以通過三階段送出(下面一節講)保證redo和binlog的一緻性,則備份無問題. 接下來看一個多事務中,事務日志和binlog日志一緻的情況
commit送出階段包含的事情:
    1. prepare
    2. write binlog  & fsync binlog
    3. commit


T1 (---prepare-----write 100[pos1]-------fsync 100-------------commit)

T2 (------prepare------write 200[pos2]---------fsync 200----------------online-backup[pos2:因為熱備取的是最近的送出事務位置]---commit)

T3 (-----------prepare-------write 300[pos3]--------fsync 300----------------------------------------------------------------------------commit)


解析:
    事務的開始順序: T1 -》T2 -》T3
    事務的送出結束順序:  T1 -》T2 -》T3
    binlog的寫入順序: T1 -》 T2 -》T3
    ps:以上的事務和binlog完全按照順序一緻運作

結論:
    T1 引擎層送出結束,T2 fsync binlog 200 也已經結束,但是T2引擎成沒有送出成功,是以這時候online-backup記錄的binlog位置是pos1(也就是T1送出後的位置)
    如果拿着備份重新恢複slave,由于熱備是不會備份binlog的,是以事務T2會復原掉,那麼change master to pos1的時候,因為T1的位置是pos1(在pos2之前),是以T2、T3事務會被重做,最終保持一緻
           

總結:

以上的問題,主要原因是熱備份工具無法備份binlog導緻的根據備份恢複的slave復原導緻的,産生這樣的原因最後還是要歸結于最後引擎層的日志沒有送出導緻
是以,xtrabackup意識到了這一點,最後多了這一步flush no_write_to_binlog engine logs,表示将innodb層的redo全部持久化到磁盤後再進行備份,在通俗的說,就是圖例上的T2一定成功後,才會再繼續進行拷貝備份
那麼如果是這樣,圖例上的T2在恢複的時候,就不會被復原了,是以就自然不會丢失事務啦
           
  • 主從資料不一緻問題

如果redo和binlog不是一緻的,那麼有可能就是master執行事務的順序和slave執行事務順序不一樣,那麼不一樣會導緻什麼問題呢?

在一些依賴事務順序的場景,尤其重要,比如我們看一個例子

master節點送出T1和T2事務按照以下順序

1.  State0: x= 1, y= 1  --初始值

2.  T1: { x:= Read(y);

3.          x:= x+1;

4.          Write(x);

5.          Commit; }



State1: x= 2, y= 1

7.  T2: { y:= Read(x);

8.            y:=y+1;

9.           Write(y);

10.          Commit; }

State2: x= 2, y= 3



以上兩個事務順序在master為 T1 -> T2
最終結果為
    State1: x= 2, y= 1
    State2: x= 2, y= 3           

如果slave的事務執行順序與master相反,會怎麼樣呢?

1.  State0: x= 1, y= 1 --初始值

2.  T2: { y:= Read(x);

3.            y:= y+1;

4.            Write(y);

5.            Commit; }

6.
State1: x= 1, y= 2

7.  T1: { x:= Read(y);

8.            x:=x+1;

9.            Write(x);

10.           Commit; }

11.
State2: x= 3, y= 2

以上兩個事務順序在master為 T2 -> T1
最終結果為
    State1: x= 1, y= 2
    State2: x= 3, y= 2
           

結論:

  1. 為了保證主備資料一緻性,slave節點必須按照同樣的順序執行,如果順序不一緻容易造成主備庫資料不一緻的風險。
  2. 而redo 和 binlog的一緻性,在單線程複制下是master和slave資料一緻性的另一個保證, 多線程複制需要依賴MTS的設定
  3. 是以,MySQL必須要保證redo 和 binlog的一緻性,也就是:引擎層送出的順序和server層binlog fsync的順序必須一緻,那麼二階段送出就是這樣的機制

3.3 二階段送出的弊端

二階段送出能夠保證同一個事務的redo和binlog的順序一緻性問題,但是無法解決多個事務送出順序一緻性的問題

四、三階段送出

4.1 什麼是三階段送出

MySQL5.7 核心技術揭秘:MySQL Group Commit
在記憶體修改相關資料,比如:DML的修改           
1. write() redo  日志  --最重要的操作,記住這個時候就開始重新整理redo了(依賴作業系統sync到磁盤),很多同學在這個地方都不太清楚,以為flush redo在最後commit階段才開始
2. 更新undo狀态
3. 等等
           
1. write binlog  --一組有序的binlog
    flush binlog 記憶體日志到磁盤緩存
2. fsync binlog  --一組有序的binlog
    sync磁盤緩存的binlog日志到磁盤持久化           
1. 更新undo的狀态
2. fsync redo & undo(強制sync到磁盤)
3. 寫入最終的commit log,代表事務結束  --一組有序的commit日志,按序送出
4. 等等           

這裡将整個事務送出的過程分為了三個大階段

InnoDB, Prepare
    SQL已經成功執行并生成了相應的redo日志
Binlog, Flush Stage(group)  -- 一階段
    寫入binlog緩存;
Binlog, Sync Stage(group)  -- 二階段
    binlog緩存将sync到磁盤
InnoDB, Commit stage(group) -- 三階段
    leader根據順序調用存儲引擎送出事務;


重要參數:

binlog_group_commit_sync_delay=N : 等待N us後,開始刷盤binlog
binlog_group_commit_sync_no_delay_count=N : 如果隊列的事務數達到N個後,就開始刷盤binlog
           

4.2 為什麼要有三階段送出

目的就是保證多事務之間的redo和binlog順序一緻性問題, 以及加入組送出機制,讓redo和binlog都可以以組的形式(有序集合)進行fsync來提高并發性能

4.3 再來聊聊MySQL組送出

隊列相關

MySQL5.7 核心技術揭秘:MySQL Group Commit

組送出舉例

MySQL5.7 核心技術揭秘:MySQL Group Commit

(一)、T1事務第一個進入第一階段 FLUSH , 由于是第一個,是以是leader,然後再等待(按照具體算法)

(二)、T2事務第二個進行第一階段 FLUSH , 由于是第二個,是以是follower,然後等待leader排程

(三)、FLUSH隊列等待結束後,開始進入下一階段SYNC階段,此時T1帶領T2進行一次fsync操作,之後進入commit階段,按序送出完成,這就是一次組送出的簡要過程了

(四)、prepare可以并行,說明兩個事務沒有任何沖突。有沖突的prepare無法進行進入同一隊列

(五)、每個隊列之間都是可以并行運作的

五、總結

  • 組送出的核心思想就是:一次fsync()調用,可以重新整理N個事務的redo log(redo的組送出) & binlog(binlog的組送出)
  • 組送出的最終目的就是為了減少IO的頻繁刷盤,來提高并發性能,當然也是之後多線程複制的基礎
  • 組送出中:sync_binlog=1 & innodb_trx_at_commit=1 代表的不再是1個事務,而是一組事務和一個事務組的binlog
  • 組送出中:binlog是順序fsync的,事務也是按照順序進行送出的,這都是有序的,MySQL5.7 并對這些有序的事務進行打好标記(last_committed,sequence_number )

六、思考問題

  • 如何保證slave執行的同一組binlog的事務順序跟master的一緻
    如果slave上同一組事務中的後面的事務先執行,那麼slave的gtid該如何表示
    如何保證slave上同一組事務中的事務是按照順序執行的           
  • 如果slave突然挂了,那麼執行到一半的一組事務,是全部復原?還是部分復原?
    如果是部分復原,那麼如何知道哪些復原了,哪些沒有復原,mysql如何自動修複掉復原的那部分事務