文章目录

  • 1. MySQL online DDL 各版本介绍
    • 1.1 online DDL in mysql 5.5
    • 1.2 online DDL in mysql5.6
    • 1.3 online DDL in mysql5.7
    • MDL-Metadata Lock
    • 1.4 pt-online-schema-change
  • 2. GH-OST工具
    • 2.1 GH-OST原理分析:
    • 2.2 GH-OST工作流程
    • 2.3 GH-OST的三种工作模式
    • 2.4 GH-OST使用限制:
    • 2.5 GH-OST安装测试
    • 2.6 各个工具的总结对比
    • 2.7 sysbench压测online DDL,PT-OSC,gh-ost

1. MySQL online DDL 各版本介绍

1.1 online DDL in mysql 5.5

在mysql5.5版本中已经增加了in-place方式,但依然会阻塞insert,update,delete操作。

mysql 5.5 online ddl 原理:

a. 按照原表的定义创建临时表;

b. 对原表进行加写锁;

c. 对新的临时表进行ddl操作;

d. 将原表中的数据copy到临时表中;

e. 释放原表的写锁;

f. 将旧表删除,临时表重命名

存在的问题:

a. 在进行copy data的过程中消耗的时间长,和消耗大量的存储空间;

b. 在原表进行加锁时,业务会中断访问。

1.2 online DDL in mysql5.6

在mysql5.6版本中,引入了新特性,Fast Index Create(FIC特性),支持更多的alter table语句来避免copy data同时支持了在线上DDL的过程中不阻塞DML操作。


mysql5.6参数设置:

innodb_online_alter_log_max_size参数,默认为128M,但是在生产场景中512M会适合。在进行在线索引添加操作时,数据库性能有20-30%的下降

1.3 online DDL in mysql5.7

mysql5.7支持的新特性:

a. 增加了alter table rename index 的语法支持,同时也继续支撑online DDL特性;

b. 修改varchar列长度操作支持online特性。


alter table t1 algorithm=INPLACE, change column c1 c1 varchar(255);
ALTER TABLE t1  ADD COLUMN new_column ALGORITHM=INPLACE,LOCK=DEFAULT;algorithm子句用来指定执行DDL采用的方式,可取值为DEFAULT,INPLACE,COPY
LOCK子句描述特有的锁类型人控制DML的并发,取值DEFAULT,NONE,SHARED,EXCLUSIVE

mysql5.7 online DDL的原理:

PREPARE:
a. 创建新的临时.frm 文件;
b. 持有排他-MDL锁,禁止读写;
c. 根据alter 类型确定执行方式;
d. 更新数据字典的内存对象;
e. 分配row_log对象用来记录增量;
f. 生成新的临时ibd文件;
DDL:
g. 降级排他锁为S锁;
h. 扫描old_table的聚簇索引每一条记录reo,并遍历新表的索引进行处理;
i. 根据reo构造对应的索引项对应的索引项,将构造索引项插入soft_buffer块排序;
j. 将sort_buffer块更新到新的索引上;
k. 记录ddl执行过程中产生的增量并在新表上重放;
l. 记录ddl执行过程中产生的增量(仅rebuild类型需要);
m. 重放row_log间产生dml操作append到row_log最后一个block;
COMMIT;
n. 当前block为row_log最后一个时,禁止读写,升级到排他-MDL锁;
o.重做row_log中最后一部分增量;
p.更新innodb的数据字典表;
q. 提交事务(刷事务的redo日志)
r. 修改统计信息;
s. rename临时ibd文件,.frm文件
online DDL使用限制与问题:
a. 仍然存在排他锁,有锁等待的风险;
b. 跟5.6一样,增量日志大小是有限制的;
c. 有可能造成主从延迟;
d. 无法暂停,只能中断。

MDL-Metadata Lock

**MDL概念:**MDL的引入会导致一定的性能的损耗,对同一个database objects访问越多,就会导致该对象的MDL的争用;

为了维护表元数据的一致性,在表中有显示或隐式的活动事务时,不可以对元数据进行写入操作,mysql 引入了metadata lock,来保护元数据信息。因此在对表进行上述操作时,如果表上有活动事务,请求写入的会话,会等待在metadata lock wait ;

造成mdl锁的场景:

a. 当session1开启事务,没有提交对t表的查询,同时session2对t表进行alter修改,则会造成MDL锁;

b. 当session1开启事务没有提交对t表的查询,同时session2对t表进行drop 操作,则会造成MDL锁;

哪些操作会获得MDL锁:

a. 表结构的更改;

b. 创建,删除索引;

c. 删除表;

d. 对表加写锁,进行读操作。对表加读锁,进行写操作;

MDL劣势:

MDL锁是表锁,无论是读或者写操作都无法进行,导致SQL阻塞;

如果监控不到位,在高并发场景中,会造成大量的SQL阻塞。除非人工DDL结束;

MDL优势:

MDL的主要目的是为了保护元数据,假如,没有MDL锁,会导致读入的数据不一致,写入的时候发生元数据冲突。同时MDL也是可以避免的,例如可以在业务低峰期使用pt-osc进行操作或者gh-ost,做好报警工作,对于未提交的事务要尽快提交,或者kill到没有commit的事务。

MDL常见的四种锁对象:

与innodb层类似的是,某些类型的MDL 锁会从上入下一层层加锁。例如,lock table … write这样的SQL,会对GLOBAL级别加INTENTION_EXCLUSIVE锁,再对SCHEMA加INTENTION_EXCLUSIVE锁,最后给TABLE加SHARED_NO_READ_WRITE锁。COMMIT对象层次的锁最主要应用于XA事务中,当分页式事务已经prepare成功,但是在xa commit之前有其他会话执行了FLUSH TABLES READ LOCK这样的操作,那么分页式事务的提交就需要等待;

锁模式 对应的SQL
MDL_INTENTION_EXCLUSIVE GLOBAL,SCHEMA对象操作会加锁
MDL_SHARED FLUSH TABLES WITH READ LOCK
MDL_SHARED_HIGH_PRIO 仅对MYISAM存储引擎有效
MDL_SHARED_READ SELECT语句(相当于Innodb共享锁)
MDL_SHARED_WRITE DML语句(相当于Innodb排他锁)
MDL_SHARED_WRITE_LOW_PRIO 对myisam存储引擎有效
MDL_SHARED_UPGRADABLE ALTER TABLE
MDL_SHARED_READ_ONLY LOCK TABLE T READ ;
MDL_SHARED_NO_WRITE FLUSH TABLES xxx,yyy,zz READ
MDL_SHARED_NO_READ_WRITE FLUSH TABLE XX WRITE
MDL_EXCLUSIVE ALTER TABLE XXX PARTITION BY 。。

监控MDL锁:

mysql 5.5:

select * from information_schema.processlist where state = 'Waiting for table metadata lock';

mysql 5.6:

select * from performance_schema.metadata_locks;
需要开启performance_schema:
root@mysqldb 10:03:  [performance_schema]> update performance_schema.setup_instruments set ENABLED = 'YES',TIMED='YES' where name = 'wait/lock/metadata/sql/mdl';
root@mysqldb 10:02:  [performance_schema]> update performance_schema.setup_consumers set ENABLED = 'YES' where name in ('global_instrumentation');

mysql5.7:

select * from sys.schema_table_lock_waits;

故障模拟:

session1:

mysql> lock table test read;
Query OK, 0 rows affected (0.01 sec)

session2:

mysql> insert into test values (5,'fd');

查看锁状态:


mysql> select * from sys.schema_table_lock_waits\G
*************************** 1. row ***************************object_schema: test_dbobject_name: testwaiting_thread_id: 35waiting_pid: 10waiting_account: root@localhostwaiting_lock_type: SHARED_WRITEwaiting_lock_duration: TRANSACTIONwaiting_query: insert into test values (5,'fd')waiting_query_secs: 23waiting_query_rows_affected: 0waiting_query_rows_examined: 0blocking_thread_id: 34blocking_pid: 9blocking_account: root@localhostblocking_lock_type: SHARED_READ_ONLYblocking_lock_duration: TRANSACTIONsql_kill_blocking_query: KILL QUERY 9
sql_kill_blocking_connection: KILL 9
1 row in set (0.00 sec)

简单解释原因,由于MDL锁是表级锁是SERVER层的锁,主要用于隔离DML和DDL操作之间的干扰。每执行一条DML,DDL语句时都会申请MDL锁,DML操作需要MDL读锁,DDL操作需要MDL写锁(MDL加锁是系统自动控制的,无法直接干涉,读读共享,读写互斥,写写互斥)。申请MDL锁的操作会形成一个队列,队列中的写锁获取优先级高于读锁,如果出现写锁,不但当前的操作会被阻塞,同时还会阻塞后续该表的所有操作。事务一旦申请到MDL锁后,直到事务执行完才会将锁释放。(这里有特殊情况如果事务中包含DDL操作,mysql会在DDL操作语句执行后,隐式提交COMMIT,以保证该DDL语句操作作为一个单独的事务而存在,同时也保证元数据排遣他锁的释放,例如:<begin;alter table t add c varchar(32) not null; select * from t;>,此时一旦alter 语句执行成功之后会马上提交事务,select会在本次的事务之外,其执行完成后不会有读锁)

总结元数据锁信息

a. shared_upgradable:(alter table语句)本身为读锁,但是有特殊,优先级为0;

  1. 并不受队列中的写锁等待而阻塞,只和当前持锁的session比对,当前持锁session为排他锁X,则等待,反之则获得锁;

  2. 为了保证一张表同时只有一个DDL操作进行,shared_upgradable之间是互斥的,一个时间只能有一个share_upgradable的状态是granted ,其余被阻塞;

b. eclusive,shared_no_read_write级别相同,先进先出;

c. shared_write(DML)和shared_read(SELECT)兼容,但shared_write优先级高于shared_read_only(LOCK TABLE T READ)且不兼容;

d. shared_read_only优先级最低,主要是因为被shared_write互斥,但如果只有shared_read则他们的优先级是兼容的。

处理MDL锁造成的阻塞:

a. 处理掉长事务;

session1的事务没有执行完毕,则kill session2可以通过下面的SQL进行洞察,可以自行评估;

root@mysqldb 10:17:  [information_schema]> select * from information_schema.INNODB_TRX\G

可以自行kill session1;

b. 直接kil ddl语句

则kill session1 选择业务低峰期执行,如果DDL执行到80%-90%,则kill 其 他会话,视实际情况而定;

1.4 pt-online-schema-change

percona-toolkit源自maatkit和Aspersa工具,用来管理mysql和系统任务,主要包括:

a. 验证主节点和复制数据的一致性,pt-table-checksum,pt-table,sync
b. 有效的对记录行进行归档 【pt-archiver】
c. 分析索引使用情况 【pt-index-usage】
d. 总结 MySQL 服务器 【pt-summary】
e. 从日志和 tcpdump 中分析查询 【pt-query-digest】
f. 问题发生时收集重要的系统信息 【pt-stalk】
g. kill 符合条件的 mysql thread 【pt-kill】
h. 在线修改表结构 【pt-online-schema-change】
优势:支持并发DML操作,稳定性强;

pt-osc原理:

a. 创建一张新表,并与原表的结构相同;
b. 对新表进行alter操作;
c. 在原表上创建insert ,update,delete 三种类型的触发器;
d. 将旧表数据copy到新表中,同时通过触发器将旧表的操作映射到新表中;
e. 如果原表有外键约束,处理外键;
f. 原表重命名为old表,new表重命名为原表,整个过程为原子操作;
g. 删除old表

PT-OSC 使用限制:

1.原表不能存在触发器。

2.原表必须存在主键 PRIMARY KEY 或者 UNIQUE KEY

3.外键的处理需要指定 alter-foreign-keys-method 参数,存在风险

4.在 pt-osc 的执行过程中,如果有对主键的更新操作则会出现重复的数据,见下面链接的 bug 说明:

https://bugs.launchpad.net/percona-toolkit/+bug/1646713

PT-OSC 存在的风险

  1. 触发器是以解释型代码保存的,因此每次查询都需要对触发器的代码进行解释的开销。

  2. 触发器与原始查询共享相同的事务空间,原始查询在表上有锁竞争,触发器也会在另一张表上有锁竞争。同时,在触发器删除时同样会有元数据锁。

  3. 触发器无法暂停,当主库 LOAD 变高,希望停止变更时,但触发器是不会停止。因此在整个操作过程中,触发器都会存在直到执行结束。

2. GH-OST工具

GH-OST(gitHub’s online schema transformer):github的在线表定义转换器;

GH-OST优势:

a. 无触发器设计;

b. 切换方案的设计;

c. 最大限度的减少了对主机的影响;

d. 实现了增量数据的获取;基本做到了原子性的切换;

2.1 GH-OST原理分析:

a. 第一步先测试数据库是否可以连通,并且验证database是否存在;
b. 确认连接的实例是否正确;
c. 权限验证 show /* gh-ost */ grants for current_user();
d. binlog验证,包括row格式验证和修改binlog格式后的重启replicate;
e. 原表存储引擎,外键,触发器检查,行数评估等;
f. 初始化stream的连接,添加binlog的监听;
g. 初始化applier连接,创建ghosttable和change logtable;
h. 并发的执行row copy和binlog copy;
i. cut over;
j. 结束

2.2 GH-OST工作流程

a. 创建一个和临时表相似的GHOST表;缓慢将数据从原始表中复制到ghost表,同时不断的传输增量到ghost表;

b. 最后,再用ghost表替换原来的表;

c. ghost使用相同的模式,不同于现在的工具,不使用触发器;

d. gh-ost使用binlog流捕获表更改uses the binary log stream ,并异步地将它们应用到ghost表。ghost对迁移过程有更大的控制,可以真正的暂停,可以真正地将迁移的写负载与主工作负载分离

e. 此外,还提供了许多操作上的好处operational perks;


工作详细流程:

a. 首先,gh-ost连接到主库,根据alter 语句在gh-ost上创建临时表t —> t.bak;

b. gh-ost做为一个“备库”连接到真正的备库上,一连从主库copy已有的数据到gh-ost的t.bak表,一边从真正的备库上拉取增量的binlog,(此时,gh-ost上存储所有的数据和将t表的变更成功之后的数据)然后再把binlog应用给主库;

c. 上图中是cut-over是最后一步,锁住主库的源表,等待binlog应用完毕,替换gh-ost表的t.bak为源表;

d. 在gh-ost执行中,会在原本的binlog event 里面增加以下hint 和心跳包,用来控制整个流程的进度,检测状态等。这样的好处为:

1.  整个流程异步执行:对于源表的增量数据操作没有额外的开销,高峰期变更业务对性能影响小; 1. 降低写压力,触发器操作都在一个事务内,gh-ost应用binlog是另外一个连接在做;1. 可停止,binlog有位置点记录,如果变更过程中发现主库性能有所影响,可以立刻停止拉binlog,停止应用binlog,稳定之后再继续应用; 1. 可测试,gh-ost提供了测试功能,可以连接到一个库上直接做online ddl,在备库上观察变更结果是否正确,再对主库操作。

2.3 GH-OST的三种工作模式

模式一:连上从库,在主库上修改(默认)

具体操作步骤:

a. 先在主库上读写行数据;

b. gh-ost将在从节点拉取binlog信息以及增量的binlog返回到ghost并将binlog信息应用给主库;

c. 在从库上查看表格式和主键,总行数信息;

d. 在从库上读取gh-ostt内部事件日志;

e. 在主库上完成表切换;

如果主库的二进制日志格式是statement,就可以使用这种模式,但从库就必须配成启用二进制日志(log_bin,log_slave_updates)还要设成row格式(binlog_format=ROW),实际上gh-ost会在从库上做这些设置。简而言之,即使把从库改成ROW格式,这仍然是对主为侵入最少的工作模式;

模式二:在主库上做修改

工作流程:

通过master节点写入数据,gh-ost拉取master节点的binlog日志及增量数据,在gh-ost进行alter操作之后,将binlog继续返回给master,并进行重命名操作。

所注意的点:

a. 主库必须产生ROW格式的二进制日志;

b. 启动gh-ost时必须用–allow-on-master选项来开启这种模式;

模式三:在从库上修改和测试

这个模式会在从库上做修改,但gh-ost仍然会连上主库,所有的操作都会在从库上做,对主库不会有影响。在操作过程中,gh-ost也会不时的暂停,以便从库的数据可以保持最新;

–migrate-on-replica:gh-ost直接在从库修改表结构,最终的切换过程也是在从库正常复制的状态下完成的;

–test-on-replica:测试为目的,在进行最终的切换操作前,复制就会被停止。原始表和测试表相互切换,再切回来。主从复制暂停的状态下,可以检查这两张表的数据。

cut over阶段:

即rename表阶段

a. 对应用请求的影响:所有请求在cut-over阶段直到临时表消失前会被阻塞,被释放后所有的请求都会在新表上执行,若cut-over阶段失败,则所有的请求一定会在旧表上执行;

b. 对主从复制的影响:备机只能看到rename操作,锁的event不会写入binlog,因此备机看到的是一个原子性的操作。

2.4 GH-OST使用限制:

a. 不能对外键关系及触发器的表进行online DDL ;

b. 要求所连接的获取增量数据的mysql binlog为row格式;

c. 若有同名但是字母大小写不同的表则无法进行修改;

d. 不支持mysql5.7json类型列的修改;

e. 不支持mysql 5.7generated coolumn 的修改。

2.5 GH-OST安装测试

准备环境:

主机名 IP
db01 192.168.214.1 主库
db02 192.168.214.129 从库

a. 安装gh-ost软件:

# yum install gh-ost-1.0.49-1.x86_64.rpm

b. 配置主从:略

并开启log_slave_updates,binlog_format=row

c. 在主从上DDL

此时的操作大概是:

  • 行数据在主库上读写

  • 读取从库的二进制日志,将变更应用到主库

  • 在从库收集表格式,字段&索引,行数等信息

  • 在从库上读取内部的变更事件(如心跳事件)

  • 在主库切换表

[root@db02 opt]# gh-ost --user="root" --password="" --host=192.168.214.129  --port=3380 --database="test" --table="t" --initially-drop-old-table --alter="ADD COLUMN y1 varchar(10),add column y2 int not null default 0 comment 'test' "  --execute
[2020/06/17 17:15:00] [info] binlogsyncer.go:133 create BinlogSyncer with config {99999 mysql 192.168.214.129 3380 root    false false <nil> false UTC true 0 0s 0s 0 false}
[2020/06/17 17:15:00] [info] binlogsyncer.go:354 begin to sync binlog from position (binlog.000007, 1016)
[2020/06/17 17:15:00] [info] binlogsyncer.go:203 register slave for master server 192.168.214.129:3380
[2020/06/17 17:15:00] [info] binlogsyncer.go:723 rotate to (binlog.000007, 1016)
# Migrating `test`.`t`; Ghost table is `test`.`_t_gho`
# Migrating db01:3380; inspecting db02:3380; executing on db02
# Migration started at Wed Jun 17 17:15:00 +0800 2020
# chunk-size: 1000; max-lag-millis: 1500ms; dml-batch-size: 10; max-load: ; critical-load: ; nice-ratio: 0.000000
# throttle-additional-flag-file: /tmp/gh-ost.throttle
# Serving on unix socket: /tmp/gh-ost.test.t.sock
# Migrating `test`.`t`; Ghost table is `test`.`_t_gho`
# Migrating db01:3380; inspecting db02:3380; executing on db02
# Migration started at Wed Jun 17 17:15:00 +0800 2020
# chunk-size: 1000; max-lag-millis: 1500ms; dml-batch-size: 10; max-load: ; critical-load: ; nice-ratio: 0.000000
# throttle-additional-flag-file: /tmp/gh-ost.throttle
# Serving on unix socket: /tmp/gh-ost.test.t.sock
Copy: 0/1 0.0%; Applied: 0; Backlog: 0/1000; Time: 0s(total), 0s(copy); streamer: binlog.000007:3378; Lag: 0.02s, State: migrating; ETA: N/A
Copy: 0/0 100.0%; Applied: 0; Backlog: 0/1000; Time: 0s(total), 0s(copy); streamer: binlog.000007:3378; Lag: 0.02s, State: migrating; ETA: due
# Migrating `test`.`t`; Ghost table is `test`.`_t_gho`
# Migrating db01:3380; inspecting db02:3380; executing on db02
# Migration started at Wed Jun 17 17:15:00 +0800 2020
# chunk-size: 1000; max-lag-millis: 1500ms; dml-batch-size: 10; max-load: ; critical-load: ; nice-ratio: 0.000000
# throttle-additional-flag-file: /tmp/gh-ost.throttle
# Serving on unix socket: /tmp/gh-ost.test.t.sock
Copy: 0/0 100.0%; Applied: 0; Backlog: 0/1000; Time: 1s(total), 0s(copy); streamer: binlog.000007:9351; Lag: 0.02s, State: migrating; ETA: due
Copy: 0/0 100.0%; Applied: 0; Backlog: 0/1000; Time: 1s(total), 0s(copy); streamer: binlog.000007:9351; Lag: 0.02s, State: migrating; ETA: due
[2020/06/17 17:15:02] [info] binlogsyncer.go:164 syncer is closing...
[2020/06/17 17:15:02] [error] binlogstreamer.go:77 close sync with err: sync is been closing...
[2020/06/17 17:15:02] [info] binlogsyncer.go:179 syncer is closed
# Done

进度提示:

Copy: 0/1 0.0%; Applied: 0; Backlog: 0/1000; Time: 0s(total), 0s(copy); streamer: binlog.000007:3378; Lag: 0.02s, State: migrating; ETA: N/A
Copy: 0/1 0.0%; 1 是指要迁移的总行数,0是已经迁移的行数 0.0%迁移完成的百分比;
Applied: 0:是指在二进制日志中处理的event数量,0的意思是迁移表没有流量,没有处理的event
Backlog: 0/1000:表示我们在读取二进制日志方面表现良好,在二进制日志队列中没有任何积压(Backlog)事件。
treamer: binlog.000007:3378;表示当前已经应用到binlog文件位置

在执行DDL中,从库会执行一次stop/start slave,要是确定从的binlog是ROW的话可以添加参数:–assume-rbr。如果从库的binlog不是row,可以用参数–switch-to-rbr来转换成ROW,此时需要注意的是执行完毕之后,binlog模式不会被转换成原来的值。–assume-rbr和–switch-to-rbr参数不能一起使用。

在主库上执行DDL:

# gh-ost --user="root" --password="" --host=192.168.214.1  --database="test" --table="t"  --alter="ADD COLUMN cc2 varchar(10),add column cc3 int not null default 0 comment 'test' " --allow-on-master  --execute[root@db01 data]# gh-ost --user="root" --password="" --host=192.168.214.1 --port=3380  --database="test" --table="tt"  --alter="ADD COLUMN cc2 varchar(10),add column cc3 int not null default 0 comment 'test' " --allow-on-master  --execute
[2020/06/17 17:53:54] [info] binlogsyncer.go:133 create BinlogSyncer with config {99999 mysql 192.168.214.1 3380 root    false false <nil> false UTC true 0 0s 0s 0 false}
[2020/06/17 17:53:54] [info] binlogsyncer.go:354 begin to sync binlog from position (binlog.000001, 11485)
[2020/06/17 17:53:54] [info] binlogsyncer.go:203 register slave for master server 192.168.214.1:3380
[2020/06/17 17:53:54] [info] binlogsyncer.go:723 rotate to (binlog.000001, 11485)
# Migrating `test`.`tt`; Ghost table is `test`.`_tt_gho`
# Migrating `test`.`tt`; Ghost table is `test`.`_tt_gho`
# Migrating db01:3380; inspecting db01:3380; executing on db01
# Migrating db01:3380; inspecting db01:3380; executing on db01
# Migration started at Wed Jun 17 17:53:54 +0800 2020
# Migration started at Wed Jun 17 17:53:54 +0800 2020
# chunk-size: 1000; max-lag-millis: 1500ms; dml-batch-size: 10; max-load: ; critical-load: ; nice-ratio: 0.000000
# chunk-size: 1000; max-lag-millis: 1500ms; dml-batch-size: 10; max-load: ; critical-load: ; nice-ratio: 0.000000
# throttle-additional-flag-file: /tmp/gh-ost.throttle
# Serving on unix socket: /tmp/gh-ost.test.tt.sock
# throttle-additional-flag-file: /tmp/gh-ost.throttle
# Serving on unix socket: /tmp/gh-ost.test.tt.sock
Copy: 0/1 0.0%; Applied: 0; Backlog: 0/1000; Time: 0s(total), 0s(copy); streamer: binlog.000001:13640; Lag: 0.02s, State: migrating; ETA: N/A
Copy: 0/0 100.0%; Applied: 0; Backlog: 0/1000; Time: 0s(total), 0s(copy); streamer: binlog.000001:13640; Lag: 0.02s, State: migrating; ETA: due
# Migrating `test`.`tt`; Ghost table is `test`.`_tt_gho`
# Migrating db01:3380; inspecting db01:3380; executing on db01
# Migration started at Wed Jun 17 17:53:54 +0800 2020
# chunk-size: 1000; max-lag-millis: 1500ms; dml-batch-size: 10; max-load: ; critical-load: ; nice-ratio: 0.000000
# throttle-additional-flag-file: /tmp/gh-ost.throttle
# Serving on unix socket: /tmp/gh-ost.test.tt.sock
Copy: 0/0 100.0%; Applied: 0; Backlog: 0/1000; Time: 1s(total), 0s(copy); streamer: binlog.000001:19701; Lag: 0.01s, State: migrating; ETA: due
Copy: 0/0 100.0%; Applied: 0; Backlog: 0/1000; Time: 1s(total), 0s(copy); streamer: binlog.000001:19701; Lag: 0.01s, State: migrating; ETA: due
[2020/06/17 17:53:55] [info] binlogsyncer.go:164 syncer is closing...
[2020/06/17 17:53:55] [error] binlogstreamer.go:77 close sync with err: sync is been closing...
[2020/06/17 17:53:55] [info] binlogsyncer.go:179 syncer is closed
# Done

在DDL上进行测试:


[root@db02 opt]# gh-ost --user="root" --password="" --host=192.168.214.129  --database="test" --table="t" --port=3380   --alter="ADD COLUMN abc1 varchar(10),add column abc2 int not null default 0 comment 'test' " --test-on-replica  --switch-to-rbr --execute
## 参数--test-on-replica:在从库上测试gh-ost,包括在从库上数据迁移(migration),数据迁移完成后stop slave,原表和ghost表立刻交换而后立刻交换回来。继续保持stop slave,使你可以对比两张表。如果不想stop slave,则可以再添加参数:--test-on-replica-skip-replica-stop
上面三种是gh-ost操作模式,上面的操作中,到最后不会清理临时表,需要手动清理,再下次执行之前果然临时表还存在,则会执行失败,可以通过参数进行删除:
--initially-drop-ghost-table:gh-ost操作之前,检查并删除已经存在的ghost表。该参数不建议使用,请手动处理原来存在的ghost表。默认不启用该参数,gh-ost直接退出操作。
--initially-drop-old-table:gh-ost操作之前,检查并删除已经存在的旧表。该参数不建议使用,请手动处理原来存在的ghost表。默认不启用该参数,gh-ost直接退出操作。
--initially-drop-socket-file:gh-ost强制删除已经存在的socket文件。该参数不建议使用,可能会删除一个正在运行的gh-ost程序,导致DDL失败。
--ok-to-drop-table:gh-ost操作结束后,删除旧表,默认状态是不删除旧表,会存在_tablename_del表。

参数详解:

--user: mysql用户
--password: mysql密码
--host: 数据库地址
--database: 要操作的数据库
--table: 数据表
--alter:DDL语句
--allow-on-master:允许gh-ost直接运行在主库上,默认gh-ost连接的从库。
--execute:实际执行alter&migrate表,默认为noop,不执行,仅仅做测试并退出,如果想要ALTER TABLE语句真正落实到数据库中去,需要明确指定-execute
--max-load string:string是状态表达式,当设置多个状态值,用逗号分隔,如“threads_running=100,threads_connected=500”,当超过该值,迁移暂停等待;
--chunk-size:在每次迭代中处理的行数量,范围为(100-100000),默认是1000
--serve-socket-file:gh-ost的socket文件绝对路径;
-exact-rowcount:准确统计表行数(用select count(*)方式)得到更准确的时间;
--test-on-replica:在从库上测试gh-ost,包括在从库上数据迁移(migration),数据迁移完成后stop slave,原表和ghost表立刻交换而后立刻交换回来。继续保持stop slave,使你可以对比两张表。
--max-lag-millis:主从复制最大延迟时间,当主从复制延迟时间超过该值后,gh-ost将采取节流措施,默认值是1500s--ok-to-drop-table:gh-ost操作结束后,删除旧表,默认状态是不删除旧表,会存在_tablename_del表
--postpone-cut-over-flag-file string:当这个文件存在的时候,gh-ost的cut-over阶段将会被推迟,数据仍然在复制,直到该文件被删除。

终止,暂停,恢复文件:

gh-ost --user="root" --password="" --host=192.168.241.1 --port=3380  --database="test" --table="t"  --alter="ADD COLUMN o2 varchar(10),add column o1 int not null default 0 comment 'test' " --exact-rowcount --serve-socket-file=/tmp/gh-ost.t1.sock --panic-flag-file=/tmp/gh-ost.panic.t1.flag  --postpone-cut-over-flag-file=/tmp/ghost.postpone.t1.flag --allow-on-master  --execute

(1) 标示着文件终止:–panic-flag-file 创建文件终止运行,例子中创建/tmp/gh-ost.panic.t1.flag文件,终止正在运行的gh-ost,临时文件清理需要手动进行;

(2) 表示文件禁止cut-over进行,即禁止表名切换,数据复制正常进行。--postpone-cut-over-flag-file

创建文件延迟cut-over进行,即推迟切换操作。例子中创建**/tmp/ghost.postpone.t1.flag**文件,gh-ost 会完成行复制,但并不会切换表,它会持续的将原表的数据更新操作同步到临时表中。

(3) 使用socket监听请求,操作者可以在命令运行后更改相应的参数. --server-socket-file,server-tcp-port(默认)

暂停:echo throttle | socat - /tmp/gh-ost.test.t1.sock

恢复:echo no-throttle | socat - /tmp/gh-ost.test.t1.sock

修改限速:

echo chunk-size=100 | socat - /tmp/gh-ost.t1.sock
echo max-lag-millis=200 | socat - /tmp/gh-ost.t1.sock
echo max-load=Thread_running=3 | socat - /tmp/gh-ost.t1.sock

2.6 各个工具的总结对比

优点 缺点
online DDL 索引的创建和删除,修改默认值速度快,没有性能影响 a. 在高并发场景存在大量写请求执行ddl会有性能影响; b. 在请求量大时,有可能因为增量数据缓存不够而失败; c. 会造成较大的主备延迟; d. 当有大量写请求写入造成数据库load过高时,不能暂停
pt-osc 并发DML支持较好,不会出现请求过多失败的情况 a. 当被修改表的主键为自增字段时,在有大量的写请求可能会产生大 量的表锁从而影响性能; b. 对主键的更新操作会产生重复数据;c. 触发器带来的开销和风险; d. 处理外键里会带来风险;
gh-ost 对主机性能几乎没有影响 a. 不支持外键和触发器; b. 不支持对JSON格式,generated column的修改; c. 不能对表名字母大小但拼写相同的表进行修改(Mytable,myTable)d. 要求binlog格式一定是row格式; e.binlog解析时会有风险

2.7 sysbench压测online DDL,PT-OSC,gh-ost

环境:sysbench压测200w行200张单表,连接主机获取增量

测试online DDL:

测试pt-osc:


我们可以看到对于select执行前的QPS和执行后的QPS差距较小,对性能没有什么影响。但是update和insert 这种大量的写入的场景中延迟会比较大,如果表中不是自增字段,可以获得比较好的性能。如果在写入量较大的场景中,且线上环境有自增列的话,使用pt-osc会造成大量的自增锁,lock_mode=AUTO_INC,同时也跟innodb_autoinc_lock_mode的配置有关系。

auto_inc: 是一个表级锁,这种锁作用于SQL语句中而不是事务。只要语句执行完成,锁就会释放。

设置innodb_autonic_lock_mode参数可以达到性能与安全的平衡;

在子解此参数之前,我们先对自增长的插入进行划分:

a. insert-like:指所有插入语句。insert,replace,insert … select ,replace … select ,load data等;

b. simple inserts:是指能在插入前就确定插入行数的语句.insert into t(name) values (“aaa”);

c. builk inserts:指在插入前不能确定得到插入行数的语句。insert into t(id,name) values(1,‘a’),(2,‘b’);

d. mixed-mode insert:指插入中有一部分的值是自增长的,有一部分是确定的。如INSERT INTO t1(c1,c2) VALUES(1,‘a’),(NULL,‘b’),(5,‘c’),(NULL,‘d’);

  1. 当Innodb_autonic_lock_mode=0时,每次都会产生表锁:

不管上述哪种类型,每条sql语句都会产生auto-inc表锁,每次产生新的记录就会获取一个auto_increment值。此种情况下产生的insert_id是连续的,但是并发性较差;

  1. 当Innodb_autonic_lock_mode=1时

这一模式对simple insert做了优化,由于simple insert 一次性插入值的个数可以马上得到确定,所以Mysql可以一次生成几个连续的值,保证了基于语句的复制安全;

这个模式是默认模式,模式的好处是auto_inc锁不要一直保持到语句结束,只要语句得到了相应的值后就可以提前释放锁;

  1. innodb_autoinc_lock_mode=2时,不会锁表

这种方式并发性能最高,且不会通过auto-inc locking的方式。当然也会带来很多问题,因为在并发场景时,批量插入的值可能是不连续的。此外,基于最重要的是sbr会出现问题,因此,使用这个模式,任何都应该使用rbr模式,这样才能保证最大的并发及主从复制一致。

gh-ost测试:


测试结果:使用gh-ost对性能的影响是最小的,无触发器可以避免MDL锁和DML锁等待。

online-DDL详细原理介绍及gh-ost讲解相关推荐

  1. zk-SNARKs详细原理介绍(简单通俗易懂)

    zk-SNARKs 什么是zk-SNARKs? 1. 引言 2. 为什么zk-SNARKs "应该"是很难实现的? 3. zk-SNARKs基础概念 3.1 polynomial ...

  2. 施密特触发电路详细原理介绍

    施密特触发电路 施密特电路的结构和工作原理 由门电路组成的施密特触发电路 施密特触发电路的应用 波形变化 脉冲整形 脉冲鉴幅 我们想获得矩形脉冲,有种方法是可以采用整形的方法获得矩形脉冲,但前提是可以 ...

  3. sever串口wifi拓展板_串口Wifi模块的工作原理和详细功能介绍

    在无线网络领域里面,无线wifi是最火的名词.对于串口wifi模块的工作原理是什么呢?串口wifi模块又有什么功能呢?wifi方案设计远嘉科技给大家讲解有关串口wifi模块的工作原理,以及详细功能介绍 ...

  4. heartbeat原理介绍

    heartbeat原理介绍 HeartBeat运行于备用主机上的Heartbeat可以通过以太网连接检测主服务器的运行状态,一旦其无法检测到主服务器的"心跳"则自动接管主服务器的资 ...

  5. Qt 实现钢笔画线效果详细原理

    前言 上一篇文章:Qt 实现画线笔锋效果详细原理,根据这篇介绍的实现笔锋效果的原理,我们很容易实现另外一种笔效:钢笔. 所谓的钢笔笔效,就是真实还原钢笔书写出来的线条效果,其特征就是:根据笔的绘制速度 ...

  6. kafka集群原理介绍

    kafka集群原理介绍 @(KAFKA)[kafka, 大数据] kafka集群原理介绍 一基础理论 二配置文件 一java调优 二参数说明 三错误处理 四zookeeper中的内容 1brokers ...

  7. SAP Fiori Elements 框架里 Smart Table 控件的工作原理介绍

    这是 Jerry 2021 年的第 34 篇文章,也是汪子熙公众号总共第 310 篇原创文章. Jerry 前一篇文章 深入掌握 SAP Fiori Elements 工作原理系列之二:如何给 Fio ...

  8. OpenFlow和SDN的历史和原理介绍

    OpenFlow相关的历史.新闻:http://blog.csdn.net/jincm13/article/details/7825754 起源与发展 [https://36kr.com/p/5035 ...

  9. LVS原理介绍及安装过程

    一.ARP技术概念介绍 为什么讲ARP技术,因为平常工作中有接触.还有就是LVS的dr模式是用到arp的技术和数据. 1.什么是ARP协议 ARP协议全程地址解析协议(AddressResolutio ...

最新文章

  1. idea直连linux部署项目,idea项目打包和在linux的部署
  2. python中元组_理解python中的元组
  3. 【C#食谱】【杭帮菜】菜单2:写一个TCP客户端
  4. Linux kernel block device 的 submit_bio 都做了什么?
  5. erp沙盘采购总监的心得_erp沙盘模拟采购总监心得
  6. python中线条颜色_python中plot用法——线条、点、颜色
  7. 【STM32】窗口看门狗相关类型和函数
  8. flink的print()函数输出的都是对象地址而非对象内容
  9. 各个大学的录取通知书,哪个颜值最高?
  10. 开源社交系统ThinkSNS+和ThinkSNS V4区别在哪里
  11. 计算机的五大组成部分(计组学习一)
  12. Linux内存初始化(四) 创建系统内存地址映射
  13. python爬虫怎么赚钱-终于找到python爬虫怎么挣钱
  14. js系列教程11-json、ajax(XMLHttpRequest)、comet、SSE、WebSocket全解
  15. spring-cloud Sleuth
  16. Java类集框架 —— ArrayList源码分析
  17. 如何让研发薪酬更具有吸引力?华为、阿里、腾讯的方法值得借鉴
  18. go语言开发工具sublime text3 + gosublime配置
  19. 万字长文带你玩转2020全国大学生计算机技能应用大赛—C语言模考整理解析
  20. 如何在云计算平台上完成分子对接

热门文章

  1. 用vue实现类ant Design的日历组件
  2. MCAL系列介绍04-GPT
  3. linux iscsi 发起程序,设置iSCSI的发起程序(客户端)(三)
  4. 网上打印文件怎么发给商家?怎么给商家发送打印资料
  5. 堡塔APP 免费使用教程【图文教程】
  6. Spark SQL_JZZ158_MBY
  7. 做自媒体如何快速实现财务自由
  8. 【MISCCFTSHOW总结】
  9. 【数据分析案例】美国2015-2018年牛油果销售数据分析
  10. 展示 用户画像_案例分析:如何建立一个产品的用户画像?