為什么要兩階段提交
事務提交之后,redo log和bin log 都是需要1持久化到磁盤中,但是這兩個是獨立的邏輯,可能出現半成功的狀態,這樣就造成兩份日志之間的邏輯不一致。如:
以id=1,name = ‘小明’執行
update stu set name = '小紅' ?where id = 1?
會出現如下兩種情況:
- 如果bin log寫入了磁盤之后,,Mysql突然宕機了,而redo log還沒來得及寫入。由于redo log沒有寫入數據,重啟之后主庫不存在這條數據,但是從庫已經同步了這條數據就會出現,主從不一致的情況
- 如果在將redo log刷入到磁盤之后,Mysql突然宕機了,而bin log還沒來得及寫入。Mysql重啟后,,通過redo log能將Buffer Pool中id = 1這行數據的name字段更新到‘小紅’但是bin log由于沒有寫入,所以從庫中的數據還是‘小明’,出現了主從不一致的情況
兩階段提交把單個事務提交分為兩個階段,分別是“準備”和“提交”階段
兩階段提交的過程是怎么樣的
當客戶端執行commit語句或者在自動提交的情況下,Mysql內部開啟一個XA事務,分為兩個階段來完成XA事務的提交
????????
事務提交的過程分為兩個階段,就是將redo log的寫入拆分為兩個步驟:prepare和commit,中間在穿插寫入bin log,具體步驟如下:
- prepare階段:將XID(內部XA事務的ID)寫入到redo log,,同時將redo log對應的事務狀態設置為prepare,然后將redo log持久化到磁盤
-
commit階段:將XID寫入到bin log然后將bin log持久化到磁盤,接著提交事務,將redo log狀態設置為commit,此時該狀態并不需要持久化到磁盤,只需要寫入到文件系統的緩存頁中就可以,因為只要bin log寫到磁盤中成功,就算redo log狀態還是prepare也沒有關系,一樣會被認為事務已經執行成功
異常重啟會發送什么現象呢
不管是時刻A(redo log寫入磁盤,bin log沒有)出現問題還是時刻B(redo log和bin log都寫入到磁盤,但是還沒有寫入commit標識)崩潰,此時得redo log都是處于prepare的狀態
在Mysql重啟之后,都會按照順序掃描redo log文件,碰到處于prepare狀態的redo log,就會拿著redo log中的XID去bin log中去查找是否存在這個XID:
- 如果bin log中沒有當前內部XA事務的XID,說明redo log完成了刷盤,但是bin log還沒有刷盤,則回滾事務,對應時刻A
- 如果bin log中有當前內部事務的XID,說明redo log和bin log都已經完成了刷盤,則提交事務,對應時刻B
可以看到,對于處于prepare階段的redo log,既可以提交事務,也可以回滾事務,這取決于能否在bin log中查找到與redo log相同的XID,如果有就提交事務,沒有就回滾事務。這樣就可以保證redo log和 bin log的一致性
兩階段提交出現的問題
兩個階段雖然保證了兩個日志文件的數據一致性,但是性能不高,主要的原因是:
- 磁盤I/O次數過高:每一個事務提交都是需要兩次刷盤(redo和bin)
- 鎖競爭激烈:兩個階段雖然可以保證“單個事務”的兩個日志一致,但是不能保證“多個事務“情況下,兩者提交的順序一致性。因此,在兩個階段提交流程的基礎上,還需要加上一個鎖來保證提交的原子性,從而保證多事務的情況下,兩個日志提交的順序一致
為了解決上訴問題,引用有一個新的技術”組提交“,當多個事務提交的時候,會將多個bin log刷盤操作合并成一個,從而減少磁盤I/O的次數。如果10個事務一次排隊刷盤的時間成本是10,那么將著10個事務一次性一起刷盤的時間成本近似.
引入組提交及之后,prepare階段不變,只針對于commit階段,將commit階段拆分為三個過程:
- flush階段:多個事務按照將進入的順序將bin log從cache寫入文件(不刷盤)
- sync階段:對于bin log文件進行fsyn操作(多個事務的bin log合并有一次刷盤)
- commit:各個事務按照順序做InnoDB的提交操作
上面的每一個階段都有一個隊列,每個階段都有鎖進行保證事務寫入的順序性,第一個進入隊列的事務會成為 leader,leader領導所在隊列的所有事務,全權負責整隊的操作,完成后通知隊內其他事務操作結束
每個階段引入隊列,鎖只是針對隊列進行保護,不會鎖住整個提交事務的整個過程。鎖的粒度減小了,這樣就可以多個階段并發執行,提升效率
有 binlog 組提交,那有 redo log 組提交嗎?
這個要看 MySQL 版本,MySQL 5.6 沒有 redo log 組提交,MySQL 5.7 有 redo log 組提交
在 MySQL 5.6 的組提交邏輯中,每個事務各自執行 prepare 階段,也就是各自將 redo log 刷盤,這樣就沒辦法對 redo log 進行組提交。
所以在 MySQL 5.7 版本中,做了個改進,在 prepare 階段不再讓事務各自執行 redo log 刷盤操作,而是推遲到組提交的 flush 階段,也就是說 prepare 階段融合在了 flush 階段。
這個優化是將 redo log 的刷盤延遲到了 fush 階段之中,sync 階段之前。通過延遲寫 redo log 的方式為 redolog 做了一次組寫入,這樣 binlog 和 redo log 都進行了優化。