環境說明:
以下討論的前提 是設置MySQL的crash safe相關參數為雙1。
sync_Binlog=1
:MySQL 每次在提交事務前會將二進制日志同步到磁盤上,保證在服務器崩潰時不會丟失事務。innodb_flush_log_at_trx_commit=1
:每次COMMIT
后立即刷新同步數據到硬盤。
相關知識概述:
1 WAL機制(Write Ahead Log)
WAL指的是對數據文件進行修改前,必須將修改先記錄日志。MySQL為了保證ACID中的一致性和持久性,使用了WAL。
2 Redo log
Redo log就是一種WAL的應用。當數據庫忽然掉電,再重新啟動時,MySQL可以通過Redo log還原數據。也就是說,每次事務提交時,不用同步刷新磁盤數據文件,只需要同步刷新Redo log就足夠了。相比寫數據文件時的隨機IO,寫Redo log時的順序IO能夠提高事務提交速度。
3 組提交(Group Commit)
3.1 在沒有開啟Binlog時
Redo log的刷盤操作將會是最終影響MySQL TPS的瓶頸所在。為了緩解這一問題,MySQL使用了組提交,將多個刷盤操作合並成一個,如果說10個事務依次排隊刷盤的時間成本是10,那么將這10個事務一次性一起刷盤的時間成本則近似於1。
3.2 當開啟Binlog時
為了保證Redo log和Binlog的數據一致性,MySQL使用了二階段提交,由Binlog作為事務的協調者。而引入“二階段提交”使得Binlog又成為了性能瓶頸,先前的“Redo log組提交”也成了擺設。為了再次緩解這一問題,MySQL增加了“Binlog組提交”機制,目的同樣是將Binlog的多個刷盤操作合並成一個,結合Redo log本身已經實現的“組提交”,分為三個階段“Flush階段”、“Sync階段”、“Commit階段”完成“Binlog組提交”,最大化每次刷盤的收益,弱化磁盤瓶頸,提高性能。
4 事務二階段提交
參見《MySQL-5.7事務二階段提交機制.md》
Binlog組提交原理:
1 概述
注意:在MySQL中每個階段都有一個隊列,每個隊列都有一把鎖保護,第一個進入隊列的事務會成為leader,leader領導所在隊列的所有事務,全權負責整隊的操作,完成后通知隊內其他事務操作結束。
2 階段描述
2.1 Flush階段
- 首先獲取隊列中的事務組;
- 將Redo log中prepare階段的數據刷盤(圖3中Flush Redo log步驟);
- 將Binlog數據寫入文件,當然此時只是寫入文件系統的緩沖,並不能保證數據庫崩潰時Binlog不丟失 (圖4中Write Binlog步驟);
- Flush階段隊列的作用是用於支撐Redo log的組提交;
- 如果在這一步完成后數據庫崩潰,由於協調者Binlog中不保證有該組事務的記錄,所以MySQL可能會在重啟后回滾該組事務。
2.2 Sync階段
- 這里為了增加一組事務中的事務數量,提高刷盤收益,MySQL使用兩個參數控制獲取隊列事務組的時機,分別如下。
Binlog_group_commit_sync_delay=N
:在等待N μs后,開始事務刷盤(圖3中Sync Binlog步驟)Binlog_group_commit_sync_no_delay_count=N
:如果隊列中的事務數達到N個,就忽視Binlog_group_commit_sync_delay
的設置,直接開始刷盤(圖4中Sync Binlog步驟)
- Sync階段隊列的作用是用於支持Binlog的組提交;
- 如果在這一步完成后數據庫崩潰,由於協調者Binlog中已經有了事務記錄,MySQL會在重啟后通過Flush 階段中Redo log刷盤的數據繼續進行事務的提交。
2.3 Commit階段
- 首先獲取隊列中的事務組;
- 依次將Redo log中已經prepare的事務在引擎層提交(圖1中InnoDB Commit步驟)
- Commit階段不用刷盤,如上所述,Flush階段中的Redo log刷盤已經足夠保證數據庫崩潰時的數據安全了
- Commit階段隊列的作用是承接Sync階段的事務,完成最后的引擎提交,使得Sync可以盡早的處理下一組事務,最大化組提交的效率
組提交在Binlog上的表現:
單純通過SHOW BINLOG EVENTS
無法發現有關組提交的任何信息,但是通過mysqlbinlog
工具,便可以發現組提交的內部信息,類似如下。
[root]# mysqlbinlog mysql-bin.0000006 | grep last_committed
#150520 14:23:11 server id 88 end_log_pos 259 CRC32 0x4ead9ad6 GTID last_committed=0 sequence_number=1
#150520 14:23:11 server id 88 end_log_pos 1483 CRC32 0xdf94bc85 GTID last_committed=0 sequence_number=2
#150520 14:23:11 server id 88 end_log_pos 2708 CRC32 0x0914697b GTID last_committed=0 sequence_number=3
#150520 14:23:11 server id 88 end_log_pos 3934 CRC32 0xd9cb4a43 GTID last_committed=0 sequence_number=4
#150520 14:23:11 server id 88 end_log_pos 5159 CRC32 0x06a6f531 GTID last_committed=0 sequence_number=5
#150520 14:23:11 server id 88 end_log_pos 6386 CRC32 0xd6cae930 GTID last_committed=0 sequence_number=6
#150520 14:23:11 server id 88 end_log_pos 7610 CRC32 0xa1ea531c GTID last_committed=6 sequence_number=7
#150520 14:23:11 server id 88 end_log_pos 8834 CRC32 0x96864e6b GTID last_committed=6 sequence_number=8
#150520 14:23:11 server id 88 end_log_pos 10057 CRC32 0x2de1ae55 GTID last_committed=6 sequence_number=9
#150520 14:23:11 server id 88 end_log_pos 11280 CRC32 0x5eb13091 GTID last_committed=6 sequence_number=10
#150520 14:23:11 server id 88 end_log_pos 12504 CRC32 0x16721011 GTID last_committed=6 sequence_number=11
#150520 14:23:11 server id 88 end_log_pos 13727 CRC32 0xe2210ab6 GTID last_committed=6 sequence_number=12
#150520 14:23:11 server id 88 end_log_pos 14952 CRC32 0xf41181d3 GTID last_committed=12 sequence_number=13
...
可以發現MySQL 5.7二進制日志較之原來的二進制日志內容多了last_committed和sequence_number這兩項內容。這兩個值即所謂的“邏輯時間戳標記(Logical Clock)”,可以用於控制多線程復制(MTS)特性。
- sequence_number:該值隨着事務順序增長,每個事務對應一個序列號。該值在事務二階段提交的Prepare階段被記錄存儲,用於標記最新提交的事務。
- last_committed:表示事務提交的時候,上次事務提交的序列號(sequence_number),如果事務具有相同的last_committed,則表示這些事務都在一組內。該值在事務二階段提交的Commit階段被記錄存儲。
參考資料:
-
《[原理解析] MySQL組提交(group commit)》
https://mp.weixin.qq.com/s/_LK8bdHPw9bZ9W1b3i5UZA -
《MySQL 5.7新特性:並行復制原理(MTS)》
https://blog.csdn.net/andong154564667/article/details/82117727