本文來自:http://baiyangtx.net/2016/09/04/mydumper-principle/
相對於MySQL官方提供的邏輯備份工具 mysqldump , mydumper最大的特點就是可以采用多線程並行備份,大大提高了數據導出的速度。這里對mydumper的工作原理做個分析,看一下mydumper如何巧妙的利用Innodb引擎提供的MVCC版本控制的功能,實現多線程並發獲取一致性數據。
這里一致性數據指的是在某個時間點,導出的數據與導出的Binlog文件信息相匹配,如果導出了多張表的數據,這些不同表之間的數據都是同一個時間點的數據。
在mydumper進行備份的時候,由一個主線程以及多個備份線程完成。其主線程的流程是:
- 連接數據庫
- FLUSH TABLES WITH READ LOCK 將臟頁刷新到磁盤並獲得只讀鎖
- START TRANSACTION /!40108 WITH CONSISTENT SNAPSHOT / 開啟事物並獲取一致性快照
- SHOW MASTER STATUS 獲得binlog信息
- 創建子線程並連接數據庫
- 為子線程分配任務並push到隊列中
- UNLOCK TABLES / FTWRL / 釋放鎖
子線程的主要流程是:
- 連接數據庫
- SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE
- START TRANSACTION /!40108 WITH CONSISTENT SNAPSHOT /
- 從隊列中pop任務並執行
上述兩個線程的流程的關系如圖

從圖中可以看到,主線程釋放鎖是在子線程開啟事物之后。這里是保證子線程獲得的數據一定為一致性數據的關鍵。
主線程在連接到數據庫后立即通過Flush tables with read lock(FTWRL) 操作將臟頁刷新到磁盤,並獲取一個全局的只讀鎖,這樣便可以保證在鎖釋放之前由主線程看到的數據是一致的。然后立即通過 Start Transaction with consistent snapshot 創建一個快照讀事物,並通過 show master status獲取binlog位置信息。
然后創建完成dump任務的子線程並為其分配任務。
主線程在創建子線程后通過一個異步消息隊列 ready 等待子線程准備完畢。 子線程在創建后立即創建到MySQL數據庫的連接,然后設置當前事務隔離級別為Repeatable Read。
設置完成之后開始快照讀事務。在完成這一系列操作之后,子線程才會通過ready隊列告訴主線自己程准備完畢。主線程等待全部子線程准備完畢開啟一致性讀Snapshot事務后才會釋放全局只讀鎖(Unlock Table)。
如果只有Innodb表,那么只有在創建任務階段會加鎖。但是如果存在MyIsam表或其他不帶有MVCC功能的表,那么在這些表的導出任務完成之前都必須對這些表進行加鎖。Mydumper本身維護了一個 non_innodb_table 列表,在創建任務階段會首先為非Innodb表創建任務。同時還維護了一個全局的unlock_table隊列以及一個原子計數器 non_innodb_table_counter , 子線程每完成一個非Innodb表的任務便將 non_innodb_table_counter 減一,如果non_innodb_table_counter 值為0 遍通過向 unlock_table 隊列push一個消息的方式通知主線程完成了非Innodb表的導出任務可以執行 unlock table操作。
mydumper支持記錄級別的並發導出。在記錄級別的導出時,主線程在做任務分配的時候會對表進行拆分,為表的一部分記錄創建一個任務。這樣做一個好處就是當有某個表特別大的時候可以盡可能的利用多線程並發以免某個線程在導出一個大表而其他線程處於空閑狀態。在分割時,首先選取主鍵(PRIMARY KEY)作為分隔依據,如果沒有主鍵則查找有無唯一索引(UNIQUE KEY)。在以上嘗試都失敗后,再選取一個區分度比較高的字段做為記錄划分的依據(通過 show index 結果集中的cardinality的值確定)。
划分的方式比較暴力,直接通過 select min(filed),max(filed) from table 獲得划分字段的取值范圍,通過 explain select filed from table 獲取字段記錄的行數,然后通過一個確定的步長獲得每一個子任務的執行時的where條件。這種計算方式只支持數字類型的字段。
以上就是mydumper的並發獲取一致性數據的方式,其關鍵在於利用了Innodb表的MVCC功能,可以通過快照讀因此只有在任務創建階段才需要加鎖。
