1. 程式人生 > >gh-ost 原理剖析

gh-ost 原理剖析

gh-ost 原理

一 簡介

上一篇文章介紹 gh-ost 引數和具體的使用方法,以及核心特性-可動態調整 暫停,動態修改引數等等。本文分幾部分從原始碼方面解釋gh-ost的執行過程,資料遷移,切換細節設計。

二 原理

2.1 執行過程

本例基於在主庫上執行ddl 記錄的核心過程。核心程式碼在

github.com/github/gh-ost/go/logic/migrator.go 的Migrate()

func (this *Migrator) Migrate() //Migrate executes the complete migration logic. This is the major gh-ost function.

1 檢查資料庫例項的基礎資訊

a 測試db是否可連通,
b 許可權驗證 
  show grants for current_user()
c 獲取binlog相關資訊,包括row格式和修改binlog格式後的重啟replicate
  select @@global.log_bin, @@global.binlog_format
  select @@global.binlog_row_image
d 原表儲存引擎是否是innodb,檢查表相關的外來鍵,是否有觸發器,行數預估等操作,需要注意的是行數預估有兩種方式  一個是通過explain 讀執行計劃 另外一個是select count(*) from table ,遇到幾百G的大表,後者一定非常慢。

  explain select /* gh-ost */ * from `test`.`b` where 1=1

2 模擬slave,獲取當前的位點資訊,建立binlog streamer監聽binlog

2019-09-08T22:01:20.944172+08:00    17760 Query show /* gh-ost readCurrentBinlogCoordinates */ master status
2019-09-08T22:01:20.947238+08:00    17762 Connect   [email protected] on  using TCP/IP
2019-09-08T22:01:20.947349+08:00    17762 Query SHOW GLOBAL VARIABLES LIKE 'BINLOG_CHECKSUM'
2019-09-08T22:01:20.947909+08:00    17762 Query SET @master_binlog_checksum='NONE'
2019-09-08T22:01:20.948065+08:00    17762 Binlog Dump   Log: 'mysql-bin.000005'  Pos: 795282

**3 建立 日誌記錄表 xx_ghc 和影子表 xx_gho 並且執行alter語句將影子表 變更為目標表結構。如下日誌記錄了該過程,gh-ost會將核心步驟記錄到 _b_ghc 中。**

2019-09-08T22:01:20.954866+08:00    17760 Query create /* gh-ost */ table `test`.`_b_ghc` (
            id bigint auto_increment,
            last_update timestamp not null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            hint varchar(64) charset ascii not null,
            value varchar(4096) charset ascii not null,
            primary key(id),
            unique key hint_uidx(hint)
        ) auto_increment=256
2019-09-08T22:01:20.957550+08:00    17760 Query create /* gh-ost */ table `test`.`_b_gho` like `test`.`b`
2019-09-08T22:01:20.960110+08:00    17760 Query alter /* gh-ost */ table `test`.`_b_gho` engine=innodb
2019-09-08T22:01:20.966740+08:00    17760 Query 
   insert /* gh-ost */ into `test`.`_b_ghc`(id, hint, value)values (NULLIF(2, 0), 'state', 'GhostTableMigrated') on duplicate key update last_update=NOW(),value=VALUES(value)

4 insert into xx_gho select * from xx 拷貝資料

獲取當前的最大主鍵和最小主鍵 然後根據命令列傳參 chunk 獲取資料 insert到影子表裡面

獲取最小主鍵 select `id` from `test`.`b` order by `id` asc limit 1;
獲取最大主鍵 soelect `id` from `test`.`b` order by `id` desc limit 1;
獲取第一個 chunk:
select  /* gh-ost `test`.`b` iteration:0 */ `id` from `test`.`b` where ((`id` > _binary'1') or ((`id` = _binary'1'))) and ((`id` < _binary'21') or ((`id` = _binary'21'))) order by `id` asc limit 1 offset 999;

迴圈插入到目標表:
insert /* gh-ost `test`.`b` */ ignore into `test`.`_b_gho` (`id`, `sid`, `name`, `score`, `x`) (select `id`, `sid`, `name`, `score`, `x` from `test`.`b` force index (`PRIMARY`)  where (((`id` > _binary'1') or ((`id` = _binary'1'))) and ((`id` < _binary'21') or ((`id` = _binary'21')))) lock in share mode;

迴圈到最大的id,之後依賴binlog 增量同步     

需要注意的是

rowcopy過程中是對原表加上 lock in share mode,防止資料在copy的過程中被修改。這點對後續理解整體的資料遷移非常重要。因為gh-ost在copy的過程中不會修改這部分資料記錄。對於解析binlog獲得的 INSERT ,UPDATE,DELETE事件我們只需要分析copy資料之前log before copy 和copy資料之後 log after copy。整體的資料遷移會在後面做詳細分析。

5 增量應用binlog遷移資料

核心程式碼在 gh-ost/go/sql/builder.go 中,這裡主要做DML轉換的解釋,當然還有其他函式做輔助工作,比如資料庫 ,表名校驗 以及語法完整性校驗。

解析到delete語句 對應轉換為delete語句

func BuildDMLDeleteQuery(databaseName, tableName string, tableColumns, uniqueKeyColumns *ColumnList, args []interface{}) (result string, uniqueKeyArgs []interface{}, err error) {
   ....省略程式碼...
    result = fmt.Sprintf(`
            delete /* gh-ost %s.%s */
                from
                    %s.%s
                where
                    %s
        `, databaseName, tableName,
        databaseName, tableName,
        equalsComparison,
    )
    return result, uniqueKeyArgs, nil
}

解析到insert語句 對應轉換為replace into語句

func BuildDMLInsertQuery(databaseName, tableName string, tableColumns, sharedColumns, mappedSharedColumns *ColumnList, args []interface{}) (result string, sharedArgs []interface{}, err error) {
   ....省略程式碼...
    result = fmt.Sprintf(`
            replace /* gh-ost %s.%s */ into
                %s.%s
                    (%s)
                values
                    (%s)
        `, databaseName, tableName,
        databaseName, tableName,
        strings.Join(mappedSharedColumnNames, ", "),
        strings.Join(preparedValues, ", "),
    )
    return result, sharedArgs, nil
}

解析到update語句 對應轉換為語句

func BuildDMLUpdateQuery(databaseName, tableName string, tableColumns, sharedColumns, mappedSharedColumns, uniqueKeyColumns *ColumnList, valueArgs, whereArgs []interface{}) (result string, sharedArgs, uniqueKeyArgs []interface{}, err error) {
   ....省略程式碼...
    result = fmt.Sprintf(`
            update /* gh-ost %s.%s */
                    %s.%s
                set
                    %s
                where
                    %s
        `, databaseName, tableName,
        databaseName, tableName,
        setClause,
        equalsComparison,
    )
    return result, sharedArgs, uniqueKeyArgs, nil
}

資料遷移的資料一致性分析

gh-ost 做ddl變更期間對原表和影子表的操作有三種:對原表的row copy (我們用A操作代替),業務對原表的DML操作(B),對影子表的apply binlog(C)。而且binlog是基於dml 操作產生的,因此對影子表的apply binlog 一定在 對原表的dml之後,共有如下幾種順序:

通過上面的幾種組合操作的分析,我們可以看到 資料最終是一致的。尤其是當copy 結束之後,只剩下apply binlog,情況更簡單。

6 copy完資料之後進行原始表和影子表cut-over 切換

gh-ost的切換是原子性切換,基本是通過兩個會話的操作來完成 。作者寫了三篇文章解釋cut-over操作的思路和切換演算法。詳細的思路請移步到下面的連結。

http://code.openark.org/blog/mysql/solving-the-non-atomic-table-swap-take-iii-making-it-atomic

http://code.openark.org/blog/mysql/solving-the-non-atomic-table-swap-take-ii

http://code.openark.org/blog/mysql/solving-the-facebook-osc-non-atomic-table-swap-problem

這裡將第三篇文章描述核心切換邏輯摘錄出來。其原理是基於MySQL 內部機制:被lock table 阻塞之後,執行rename的優先順序高於dml,也即先執行rename table ,然後執行dml 。假設gh-ost操作的會話是c10 和c20 ,其他業務的dml請求的會話是c1-c9,c11-c19,c21-c29。

1 會話 c1..c9: 對b表正常執行DML操作。
2 會話 c10 : 建立_b_del 防止提前rename 表,導致資料丟失。
      create /* gh-ost */ table `test`.`_b_del` (
            id int auto_increment primary key
        ) engine=InnoDB comment='ghost-cut-over-sentry'
        
3 會話 c10 執行LOCK TABLES b WRITE, `_b_del` WRITE。
4 會話c11-c19 新進來的dml或者select請求,但是會因為表b上有鎖而等待。
5 會話c20:設定鎖等待時間並執行rename
    set session lock_wait_timeout:=1
    rename /* gh-ost */ table `test`.`b` to `test`.`_b_20190908220120_del`, `test`.`_b_gho` to `test`.`b`
  c20 的操作因為c10鎖表而等待。
  
6 c21-c29 對於表 b 新進來的請求因為lock table和rename table 而等待。
7 會話c10 通過sql 檢查會話c20 在執行rename操作並且在等待mdl鎖。
select id
            from information_schema.processlist
            where
                id != connection_id()
                and 17765 in (0, id)
                and state like concat('%', 'metadata lock', '%')
                and info  like concat('%', 'rename', '%')

8 c10 基於步驟7 執行drop table `_b_del` ,刪除命令執行完,b表依然不能寫。所有的dml請求都被阻塞。

9 c10 執行UNLOCK TABLES; 此時c20的rename命令第一個被執行。而其他會話c1-c9,c11-c19,c21-c29的請求可以操作新的表b。

劃重點點(敲黑板)

1 建立_b_del表是為了防止cut-over提前執行,導致資料丟失。

2 同一個會話先執行write lock之後還是可以drop表的。

3 無論rename table和dml操作誰先執行,被阻塞後rename table總是優先於dml被執行。

大家可以一邊自己執行gh-ost ,一邊開啟general log 檢視具體的操作過程。

2019-09-08T22:01:24.086734  17765   create /* gh-ost */ table `test`.`_b_20190908220120_del` (
            id int auto_increment primary key
        ) engine=InnoDB comment='ghost-cut-over-sentry'
2019-09-08T22:01:24.091869  17760 Query lock /* gh-ost */ tables `test`.`b` write, `test`.`_b_20190908220120_del` write
2019-09-08T22:01:24.188687  17765   START TRANSACTION
2019-09-08T22:01:24.188817  17765   select connection_id()
2019-09-08T22:01:24.188931  17765   set session lock_wait_timeout:=1
2019-09-08T22:01:24.189046  17765   rename /* gh-ost */ table `test`.`b` to `test`.`_b_20190908220120_del`, `test`.`_b_gho` to `test`.`b`
2019-09-08T22:01:24.192293+08:00    17766 Connect   [email protected] on test using TCP/IP
2019-09-08T22:01:24.192409  17766   SELECT @@max_allowed_packet
2019-09-08T22:01:24.192487  17766   SET autocommit=true
2019-09-08T22:01:24.192578  17766   SET NAMES utf8mb4
2019-09-08T22:01:24.192693  17766   select id
            from information_schema.processlist
            where
                id != connection_id()
                and 17765 in (0, id)
                and state like concat('%', 'metadata lock', '%')
                and info  like concat('%', 'rename', '%')
2019-09-08T22:01:24.193050  17766 Query select is_used_lock('gh-ost.17760.lock')
2019-09-08T22:01:24.193194  17760 Query drop /* gh-ost */ table if exists `test`.`_b_20190908220120_del`
2019-09-08T22:01:24.194858  17760 Query unlock tables
2019-09-08T22:01:24.194965  17760 Query ROLLBACK
2019-09-08T22:01:24.197563  17765 Query ROLLBACK
2019-09-08T22:01:24.197594  17766 Query show /* gh-ost */ table status from `test` like '_b_20190908220120_del'
2019-09-08T22:01:24.198082  17766 Quit
2019-09-08T22:01:24.298382  17760 Query drop /* gh-ost */ table if exists `test`.`_b_ghc`

如果cut-over過程的各個環節執行失敗會發生什麼? 其實除了安全,什麼都不會發生。

如果c10的create `_b_del` 失敗,gh-ost 程式退出。

如果c10的加鎖語句失敗,gh-ost 程式退出,因為表還未被鎖定,dml請求可以正常進行。

如果c10在c20執行rename之前出現異常
 
 A. c10持有的鎖被釋放,查詢c1-c9,c11-c19的請求可以立即在b執行。
 B. 因為`_b_del`表存在,c20的rename table b to  `_b_del`會失敗。
 C. 整個操作都失敗了,但沒有什麼可怕的事情發生,有些查詢被阻止了一段時間,我們需要重試。

如果c10在c20執行rename被阻塞時失敗退出,與上述類似,鎖釋放,則c20執行rename操作因為——b_old表存在而失敗,所有請求恢復正常。

如果c20異常失敗,gh-ost會捕獲不到rename,會話c10繼續執行,釋放lock,所有請求恢復正常。

如果c10和c20都失敗了,沒問題:lock被清除,rename鎖被清除。 c1-c9,c11-c19,c21-c29可以在b上正常執行。

整個過程對應用程式的影響

應用程式對錶的寫操作被阻止,直到交換影子表成功或直到操作失敗。如果成功,則應用程式繼續在新表上進行操作。如果切換失敗,應用程式繼續繼續在原表上進行操作。

對複製的影響

slave因為binlog檔案中不會複製lock語句,只能應用rename 語句進行原子操作,對複製無損。

7 處理收尾工作

最後一部分操作其實和具體引數有一定關係。最重要必不可少的是

關閉binlogsyncer連線

至於刪除中間表 ,其實和引數有關 --initially-drop-ghost-table --initially-drop-old-table

三 小結

縱觀gh-ost的執行過程,檢視原始碼演算法設計, 尤其是cut-over設計思路之精妙,原子操作,任何異常都不會對業務有嚴重影響。歡迎已經使用過的朋友分享各自遇到的問題,也歡迎還未使用過該工具的朋友大膽嘗試。

參考文章

https://www.cnblogs.com/mysql-dba/p/9901589.html


本公眾號長期關注於資料庫技術以及效能優化,故障案例分析,資料庫運維技術知識分享,個人成長和自我管理等主題,歡迎掃碼關注。

相關推薦

gh-ost 原理剖析

gh-ost 原理 一 簡介 上一篇文章介紹 gh-ost 引數和具體的使用方法,以及核心特性-可動態調整 暫停,動態修改引數等等。本文分幾部分從原始碼方面解釋gh-ost的執行過程,資料遷移,切換細節設計。 二 原理 2.1 執行過程 本例基於在主庫上執行ddl 記錄的核心過程。核心程式碼在 github.

gh-ost原理

osc select 累加 退出程序 ren roc 結合 use 集群 gh-ost原理 一、三種模式架構圖 1、連上從庫,在主庫上修改 這是gh-ost默認的工作模式,它會查看從庫情況,找到集群的主庫並且連接上去,對主庫侵入最少,大體步驟是: 在主庫上創建_xxx_g

NIO原理剖析與Netty初步----淺談高性能服務器開發(一)

返回 創建 基於 register 訪問 io操作 nbsp info class 除特別註明外,本站所有文章均為原創,轉載請註明地址 在博主不長的工作經歷中,NIO用的並不多,由於使用原生的Java NIO編程的復雜性,大多數時候我們會選擇Netty,m

LVS/DR模式原理剖析(FAQs)

load 是否 live forward onf client 默認 rec add 1. LVS/DR如何處理請求報文的,會修改IP包內容嗎? 1.1 vs/dr本身不會關心IP層以上的信息,即使是端口號也是tcp/ip協議棧去判斷是否正確,vs/dr本身主要做這麽幾個事

『理論』科學計算專項_線性代數幾何原理剖析

str tar 是否 數學 這就是 cti bsp 存在 amp 矩陣左乘向量的兩種理解 1,矩陣左乘向量可以理解為對向量進行線性變換 探究原理的話,可以理解左乘為對整個空間(基&目標向量)進行線性變換,其中, 變換矩陣是基‘在基的坐標的列向量組合 目標向量是向量

Spring Boot 揭秘與實戰 源碼分析 - 工作原理剖析

pro rop 功能 row commons 擴展 onf 公眾 ica 文章目錄 1. EnableAutoConfiguration 幫助我們做了什麽 2. 配置參數類 – FreeMarkerProperties 3. 自動配置類 – FreeMarkerAuto

CSS繪制三角形的原理剖析

知識庫 osi too 完全 部分 學習 lib 軟件 bootstra 今天學習Bootstrap時候,看到按鈕的向下三角形源碼: 1 .caret { 2 display: inline-block; 3 width: 0; 4 hei

下載ASP.NET MVC5框架剖析與案例解析(MVC5原理剖析、漏洞及運維安全、設計模式)

mvc5框架剖析與案例解析 運維安全 mvc5原理剖析 地址:http://pan.baidu.com/s/1dFhBu2d 密碼:peas轉一播放碼,200多課!本課程針對MVC5版本的ASP.NET MVC,同時涉及太多底層實現的內容,所以大部分是找不到現成參考資料的,這些內容大都來自講師對源

ASP.NET Core 運行原理剖析1:初始化WebApp模版並運行

正式版 功能 option urn server ack reference 修改 tin ASP.NET Core 運行原理剖析1:初始化WebApp模版並運行 核心框架 ASP.NET Core APP 創建與運行 總結 之前兩篇文章簡析.NET Core

mysql索引原理剖析

mage add 獲得 旋轉速度 tran 運行期 多個 pla 線性 一、索引的原理   所謂索引,即是快速定位與查找,那麽索引的結構組織要盡量減少查找過程中磁盤I/O的存取次數(B+樹相比B樹,其非葉子節點占用更小的空間,可以有更多非葉子節點存放在再內存中,減少大量的

HDFS原理剖析

gpo body 剖析 bdb x86 ade ref xiv .html 燒m6孟顆掛菜ws刎戳雌換戀榷挪禾岸乓屹27廢儀俅http://blog.sina.com.cn/s/blog_172ce15720102xhzg.html玖sk鹵粱構招yy境媒誌氯市趴徹譖罷迅墜c

HashMap原理剖析

object 是把 continue city http 就是 遞歸實現 mage ima 什麽叫hash? 就是把一個不固定的長度的二進制值映射成一個固定長度的二進制值。 hash算法:就是對應的這個映射規則。hash值:固定長度的二進制值。 什麽叫hash表?

初試GH-OST(轉)

多個 比較 ike tps 5.5 記錄 online tab http 最近老板讓做一個gh-ost和pt-osc 的對比測試,本文將對兩者做對比。 一。原理和所用說明 PT-OSC GH-OST 原理 1.創建一個和要執行 a

gh-ost:不一樣的在線表結構變更

配置 mysql 量變 不依賴 重新 重啟 初始 tab copy 簡介: 2016年8月份,shlomi-noach在GitHub Engineering發文宣布gh-ost開源。gh-ost是什麽?一個不依賴觸發器實現的在線表結構變更工具. 對於數據庫運維人員來

MySQL在線DDL gh-ost 使用說明

migrate 寫入 bubuko cte 行數據 結構 mysql 源表 3.0 背景: 作為一個DBA,大表的DDL的變更大部分都是使用Percona的pt-online-schema-change,本文說明下另一種工具gh-ost的使用:不依賴於觸發器,

Netty原理剖析

知識 狀態切換 簡單的 edi 負責 外部 eap 但是 會同 1. Netty簡介 Netty是一個高性能、異步事件驅動的NIO框架,基於JAVA NIO提供的API實現。它提供了對TCP、UDP和文件傳輸的支持,作為一個異步NIO框架,Netty的所有IO操作都是異步非

Spark2.1內部原理剖析與源碼閱讀、程序設計與企業級應用案例

封裝 以及 url string 計算機網絡 內部原理 企業級 目標 sql 1、本文目標以及其它說明: 本文或者本次系列主要是弄清楚spark.2.2.0版本中,spark core 包下rpc通信情況。從源代碼上面看到,底層通信是用的netty,因為本系

項目實戰-大數據Kafka原理剖析及(實戰)演練

實戰 kafka 大數據 com nbsp attach forum ignore spa Kafka原理剖析及實戰演練 Kafka理論+實戰視頻教程 Kafka完美入門視頻教程 煉數成金<ignore_js_op> <ignore_js_op> &

spring boot 啟動原理剖析

urn rgs 獲取 target loader 技術分享 提前 pub 流程 準備 SpringBoot為我們做的自動配置,確實方便快捷,若不大明白SpringBoot內部啟動原理,以後難免會吃虧,所以這次博主就跟你們一起一步步揭開SpringBoot的神秘面紗,讓它不再