众所周知,很多互联网业务都面临着无法停机,需要在线变更数据库结构的情况。但是在线修改数据量较大的表,可能对线上业务产生较大影响,比如:
-
在线修改大表的表结构执行时间往往不可预估,一般时间较长。
-
由于修改表结构是表级锁,因此在修改表结构时,影响表写入操作。
-
如果长时间的修改表结构,中途修改失败,由于修改表结构是一个事务,因此失败后会还原表结构,在这个过程中表都是锁着不可写入。
-
修改大表结构容易导致数据库 CPU、IO 等性能消耗,使 MySQL 服务器性能降低。
-
在线修改大表结构容易导致主从延时,从而影响业务读取。
Percona-Toolkit 源自 Maatkit 和 Aspersa 工具,这两个工具是管理 MySQL 的最有名的工具,但 Maatkit 已经不维护了,全部归并到 Percona-Toolkit。Percona Toolkit 是一组高级的命令行工具,用来管理 MySQL 和系统任务,主要包括以下功能:
-
验证主节点和复制数据的一致性
-
有效的对记录行进行归档
-
找出重复的索引
-
总结 MySQL 服务器
-
从日志和 tcpdump 中分析查询
-
问题发生时收集重要的系统信息
-
在线修改表结构
pt-online-schema-change 是 Percona-Toolkit 工具集中的一个组件,很多 DBA 在使用 Percona-Toolkit 时第一个使用的工具就是它,同时也是使用最频繁的一个工具。它可以做到在修改表结构的同时(即进行 DDL 操作)不阻塞数据库表 DML 的进行,这样降低了对生产环境数据库的影响。
在 MySQL 5.6.7 之前是不支持 Online DDL 特性的,即使在添加二级索引的时候有 FIC 特性,但是在修改表字段的时候还是会有锁表并阻止表的 DML 操作。这样对于 DBA 来说是非常痛苦的,好在有 pt-online-schema-change 工具在没有 Online DDL 时解决了这一问题,pt-online-schema-change 其主要特点就是在数据库结构修改过程中不会造成读写阻塞。
pt-online-schema-change 安装
pt-online-schema-change 安装非常简单,官方已经为我们准备好了各主流平台的安装包,只需下载对应版本安装即可。目前最新版本是 3.1.0,这里我们以 CentOS 7 为例:
1 | # 安装相关依赖包 |
更多平台的安装包可以直接在官网地址下载:https://www.percona.com/downloads/percona-toolkit/LATEST/
pt-online-schema-change 语法说明
- 常用选项说明
1 | $ pt-online-schema-change [OPTIONS] DSN |
- DSN 选项(DSN)
可以使用 DSN 方式来连接数据库,DSN 选项为 key=value 方式,在等号的两侧不能有空格出现,并且区分大小写,多个选项之前以’,’(逗号)隔开,主要选项如下:
-
A 指定字符集
-
D 指定变更表所在数据库
-
t 指定需要变更的表
-
h 指定要连接的 HOST
-
P 指定要连接的 PORT
-
S 指定连接所使用的 SOCKET 文件(Unix systems)
-
u 指定连接的用户名
-
p 指定连接的用户名密码
示例:
1 | h=192.168.58.3,P=3306,D=employees,t=employees |
pt-online-schema-change 使用限制
-
要求需要执行变更的表有主键 (Primary key) 或唯一索引 (Unique index),否则工具会执行失败,参考选项 --alter 说明;
-
如果检测到表有外键约束 (Foreign key),工具除非选项 --alter-foreign-keys-method,否则不会执行变更;
-
如果检测到主从复制中存在过滤,则工具不会执行,参考选项 --[no]check-replication-filters 说明;
-
如果检测到主从复制有延迟,则工具有可能会暂停数据拷贝,参考选项 --max-lag 说明;
-
如果检测到连接当前服务器负载过高,则工具有可能暂停执行或中止退出,参考选项 --max-load 各 --critical-load 说明。
pt-online-schema-change 使用实例
- 测试数据准备
本文基于 MySQL 官方示例数据库 employee:Example Databases 进行测试。
1 | -- employees: |
- 添加一个字段
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=employees --user=admin --ask-pass --alter "add comment varchar(50) not null default 'pt-osc'" --charset=utf8 |
因为 employees 表中的 emp_no 字段被其他表外建关联,以下命令执行时会报如下错误:
1 | You did not specify --alter-foreign-keys-method, but there are foreign keys that reference the table. Please read the tool's documentation carefully. |
根据报错信息的提示,加入选项 --alter-foreign-keys-method
重新执行并通过选项 --dry-run
查看执行过程主要信息:
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=employees --user=admin --ask-pass --alter "add comment varchar(50) not null default 'pt-osc'" --alter-foreign-keys-method=auto --charset=utf8 --dry-run |
- 修改一个字段
将表 employees 的 comment 字段的字符集修改为 utf8mb4。
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=employees --user=admin --ask-pass --alter "modify column comment varchar(50) character set utf8mb4" --alter-foreign-keys-method=auto --charset=utf8 --execute |
- 删除字段
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=employees --user=admin --ask-pass --alter "drop column comment" --alter-foreign-keys-method=auto --charset=utf8 --execute |
- 添加索引
为表 dept_emp 的字段 from_date 和 to_date 创建复合索引 idx_fr_to_date。
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=dept_emp --user=admin --ask-pass --alter "add index idx_fr_to_date(from_date,to_date)" --alter-foreign-keys-method=auto --charset=utf8 --execute |
- 删除索引
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=dept_emp --user=admin --ask-pass --alter "drop index idx_fr_to_date" --alter-foreign-keys-method=auto --charset=utf8 --execute |
- 修改字段允许 NULL
将表 dept_emp 的字段 to_date 指定为允许 NULL。
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=dept_emp --user=admin --ask-pass --alter "modify column to_date date null" --alter-foreign-keys-method=auto --charset=utf8 --execute |
- 修改字段不允许 NULL (NOT NULL)
为表 employees 添加字段 ptosc_num 并允许 NULL,字段类型为 int,没有指定默认值。
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=employees --user=admin --ask-pass --alter "add ptosc_num int null" --alter-foreign-keys-method=auto --charset=utf8 --execute |
修改字段 ptosc_num 为不允许 NULL (NOT NULL),需要通过指定选项 --null-to-not-null
,否则会报错。
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=employees --user=admin --ask-pass --alter "modify column ptosc_num int not null" --alter-foreign-keys-method=auto --null-to-not-null --charset=utf8 --execute |
- 删除外键
需要为外键指定名称为 _forigen_key,因为在创建新表时候默认为新表上的外键创建这样的名称,如果没这样指定则无法删除。
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=dept_emp --user=admin --ask-pass --alter "drop foreign key _dept_emp_ibfk_1" --alter-foreign-keys-method=auto --charset=utf8 --execute |
- 重建表
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=employees --user=admin --ask-pass --alter "engine=InnoDB" --alter-foreign-keys-method=auto --charset=utf8 --execute |
- 变更后保留旧表
如果是涉及外键关联的父表进行变更,则建议选项 --alter-foreign-keys-method=rebuild_constraints
,这样在子表中会重命名外键约束名,如果选项 --alter-foreign-keys-method
有可能取值 drop_swap 时,则会强制使用选项 --no-swap-tables
和--no-drop-old-table
,其中 --no-swap-tables
并不会有旧表的产生,就不存在保留之说了。
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=dept_emp --user=admin --ask-pass --alter "add comment varchar(50) notnull default 'pt-osc'" --no-drop-old-table --charset=utf8 --execute |
以上语句执行完成后会在数据库中生成名为 _dept_emp_old 的表,即变更之前的旧表。
- 变更后保留新表
顾名思义,就是先做一次完整的表变更操作,但是不进行旧表与新表的交换,也不删除变更之后的新表,通过指定选项 --no-drop-new-table
和 --no-swap-tables
实现,可以通过选项 --new-table-name
指定新表名,当选项 --alter-foreign-keys-method=drop_swap
时,--no-drop-new-table
不生效,与保留旧表的情形一致。
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=dept_emp --user=admin --ask-pass --alter "add comment varchar(50) notnull default 'pt-osc'" --no-drop-new-table --no-swap-tables --new-table-name='dept_emp_bak' --charset=utf8 --execute |
以上语句执行完成后会在数据库中生成名为 dept_emp_bak 的表,即变更之后的新表,但对旧表不会做任何修改。
- 添加主键
如果是 InnoDB
表没有主键,真的不敢想像啊,但还是要进行测式下。这里测试基于 employees 表创建 employees_ptosc 表:
1 | mysql root@localhost:employees> create table employees_ptosc as select * from employees; |
对 employees_ptosc 表添加主键:
1 | -- 如果 employees_ptosc 表没有任何索引和约束会报如下信息,工具执行失败 |
使用选项 --no-check-unique-key-change
再次执行添加主键操作:
1 | # pt-online-schema-change h=192.168.58.3,P=3306,D=employees,t=employees_ptosc --user=admin --ask-pass --alter "add primary key(emp_no)" --charset=utf8 --no-check-unique-key-change --charset=utf8 --execute |
pt-online-schema-change 工作流程
为了了解 pt-online-schema-change 工具是如何做到不阻塞 DML 的,还是通过 General log 来了解。
以添加字段的执行语句获得的 General log 为例说明:
1 | -- 初始的一些检查数据库参数、负载信息这里不再细说。 |
整个工作流程总结如下:
-
查询当前数据库服务器信息,包括参数设置,负载信息等,判断表是否有存在触发器,是否有外键关联;
-
创建一张与旧表结构相同的新表,表名为_旧表名;
-
在新创建的表上做变更操作;
-
旧表上创建 DELETE、UPDATE、INSERT 3 个触发器;
-
拷贝旧表数据到新表上,以 chunk 为单位进行,拷贝期间涉及的行会持有共享读锁;
-
拷贝期间如果旧表如有 DML 操作,则通过触发器更新同步到新表上;
-
当拷贝数据完成之后旧表与新表进行重命名;
-
如果有涉及到外键,根据工具指定选项进行外键处理;
-
删除旧表;
-
删除旧表上触发器。
总结
当业务量较大时,修改操作会等待没有数据修改后,执行最后的 rename 操作。因此,在修改表结构时,应该尽量选择在业务相对空闲时,至少修改表上的数据操作较低时,执行较为妥当。由于可能存在一定的风险,在操作之前,建议对数据表进行备份,可以使得操作更安全、可靠。
pt-online-schema-change 工具对于任意的 DDL 语句都是通过创新表拷贝数据来进行,期间都支持 DML,而 Online DDL 根据 DDL 类型的来区分是否需要对表进行 COPY TABLE 操作,有点类似于工具的创建临时表进行变更,而不需要 COPY TABLE 操作的 DDL 语句在执行期间支持DML。
关于在对表进行 DDL 时使用 MySQL 原生的 Online DDL 特性还是使用 pt-online-schema-change 工具,通过以上对工具使用的说明与用法测试可以总结如下:
-
如果 MySQL 版本不支持 Online DDL 特性,比如早于 5.6 版本的 MySQL,则使用 pt-online-schema-change 工具;
-
如果 MySQL 版本支持 Online DDL 特性,则优先考虑使用 Online DDL,因为毕竟原生的支持较好,同时不容易产生不可预知的错误;
-
如果 DDL 语句在使用 Online DDL 时需要进行 COPY TABLE 操作,建议使用 pt-online-schema-change 工具,因为期间支持 DML 操作。
-
如果表存在触发器的情况下,优先使用 Online DDL,对于 MySQL 5.7.2 之后版本则可以 pt-online-schema-change 工具并通过指定选项 --preserve-triggers;
-
如果涉及外键关联的表,优先考虑使用 Online DDL。