[TOC]
1-概述
pt-online-schema-change 它可以做到在修改表结构的同时(即进行DDL操作)不阻塞数据库表DML的进行,这样降低了对生产环境数据库的影响。在MySQL5.6之前是不支持Online DDL特性的,在修改表字段的时候会有锁表并阻止表的DML操作,pt-online-schema-change工具在没有Online DDL时解决了这一问题。
当前大部分工作都能使用Online DDL进行操作
官方文档:https://www.percona.com/doc/percona-toolkit/LATEST/pt-online-schema-change.html
1.1 工作原理
- 查询当前数据库服务器信息,包括参数设置,负载信息等,判断表是否有存在触发器,是否有外键关联;
- 创建一张与旧表结构相同的新表,表名为
_旧表名
; - 在新创建的表上做变更操作;
- 旧表上创建
DELETE
、UPDATE
、INSERT
3个触发器; - 拷贝旧表数据到新表上,以
chunk
为单位进行,拷贝期间涉及的行会持有共享读锁; - 拷贝期间如果旧表如有
DML
操作,则通过触发器更新同步到新表上; - 当拷贝数据完成之后旧表与新表进行重命名;
- 如果有涉及到外键,根据工具指定选项进行外键处理;
- 删除旧表;
- 删除旧表上触发器。
2-安装
2.1-下载
下载工具包,里面包含所有pt工具集
# 下载地址
https://www.percona.com/downloads/percona-toolkit/LATEST
# 下载
wget https://www.percona.com/downloads/percona-toolkit/3.2.0/binary/redhat/7/x86_64/percona-toolkit-3.2.0-1.el7.x86_64.rpm
2.2-安装
rpm -ivh percona-toolkit-3.2.0-1.el7.x86_64.rpm
3-使用限制与建议
3.1-限制
- 被执行变更的表有主键或唯一键,否则会执行失败
- 如果检测到表有外键约束,则需要添加
--alter-foreign-keys-method
选项,否则不会执行 - 如果检测到主从复制中存在过滤,则工具不会执行,参考选项
--[no]check-replication-filters
说明; - 如果检测到主从复制有延迟,则工具有可能会暂停数据拷贝,参考选项
--max-lag
说明; - 如果检测到连接当前服务器负载过高,则工具有可能暂停执行或中止退出,参考选项
--max-load
各--critical-load
说明。 - 源表上不要有触发器
- 在配置从库延迟检查时候
--check-slave-lag
,需要配置--max-lag
,延迟低于--max-lag
时才继续工作,否则暂停
3.2-建议
- 如果MySQL支持Online DDL特性,优先考虑使用Online DDL
- 如果MySQL版本不支持Online DDL特性,比如早于5.6版本的MySQL,则使用pt-online-schema-change工具
- 如果DDL语句在使用Online DDL时需要进行COPY TABLE操作,建议使用pt-online-schema-change工具,因为期间支持DML操作
- 如果表存在触发器的情况下,优先使用Online DDL,对于MySQL5.7.2之后版本则可以pt-online-schema-change工具并通过指定选项
--preserve-triggers
- 如果涉及外键关联的表,优先考虑使用Online DDL
Online DDL :https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html
4-使用示例
常用参数介绍
名称 | 描述 |
---|---|
--dry-run | 这个参数不建立触发器,不拷贝数据,也不会替换原表。只是创建和更改新表。 |
--execute | 这个参数的作用和前面工作原理的介绍的一样,会建立触发器,来保证最新变更的数据会影响至新表。 注意:如果不加这个参数,这个工具会在执行一些检查后退出。 |
--critical-load | 每次chunk操作前后,会根据show global status统计指定的状态量的变化,默认是统计Thread_running。 目的是为了安全,防止原始表上的触发器引起负载过高。 这也是为了防止在线DDL对线上的影响。 超过设置的阀值,就会终止操作,在线DDL就会中断。提示的异常如上报错信息 |
--max-load | 选项定义一个阀值,在每次chunk操作后,查看show global status状态值是否高于指定的阀值。 该参数接受一个mysql status状态变量以及一个阀值,如果没有给定阀值,则定义一个阀值为为高于当前值的20%。 注意这个参数不会像--critical-load终止操作,而只是暂停操作。 当status值低于阀值时,则继续往下操作。 是暂停还是终止操作这是--max-load和--critical-load的差别 |
--charset=utf8 | 连接到MySQL后运行SET NAMES UTF8 |
--check-replication-filters | 检查复制中是否设置了过滤条件,如果设置了,程序将退出 |
--nocheck-replication-filters | 不检查复制中是否设置了过滤条件 |
--set-vars | 设置mysql的变量值 |
--check-slave-lag | 检查主从延迟 |
--chunk-size | 默认值:1000,为每个复制的块选择的行数。 |
--[no]drop-old-table | 默认值:是;重命名后删除原始表。 成功重命名原始表以让新表取代它之后,如果没有错误,该工具将默认删除原始表。 如果有任何错误,该工具会将原始表保留在原位。 |
注意:
如果异常中断,则需要删除原表上已创建的触发器与创建出的新表“_XXXXX”
4.1-测试数据结构
mysql> show create table data_million_small\G
*************************** 1. row ***************************
Table: data_million_small
Create Table: CREATE TABLE `data_million_small` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` varchar(255) DEFAULT NULL,
`b` varchar(255) DEFAULT NULL,
`c` varchar(255) DEFAULT NULL,
`d` varchar(255) DEFAULT NULL,
`e` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_a` (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> select count(*) from data_million_small;
+----------+
| count(*) |
+----------+
| 100000 |
+----------+
1 row in set (0.01 sec)
mysql> select version();
+------------+
| version() |
+------------+
| 5.7.25-log |
+------------+
1 row in set (0.00 sec)
4.2-添加字段【可使用Online DDL特性替换】
pt-online-schema-change h=192.168.66.101,P=3306,D=ytest,t=data_million_small --user=root --ask-pass --alter "add comment varchar(50)" --charset=utf8 --execute
限制活跃连接超出25即停止pt-osc操作,--critical-load=Threads_running=25
4.3-修改字段
pt-online-schema-change h=192.168.66.101,P=3306,D=ytest,t=data_million_small --user=root --ask-pass --alter "modify column comment varchar(50) character set utf8mb4" --execute
4.3-删除字段【可使用Online DDL特性替换】
pt-online-schema-change h=192.168.66.101,P=3306,D=ytest,t=data_million_small --user=root --ask-pass --alter "drop column comment,drop column comment2" --execute
4.4-添加索引【可使用Online DDL特性替换】
pt-online-schema-change h=192.168.66.101,P=3306,D=ytest,t=data_million_small --user=root --ask-pass --alter "add index idx_b(b)" --execute
4.5-删除索引【可使用Online DDL特性替换】
pt-online-schema-change h=192.168.66.101,P=3306,D=ytest,t=data_million_small --user=root --ask-pass --alter "drop index idx_b" --execute
4.6-修改字段为null/not null【可使用Online DDL特性替换】
# 改为null
pt-online-schema-change h=192.168.66.101,P=3306,D=ytest,t=data_million_small --user=root --ask-pass --alter "modify column c varchar(255) default null" --execute
# 改为 not null
pt-online-schema-change h=192.168.66.101,P=3306,D=ytest,t=data_million_small --user=root --ask-pass --alter "modify column c varchar(255) not null" --execute
4.7-添加主键【可使用Online DDL特性替换】
pt-online-schema-change h=192.168.66.101,P=3306,D=ytest,t=data_million_small --user=root --ask-pass --alter "add primary key(id)" --execute
4.8-删除主键【锁表,推荐使用pt-online-schema-change】
pt-online-schema-change h=192.168.66.101,P=3306,D=ytest,t=data_million_small --user=root --ask-pass --alter "drop primary key" --execute
4.9-重建表
pt-online-schema-change h=192.168.66.101,P=3306,D=ytest,t=data_million_small --user=root --ask-pass --alter "engine=InnoDB" --execute
4.8-保留变更的新旧表
# 保留旧表
pt-online-schema-change h=192.168.66.101,P=3306,D=ytest,t=data_million_small --user=root --ask-pass --alter "add comment varchar(50)" --no-drop-old-table --execute
# 保留新表,
pt-online-schema-change h=192.168.66.101,P=3306,D=ytest,t=data_million_small --user=root --ask-pass --alter "add comment2 varchar(50)" --no-drop-new-table --no-swap-tables --new-table-name='data_million_small_newt' --execute
5-原理
-- 初始的一些检查数据库参数、负载信息
2020-05-07 10:44:52.164136 453 [email protected] on ytest using TCP/IP
2020-05-07 10:44:52.185222 453 SHOW VARIABLES LIKE 'innodb\_lock_wait_timeout'
2020-05-07 10:44:52.188090 453 SET SESSION innodb_lock_wait_timeout=1
2020-05-07 10:44:52.188617 453 SHOW VARIABLES LIKE 'lock\_wait_timeout'
2020-05-07 10:44:52.190239 453 SET SESSION lock_wait_timeout=60
2020-05-07 10:44:52.190730 453 SHOW VARIABLES LIKE 'wait\_timeout'
2020-05-07 10:44:52.192305 453 SET SESSION wait_timeout=10000
2020-05-07 10:44:52.192933 453 SELECT @@SQL_MODE
2020-05-07 10:44:52.193432 453 SET @@SQL_QUOTE_SHOW_CREATE = 1/*!40101, @@SQL_MODE='NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'*/
2020-05-07 10:44:52.193927 453 SELECT VERSION()
2020-05-07 10:44:52.194421 453 SHOW VARIABLES LIKE 'character_set_server'
2020-05-07 10:44:52.196770 453 SELECT @@server_id /*!50038 , @@hostname*/
2020-05-07 10:44:52.198925 454 [email protected] on ytest using TCP/IP
2020-05-07 10:44:52.199614 454 SHOW VARIABLES LIKE 'innodb\_lock_wait_timeout'
2020-05-07 10:44:52.202254 454 SET SESSION innodb_lock_wait_timeout=1
2020-05-07 10:44:52.209802 454 SHOW VARIABLES LIKE 'lock\_wait_timeout'
2020-05-07 10:44:52.212032 454 SET SESSION lock_wait_timeout=60
2020-05-07 10:44:52.212604 454 SHOW VARIABLES LIKE 'wait\_timeout'
2020-05-07 10:44:52.214144 454 SET SESSION wait_timeout=10000
2020-05-07 10:44:52.214597 454 SELECT @@SQL_MODE
2020-05-07 10:44:52.215036 454 SET @@SQL_QUOTE_SHOW_CREATE = 1/*!40101, @@SQL_MODE='NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'*/
2020-05-07 10:44:52.215497 454 SELECT VERSION()
2020-05-07 10:44:52.215957 454 SHOW VARIABLES LIKE 'character_set_server'
2020-05-07 10:44:52.217592 454 SELECT @@server_id /*!50038 , @@hostname*/
2020-05-07 10:44:52.218198 453 SHOW VARIABLES LIKE 'wsrep_on'
2020-05-07 10:44:52.220235 453 SHOW VARIABLES LIKE 'version%'
2020-05-07 10:44:52.221924 453 SHOW ENGINES
2020-05-07 10:44:52.222691 453 SHOW VARIABLES LIKE 'innodb_version'
2020-05-07 10:44:52.224824 453 SHOW VARIABLES LIKE 'innodb_stats_persistent'
2020-05-07 10:44:52.226627 453 SELECT @@SERVER_ID
2020-05-07 10:44:52.227133 453 SHOW GRANTS FOR CURRENT_USER()
2020-05-07 10:44:52.227622 453 SHOW FULL PROCESSLIST
2020-05-07 10:44:52.228280 453 SHOW SLAVE HOSTS
2020-05-07 10:44:52.229008 453 SHOW GLOBAL STATUS LIKE 'Threads_running'
2020-05-07 10:44:52.230489 453 SHOW GLOBAL STATUS LIKE 'Threads_running'
2020-05-07 10:44:52.231733 453 SELECT CONCAT(@@hostname, @@port)
-- 查看需要执行变更的表状态
2020-05-07 10:44:52.232574 453 SHOW TABLES FROM `ytest` LIKE 'data\_million\_small'
2020-05-07 10:44:52.233211 453 SELECT VERSION()
-- 查看表是否存在触发器
2020-05-07 10:44:52.233722 453 SHOW TRIGGERS FROM `ytest` LIKE 'data\_million\_small'
2020-05-07 10:44:52.234577 453 /*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, @@SQL_MODE := '', @OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, @@SQL_QUOTE_SHOW_CREATE := 1 */
-- 查看表的建表语句
2020-05-07 10:44:52.235007 453 USE `ytest`
2020-05-07 10:44:52.235498 453 SHOW CREATE TABLE `ytest`.`data_million_small`
2020-05-07 10:44:52.236206 453 /*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, @@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */
-- 查询表的执行计划,确定表是否有外键关联
2020-05-07 10:44:52.237035 453 EXPLAIN SELECT * FROM `ytest`.`data_million_small` WHERE 1=1
2020-05-07 10:44:52.237856 453 SELECT table_schema, table_name FROM information_schema.key_column_usage WHERE referenced_table_schema='ytest' AND referenced_table_name='data_million_small'
2020-05-07 10:44:52.251785 453 SHOW VARIABLES LIKE 'version%'
2020-05-07 10:44:52.254181 453 SHOW ENGINES
2020-05-07 10:44:52.254934 453 SHOW VARIABLES LIKE 'innodb_version'
2020-05-07 10:44:52.256860 453 SELECT table_schema, table_name FROM information_schema.key_column_usage WHERE referenced_table_schema='ytest' AND referenced_table_name='data_million_small'
2020-05-07 10:44:52.266957 453 SHOW VARIABLES LIKE 'wsrep_on'
2020-05-07 10:44:52.286189 453 /*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, @@SQL_MODE := '', @OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, @@SQL_QUOTE_SHOW_CREATE := 1 */
2020-05-07 10:44:52.287559 453 USE `ytest`
2020-05-07 10:44:52.290402 453 SHOW CREATE TABLE `ytest`.`data_million_small`
2020-05-07 10:44:52.293105 453 /*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, @@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */
-- 创建'_'(下划线)开头相同表结构的新表,并先在新表上执行变更操作
2020-05-07 10:44:52.297212 453 CREATE TABLE `ytest`.`_data_million_small_new` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` varchar(255) DEFAULT NULL,
`b` varchar(255) DEFAULT NULL,
`c` varchar(255) NOT NULL,
`d` varchar(255) DEFAULT NULL,
`e` varchar(255) DEFAULT NULL,
`comment3` varchar(50) DEFAULT NULL,
`comment` varchar(50) DEFAULT NULL,
`comment5` varchar(50) DEFAULT NULL,
`comment6` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_a` (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET
2020-05-07 10:44:52.396325 453 ALTER TABLE `ytest`.`_data_million_small_new` add comment7 varchar(50)
2020-05-07 10:44:52.533918 453 /*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, @@SQL_MODE := '', @OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, @@SQL_QUOTE_SHOW_CREATE := 1 */
-- 查看原表上是否有触发器
2020-05-07 10:44:52.534507 453 USE `ytest`
2020-05-07 10:44:52.535005 453 SHOW CREATE TABLE `ytest`.`_data_million_small_new`
2020-05-07 10:44:52.535651 453 /*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, @@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */
2020-05-07 10:44:52.537445 453 SELECT TRIGGER_SCHEMA, TRIGGER_NAME, DEFINER, ACTION_STATEMENT, SQL_MODE, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, EVENT_MANIPULATION, ACTION_TIMING FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_MANIPULATION = 'DELETE' AND ACTION_TIMING = 'AFTER' AND TRIGGER_SCHEMA = 'ytest' AND EVENT_OBJECT_TABLE = 'data_million_small'
2020-05-07 10:44:52.538626 453 SELECT TRIGGER_SCHEMA, TRIGGER_NAME, DEFINER, ACTION_STATEMENT, SQL_MODE, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, EVENT_MANIPULATION, ACTION_TIMING FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_MANIPULATION = 'UPDATE' AND ACTION_TIMING = 'AFTER' AND TRIGGER_SCHEMA = 'ytest' AND EVENT_OBJECT_TABLE = 'data_million_small'
2020-05-07 10:44:52.539597 453 SELECT TRIGGER_SCHEMA, TRIGGER_NAME, DEFINER, ACTION_STATEMENT, SQL_MODE, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, EVENT_MANIPULATION, ACTION_TIMING FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_MANIPULATION = 'INSERT' AND ACTION_TIMING = 'AFTER' AND TRIGGER_SCHEMA = 'ytest' AND EVENT_OBJECT_TABLE = 'data_million_small'
2020-05-07 10:44:52.540487 453 SELECT TRIGGER_SCHEMA, TRIGGER_NAME, DEFINER, ACTION_STATEMENT, SQL_MODE, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, EVENT_MANIPULATION, ACTION_TIMING FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_MANIPULATION = 'DELETE' AND ACTION_TIMING = 'BEFORE' AND TRIGGER_SCHEMA = 'ytest' AND EVENT_OBJECT_TABLE = 'data_million_small'
2020-05-07 10:44:52.541423 453 SELECT TRIGGER_SCHEMA, TRIGGER_NAME, DEFINER, ACTION_STATEMENT, SQL_MODE, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, EVENT_MANIPULATION, ACTION_TIMING FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_MANIPULATION = 'UPDATE' AND ACTION_TIMING = 'BEFORE' AND TRIGGER_SCHEMA = 'ytest' AND EVENT_OBJECT_TABLE = 'data_million_small'
2020-05-07 10:44:52.542383 453 SELECT TRIGGER_SCHEMA, TRIGGER_NAME, DEFINER, ACTION_STATEMENT, SQL_MODE, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, EVENT_MANIPULATION, ACTION_TIMING FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_MANIPULATION = 'INSERT' AND ACTION_TIMING = 'BEFORE' AND TRIGGER_SCHEMA = 'ytest' AND EVENT_OBJECT_TABLE = 'data_million_small'
-- 在原表上分别创建DELETE、UPDATE、INSERT三个触发器
2020-05-07 10:44:52.543320 453 CREATE TRIGGER `pt_osc_ytest_data_million_small_del` AFTER DELETE ON `ytest`.`data_million_small` FOR EACH ROW DELETE IGNORE FROM `ytest`.`_data_million_small_new` WHERE `ytest`.`_data_million_small_new`.`id` <=> OLD.`id`
2020-05-07 10:44:52.571388 453 CREATE TRIGGER `pt_osc_ytest_data_million_small_upd` AFTER UPDATE ON `ytest`.`data_million_small` FOR EACH ROW BEGIN DELETE IGNORE FROM `ytest`.`_data_million_small_new` WHERE !(OLD.`id` <=> NEW.`id`) AND `ytest`.`_data_million_small_new`.`id` <=> OLD.`id`;REPLACE INTO `ytest`.`_data_million_small_new` (`id`, `a`, `b`, `c`, `d`, `e`, `comment3`, `comment`, `comment5`, `comment6`) VALUES (NEW.`id`, NEW.`a`, NEW.`b`, NEW.`c`, NEW.`d`, NEW.`e`, NEW.`comment3`, NEW.`comment`, NEW.`comment5`, NEW.`co` -- ...略
2020-05-07 10:44:52.596528 453 CREATE TRIGGER `pt_osc_ytest_data_million_small_ins` AFTER INSERT ON `ytest`.`data_million_small` FOR EACH ROW REPLACE INTO `ytest`.`_data_million_small_new` (`id`, `a`, `b`, `c`, `d`, `e`, `comment3`, `comment`, `comment5`, `comment6`) VALUES (NEW.`id`, NEW.`a`, NEW.`b`, NEW.`c`, NEW.`d`, NEW.`e`, NEW.`comment3`, NEW.`comment`, NEW.`comment5`, NEW.`comment6`)
-- 根据执行计划判断chunk包含的行数,以chunk数为单位拷贝数据,为在拷贝过程中为这些行加共享读锁
2020-05-07 10:44:52.621818 453 EXPLAIN SELECT * FROM `ytest`.`data_million_small` WHERE 1=1
2020-05-07 10:44:52.623608 453 SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `ytest`.`data_million_small` FORCE INDEX(`PRIMARY`) ORDER BY `id` LIMIT 1 /*first lower boundary*/
2020-05-07 10:44:52.624543 453 SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `ytest`.`data_million_small` FORCE INDEX (`PRIMARY`) WHERE `id` IS NOT NULL ORDER BY `id` LIMIT 1 /*key_len*/
2020-05-07 10:44:52.625181 453 EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ * FROM `ytest`.`data_million_small` FORCE INDEX (`PRIMARY`) WHERE `id` >= '1' /*key_len*/
2020-05-07 10:44:52.625944 453 EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `ytest`.`data_million_small` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1')) ORDER BY `id` LIMIT 999, 2 /*next chunk boundary*/
2020-05-07 10:44:52.626592 453 SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `ytest`.`data_million_small` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1')) ORDER BY `id` LIMIT 999, 2 /*next chunk boundary*/
2020-05-07 10:44:52.627433 453 EXPLAIN SELECT `id`, `a`, `b`, `c`, `d`, `e`, `comment3`, `comment`, `comment5`, `comment6` FROM `ytest`.`data_million_small` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1')) AND ((`id` <= '1000')) LOCK IN SHARE MODE /*explain pt-online-schema-change 18112 copy nibble*/
2020-05-07 10:44:52.628189 453 INSERT LOW_PRIORITY IGNORE INTO `ytest`.`_data_million_small_new` (`id`, `a`, `b`, `c`, `d`, `e`, `comment3`, `comment`, `comment5`, `comment6`) SELECT `id`, `a`, `b`, `c`, `d`, `e`, `comment3`, `comment`, `comment5`, `comment6` FROM `ytest`.`data_million_small` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1')) AND ((`id` <= '1000')) LOCK IN SHARE MODE /*pt-online-schema-change 18112 copy nibble*/
-- 每次拷贝完chunk中数据后,查看是否有警告,查看服务器的负载情况,这是在每个chunk拷贝完成后进行的
2020-05-07 10:44:52.681451 453 SHOW WARNINGS
2020-05-07 10:44:52.682218 453 SHOW GLOBAL STATUS LIKE 'Threads_running'
2020-05-07 10:44:52.683950 453 EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `ytest`.`data_million_small` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1001')) ORDER BY `id` LIMIT 9404, 2 /*next chunk boundary*/
2020-05-07 10:44:52.684658 453 SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `ytest`.`data_million_small` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1001')) ORDER BY `id` LIMIT 9404, 2 /*next chunk boundary*/
2020-05-07 10:44:52.694194 453 EXPLAIN SELECT `id`, `a`, `b`, `c`, `d`, `e`, `comment3`, `comment`, `comment5`, `comment6` FROM `ytest`.`data_million_small` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1001')) AND ((`id` <= '10405')) LOCK IN SHARE MODE /*explain pt-online-schema-change 18112 copy nibble*/
2020-05-07 10:44:52.699243 453 INSERT LOW_PRIORITY IGNORE INTO `ytest`.`_data_million_small_new` (`id`, `a`, `b`, `c`, `d`, `e`, `comment3`, `comment`, `comment5`, `comment6`) SELECT `id`, `a`, `b`, `c`, `d`, `e`, `comment3`, `comment`, `comment5`, `comment6` FROM `ytest`.`data_million_small` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1001')) AND ((`id` <= '10405')) LOCK IN SHARE MODE /*pt-online-schema-change 18112 copy nibble*/
... 略
2020-05-07 10:44:54.665179 453 INSERT LOW_PRIORITY IGNORE INTO `ytest`.`_data_million_small_new` (`id`, `a`, `b`, `c`, `d`, `e`, `comment3`, `comment`, `comment5`, `comment6`) SELECT `id`, `a`, `b`, `c`, `d`, `e`, `comment3`, `comment`, `comment5`, `comment6` FROM `ytest`.`data_million_small` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '76055')) AND ((`id` <= '100000')) LOCK IN SHARE MODE /*pt-online-schema-change 18112 copy nibble*/
2020-05-07 10:44:54.997152 453 SHOW WARNINGS
2020-05-07 10:44:54.997953 453 SHOW GLOBAL STATUS LIKE 'Threads_running'
-- 当拷贝数据完成之后,及时分析表进行统计信息的收集
2020-05-07 10:44:55.000173 453 ANALYZE TABLE `ytest`.`_data_million_small_new` /* pt-online-schema-change */
-- 完成旧表与新表的交换
2020-05-07 10:44:55.025956 453 RENAME TABLE `ytest`.`data_million_small` TO `ytest`.`_data_million_small_old`, `ytest`.`_data_million_small_new` TO `ytest`.`data_million_small`
2020-05-07 10:44:55.026906 453 RENAME TABLE `ytest`.`data_million_small` TO `ytest`.`__data_million_small_old`, `ytest`.`_data_million_small_new` TO `ytest`.`data_million_small`
-- 删除旧表
2020-05-07 10:44:55.102094 453 DROP TABLE IF EXISTS `ytest`.`__data_million_small_old`
-- 删除触发器
2020-05-07 10:44:55.140694 453 DROP TRIGGER IF EXISTS `ytest`.`pt_osc_ytest_data_million_small_del`
2020-05-07 10:44:55.153701 453 DROP TRIGGER IF EXISTS `ytest`.`pt_osc_ytest_data_million_small_upd`
2020-05-07 10:44:55.160543 453 DROP TRIGGER IF EXISTS `ytest`.`pt_osc_ytest_data_million_small_ins`
2020-05-07 10:44:55.167288 453 SHOW TABLES FROM `ytest` LIKE '\_data\_million\_small\_new'
工作流程总结:
- 查询当前数据库服务器信息,包括参数设置,负载信息等,判断表是否有存在触发器,是否有外键关联;
- 创建一张与旧表结构相同的新表,表名为
_旧表名
; - 在新创建的表上做变更操作;
- 旧表上创建
DELETE
、UPDATE
、INSERT
3个触发器; - 拷贝旧表数据到新表上,以
chunk
为单位进行,拷贝期间涉及的行会持有共享读锁; - 拷贝期间如果旧表如有
DML
操作,则通过触发器更新同步到新表上; - 当拷贝数据完成之后旧表与新表进行重命名;
- 如果有涉及到外键,根据工具指定选项进行外键处理;
- 删除旧表;
- 删除旧表上触发器。
参考:
https://www.cnblogs.com/dbabd/p/10605629.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html#online-ddl-column-operations