mysql使用ghost执行ddl过程分析

wdg - September 18, 2025

operation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# version 1.1.7
gh-ost                                   \
-execute                                 \
-allow-on-master                         \
-assume-rbr                              \
-ok-to-drop-table                        \
-default-retries 120                     \
-cut-over-lock-timeout-seconds 10        \
-host 10.1.1.100                         \
-port 11000                              \
-user weideguo                           \
-password my_password                    \
-database my_db                          \
-table a                                 \
-alter "engine=innodb"                   \
--chunk-size=10

info

1
2
3
4
5
6
CREATE TABLE `a` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `success_time` timestamp NULL DEFAULT NULL,
  `x` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
Version: 5.7.44-log (MySQL Community Server (GPL)).
Id  Command  Argument
663 Connect  weideguo@10.1.1.100 on my_db using TCP/IP
663 Query    set names utf8mb4
663 Query    SET NAMES utf8mb4
663 Query    SET autocommit = true, transaction_isolation = "REPEATABLE-READ"
663 Query    select @@global.version
663 Query    select @@global.port
663 Query    select @@global.hostname, @@global.port
663 Query    show /* gh-ost */ grants for current_user()
663 Query    select /* gh-ost */ @@global.log_bin, @@global.binlog_format
663 Query    select /* gh-ost */ @@global.binlog_row_image
663 Query    show /* gh-ost */ table status from `my_db` like 'a'
663 Query    SELECT /* gh-ost */ ... FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE ...               -- 确认外键的情况,依赖外表的字段、被依赖的字段
663 Query    SELECT /* gh-ost */ COUNT(*) AS num_triggers FROM INFORMATION_SCHEMA.TRIGGERS ...  -- 确认触发器的情况
663 Query    explain select /* gh-ost */ * from `my_db`.`a` where 1=1
663 Query    SELECT /* gh-ost */ ... FROM INFORMATION_SCHEMA.COLUMNS ...          

663 Query    show columns from `my_db`.`a`
663 Query    SELECT /* gh-ost */ AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES ...              -- 获取表的AUTO_INCREMENT值
    
663 Query    select /* gh-ost */ @@global.log_slave_updates
663 Query    select @@global.version
663 Query    select @@global.port
663 Query    show /* gh-ost readCurrentBinlogCoordinates */ master status
666 Connect    weideguo@10.1.1.100 on  using TCP/IP
666 Query    set names utf8mb4
666 Query    SHOW GLOBAL VARIABLES LIKE 'BINLOG_CHECKSUM'                                                                            
666 Query    SET @master_binlog_checksum='NONE'                                                                                      
666 Query    SET @slave_uuid = 'f074c201-9444-11f0-95ac-4cd98f4be3d9', @replica_uuid = 'f074c201-9444-11f0-95ac-4cd98f4be3d9'       -- 
666 Binlog Dump    Log: 'binlog.000082'  Pos: 17177306                                                                              -- 复制binlog
667 Connect    weideguo@10.1.1.100 on my_db using TCP/IP
667 Query    set names utf8mb4
667 Query    SET NAMES utf8mb4
667 Query    SET autocommit = true, transaction_isolation = "REPEATABLE-READ"
667 Query    select @@global.version
667 Query    select @@global.port
668 Connect    weideguo@10.1.1.100 on my_db using TCP/IP
668 Query    set names utf8mb4
668 Query    SET NAMES utf8mb4
668 Query    SET transaction_isolation = "REPEATABLE-READ", autocommit = true
668 Query    select @@global.version
668 Query    select @@global.port
667 Query    select /* gh-ost */ @@global.time_zone, @@global.wait_timeout
667 Query    select @@global.hostname, @@global.port
667 Query    show columns from `my_db`.`a`
667 Query    show /* gh-ost */ table status from `my_db` like '_a_gho'
667 Query    show /* gh-ost */ table status from `my_db` like '_a_del'
667 Query    drop /* gh-ost */ table if exists `my_db`.`_a_ghc`
667 Query    create /* gh-ost */ table `my_db`.`_a_ghc` (                         -- 这个表记录执行的进度
            id bigint unsigned 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 comment='gh-ost changelog'
667 Query    START TRANSACTION
667 Query    SET SESSION time_zone = '+08:00', sql_mode = CONCAT(@@session.sql_mode, ',NO_AUTO_VALUE_ON_ZERO,STRICT_ALL_TABLES')
667 Query    create /* gh-ost */ table `my_db`.`_a_gho` like `my_db`.`a`
667 Query    COMMIT
667 Query    START TRANSACTION
667 Query    SET SESSION time_zone = '+08:00', sql_mode = CONCAT(@@session.sql_mode, ',NO_AUTO_VALUE_ON_ZERO,STRICT_ALL_TABLES')
667 Query    alter /* gh-ost */ table `my_db`.`_a_gho` engine=innodb
667 Query    COMMIT
667 Query    alter /* gh-ost */ table `my_db`.`_a_gho` AUTO_INCREMENT=236

667 Query    insert /* gh-ost */ into `my_db`.`_a_ghc` ...                        -- 将ghost执行进度写入表中

663 Query    SELECT /* gh-ost */ ... FROM INFORMATION_SCHEMA.COLUMNS ...          -- 查询mysql系统表获取表的主键信息
            
663 Query    show columns from `my_db`.`_a_gho`
663 Query    select /* gh-ost */ * from information_schema.columns ...            -- 获取表的字段信息,a、_a_gho

667 Query    START TRANSACTION                                                    -- 确认要复制的主键范围
667 Query    select /* gh-ost `my_db`.`a` */ `id`
        from
            `my_db`.`a`
        force index (PRIMARY)
        order by
            `id` asc
        limit 1
667 Query    select /* gh-ost `my_db`.`a` */ `id`
        from
            `my_db`.`a`
        force index (PRIMARY)
        order by
            `id` desc
        limit 1
667 Query    COMMIT

679 Query    SET autocommit = true, transaction_isolation = "REPEATABLE-READ"
680 Query    SET transaction_isolation = "REPEATABLE-READ", autocommit = true

680 Query    SET /* gh-ost */ SESSION time_zone = '+00:00', sql_mode = CONCAT(@@session.sql_mode, ',NO_AUTO_VALUE_ON_ZERO,STRICT_ALL_TABLES')
680 Query    START TRANSACTION                                  -- 通过binlog补操作时的事务,一条update转换成一条delete一条replace
680 Query    delete /* gh-ost `my_db`.`_a_gho` */               -- 
        from                                                    -- 主键130至138被改成+1000 
            `my_db`.`_a_gho`                                    -- 并不是优先执行这个语句,通用日志记录问题而已
        where                                                   -- 实际应该是ghost判断主键是否已经复制到影子表,再执行
            ((`id` = 130));                                     -- 数据量大时多个事务执行,并不必等待全表所有记录都复制到到影子表
680 Query    replace /* gh-ost `my_db`.`_a_gho` */              -- 
        into                                                    
            `my_db`.`_a_gho`                                    
            (`id`, `success_time`, `x`)                         
        values                                                  
            (1130, '2025-09-18 03:14:52', 12);

...

680 Query    COMMIT
680 Query    select /* gh-ost `my_db`.`a` iteration:0 */                      -- 确认该批次复制的主键的上限
            `id`
        from
            `my_db`.`a`
        where
            ((`id` > 130) or ((`id` = 130))) and ((`id` < 1129) or ((`id` = 1129)))
        order by
            `id` asc
        limit 1
        offset 9
680 Query    START TRANSACTION
680 Query    SET SESSION time_zone = '+08:00', sql_mode = CONCAT(@@session.sql_mode, ',NO_AUTO_VALUE_ON_ZERO,STRICT_ALL_TABLES')
680 Query    insert /* gh-ost `my_db`.`a` */ ignore                           -- 根据主键迭代复制数据到影子表
        into
            `my_db`.`_a_gho`
            (`id`, `success_time`, `x`)
        (
            select `id`, `success_time`, `x`
            from
                `my_db`.`a`
            force index (`PRIMARY`)
            where
                (((`id` > 130) or ((`id` = 130))) and ((`id` < 148) or ((`id` = 148))))
                lock in share mode
        )
680 Query    COMMIT

...                                                                                       -- 迭代多次,实现表的数据复制到影子表。中间根据条件补上根据binlog获取的事务。

680 Query    START TRANSACTION
680 Query    select /* gh-ost */ connection_id()
680 Query    select /* gh-ost */ get_lock('gh-ost.680.lock', 0)
680 Query    set /* gh-ost */ session lock_wait_timeout:=20                               -- 为gh-ost参数cut-over-lock-timeout-seconds 超时时间*2;确保先获取锁
679 Query    show /* gh-ost */ table status from `my_db` like '_a_del'
679 Query    create /* gh-ost */ table `my_db`.`_a_del` (
            id int auto_increment primary key
        ) engine=InnoDB comment='ghost-cut-over-sentry'
680 Query    set /* gh-ost */ session wait_timeout:=30
680 Query    lock /* gh-ost */ tables `my_db`.`a` write, `my_db`.`_a_del` write           -- 先锁表

...
663 Query    select /* gh-ost */ hint, value from `my_db`.`_a_ghc` ...
679 Query    insert /* gh-ost */ into `my_db`.`_a_ghc` ...
...

679 Query    START TRANSACTION
679 Query    select /* gh-ost */ connection_id()
679 Query    set /* gh-ost */ session lock_wait_timeout:=10                                                -- 为gh-ost参数cut-over-lock-timeout-seconds 超时时间,当这个失败则ghost退出
679 Query    rename /* gh-ost */ table `my_db`.`a` to `my_db`.`_a_del`, `my_db`.`_a_gho` to `my_db`.`a`    -- 等待锁释放才能执行
667 Query    select /* gh-ost */ id
        from
            information_schema.processlist
        where
            id != connection_id()
            and 679 in (0, id)
            and state like concat('%', 'metadata lock', '%')
            and info like concat('%', 'rename', '%')
667 Query    select /* gh-ost */ is_used_lock('gh-ost.680.lock')
680 Query    drop /* gh-ost */ table if exists `my_db`.`_a_del`
680 Query    unlock /* gh-ost */ tables                                                           -- 释放锁,rename操作得以执行
667 Query    set /* gh-ost */ session wait_timeout:=1800
680 Query    ROLLBACK
680 Query    show /* gh-ost */ table status from `my_db` like '_a_del'
679 Query    ROLLBACK

680 Query    insert /* gh-ost */ into `my_db`.`_a_ghc` ...

682 Connect  weideguo@10.1.1.100 on  using TCP/IP
682 Query    KILL 666                                                    -- 关闭复制binlog的连接
680 Query    drop /* gh-ost */ table if exists `my_db`.`_a_ghc`
680 Query    drop /* gh-ost */ table if exists `my_db`.`_a_del`
                                                                         -- ghost所有连接退出