前言:在生产环境中,DBA经常遇到开发要求在生产环境增加一个字段,添加一个索引什么的,如果是5.6、5.7那还好一些,因为可以online DDL,但是原生的online DDL也是有一定条件的,我们会在后面提到,更多情况下,我们会选择使用pt-osc工具来执行DDL
一、PT-OSC工具使用
pt-osc是pt-online-schema-change的简写,是percona-toolkit工具集中的重要一员,是MySQL5.5及以前版本重要的维护工具之一,下面我们来看下pt-osc工具的使用方法
[root@Mariadb-04 mysql]# pt-online-schema-change --help
pt-online-schema-change alters a table's structure without blocking reads or
writes. Specify the database and table in the DSN. Do not use this tool before
reading its documentation and checking your backups carefully. For more
details, please use the --help option, or try 'perldoc
/usr/bin/pt-online-schema-change' for complete documentation.
Usage: pt-online-schema-change [OPTIONS] DSN
Options:
--alter=s The schema modification, without the ALTER
TABLE keywords
--alter-foreign-keys-method=s How to modify foreign keys so they reference
the new table
--[no]analyze-before-swap Execute ANALYZE TABLE on the new table
before swaping with the old one (default yes)
--ask-pass Prompt for a password when connecting to
MySQL
--charset=s -A Default character set
--[no]check-alter Parses the --alter specified and tries to
warn of possible unintended behavior (
default yes)
--check-interval=m Sleep time between checks for --max-lag (
default 1). Optional suffix s=seconds, m=
minutes, h=hours, d=days; if no suffix, s is
used.
--[no]check-plan Check query execution plans for safety (
default yes)
--[no]check-replication-filters Abort if any replication filter is set on
any server (default yes)
--check-slave-lag=s Pause the data copy until this replica's lag
is less than --max-lag
--chunk-index=s Prefer this index for chunking tables
--chunk-index-columns=i Use only this many left-most columns of a --
chunk-index
--chunk-size=z Number of rows to select for each chunk
copied (default 1000)
--chunk-size-limit=f Do not copy chunks this much larger than the
desired chunk size (default 4.0)
--chunk-time=f Adjust the chunk size dynamically so each
data-copy query takes this long to execute (
default 0.5)
--config=A Read this comma-separated list of config
files; if specified, this must be the first
option on the command line
--critical-load=A Examine SHOW GLOBAL STATUS after every
chunk, and abort if the load is too high (
default Threads_running=50)
--database=s -D Connect to this database
--default-engine Remove ENGINE from the new table
--defaults-file=s -F Only read mysql options from the given file
--[no]drop-new-table Drop the new table if copying the original
table fails (default yes)
--[no]drop-old-table Drop the original table after renaming it (
default yes)
--[no]drop-triggers Drop triggers on the old table. --no-drop-
triggers forces --no-drop-old-table (default
yes)
--dry-run Create and alter the new table, but do not
create triggers, copy data, or replace the
original table
--execute Indicate that you have read the
documentation and want to alter the table
--force This options bypasses confirmation in case
of using alter-foreign-keys-method = none ,
which might break foreign key constraints
--help Show help and exit
--host=s -h Connect to host
--max-flow-ctl=f Somewhat similar to --max-lag but for PXC
clusters
--max-lag=m Pause the data copy until all replicas' lag
is less than this value (default 1s).
Optional suffix s=seconds, m=minutes, h=
hours, d=days; if no suffix, s is used.
--max-load=A Examine SHOW GLOBAL STATUS after every
chunk, and pause if any status variables are
higher than their thresholds (default
Threads_running=25)
--new-table-name=s New table name before it is swapped. %T is
replaced with the original table name (
default %T_new)
--password=s -p Password to use when connecting
--pid=s Create the given PID file
--plugin=s Perl module file that defines a
pt_online_schema_change_plugin class
--port=i -P Port number to use for connection
--print Print SQL statements to STDOUT
--progress=a Print progress reports to STDERR while
copying rows (default time,30)
--quiet -q Do not print messages to STDOUT (disables --
progress)
--recurse=i Number of levels to recurse in the hierarchy
when discovering replicas
--recursion-method=a Preferred recursion method for discovering
replicas (default processlist,hosts)
--set-vars=A Set the MySQL variables in this comma-
separated list of variable=value pairs
--sleep=f How long to sleep (in seconds) after copying
each chunk (default 0)
--socket=s -S Socket file to use for connection
--statistics Print statistics about internal counters
--[no]swap-tables Swap the original table and the new, altered
table (default yes)
--tries=a How many times to try critical operations
--user=s -u User for login if not current user
--version Show version and exit
--[no]version-check Check for the latest version of Percona
Toolkit, MySQL, and other programs (default
yes)
Option types: s=string, i=integer, f=float, h/H/a/A=comma-separated list, d=DSN, z=size, m=time
Rules:
--dry-run and --execute are mutually exclusive.
This tool accepts additional command-line arguments. Refer to the SYNOPSIS and usage information for details.
DSN syntax is key=value[,key=value...] Allowable DSN keys:
KEY COPY MEANING
=== ==== =============================================
A yes Default character set
D yes Database for the old and new table
F yes Only read default options from the given file
P yes Port number to use for connection
S yes Socket file to use for connection
h yes Connect to host
p yes Password to use when connecting
t no Table to alter
u yes User for login if not current user
If the DSN is a bareword, the word is treated as the 'h' key.
Options and values after processing arguments:
--alter (No value)
--alter-foreign-keys-method (No value)
--analyze-before-swap TRUE
--ask-pass FALSE
--charset (No value)
--check-alter TRUE
--check-interval 1
--check-plan TRUE
--check-replication-filters TRUE
--check-slave-lag (No value)
--chunk-index (No value)
--chunk-index-columns (No value)
--chunk-size 1000
--chunk-size-limit 4.0
--chunk-time 0.5
--config /etc/percona-toolkit/percona-toolkit.conf,/etc/percona-toolkit/pt-online-schema-change.conf,/root/.percona-toolkit.conf,/root/.pt-online-schema-change.conf
--critical-load Threads_running=50
--database (No value)
--default-engine FALSE
--defaults-file (No value)
--drop-new-table TRUE
--drop-old-table TRUE
--drop-triggers TRUE
--dry-run FALSE
--execute FALSE
--force FALSE
--help TRUE
--host (No value)
--max-flow-ctl (No value)
--max-lag 1
--max-load Threads_running=25
--new-table-name %T_new
--password (No value)
--pid (No value)
--plugin (No value)
--port (No value)
--print FALSE
--progress time,30
--quiet FALSE
--recurse (No value)
--recursion-method processlist,hosts
--set-vars
--sleep 0
--socket (No value)
--statistics FALSE
--swap-tables TRUE
--tries (No value)
--user (No value)
--version FALSE
--version-check TRUE
乍一看,参数是不是眼花缭乱?其实要掌握的参数其实也不多:
—user 指定执行的用户
—ask-pass 提示输入密码
—host 指定连接主机
—alter 后面跟上需要执行的sql
D 指定DBNAME
t 指定table name
—print 打印输出信息
—execute 提示输入密码后立即执行sql
二、实战一把
正好,今天查看慢日志,有个SQL需要优化,看了下SQL以后发现目前的执行计划走的索引只匹配到了一个字段,需要根据条件另外创建一个索引,那么我们可以通过pt-osc工具来添加此索引。
[root@Mariadb-04 mysql]# pt-online-schema-change --user=root --ask-pass --host=localhost --alter "add key idx_chnlid_docstatus_dockind_docpubtime(chnlid,docstatus,dockind,docpubtime)" D=xx,t=xx --print --execute
Enter MySQL password:
....
Operation, tries, wait:
analyze_table, 10, 1
copy_rows, 10, 0.25
create_triggers, 10, 1
drop_triggers, 10, 1
swap_tables, 10, 1
update_foreign_keys, 10, 1
Altering `xx`.`xx`...
Creating new table...
CREATE TABLE `xxxx`.`_xxxx_new` (
`xxx` int(11) NOT NULL,
`xx` int(11) NOT NULL DEFAULT '0',
`xx` datetime DEFAULT NULL,
`xx` varchar(50) DEFAULT NULL,
`xx` datetime DEFAULT NULL,
`xx` int(11) DEFAULT '1',
`xx` datetime DEFAULT NULL,
`xx` int(11) DEFAULT NULL,
`xx` int(11) DEFAULT NULL,
`xx` int(11) DEFAULT '0',
`xx` int(11) NOT NULL DEFAULT '0',
`xxx` int(11) NOT NULL DEFAULT '0',
`xx` datetime DEFAULT NULL,
`xx` int(11) DEFAULT '0',
`xx` varchar(200) DEFAULT NULL,
`xx` int(2) DEFAULT '0',
`xx` int(11) DEFAULT NULL,
PRIMARY KEY (`xx`),
KEY `xx` (`xxx`,`xxx`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Created new table xx._xx_new OK.
Altering new table...
ALTER TABLE `xx`.`_xx_new` add key idx_chnlid_docstatus_dockind_docpubtime(xx,xx,xx,xx)
Altered `xx`.`_xx_new` OK.
2018-05-16T17:29:51 Creating triggers...
CREATE TRIGGER `pt_osc_xx_xx_del` AFTER DELETE ON `xx`.`xx` FOR EACH ROW DELETE IGNORE FROM `xx`.`_xx_new` WHERE `xx`.`_xx_new`.`recid` <=> OLD.`recid`
CREATE TRIGGER `pt_osc_xx_xx_upd` AFTER UPDATE ON `xx`.`xx` FOR EACH ROW REPLACE INTO `xx`.`_xx_new` (..........)
CREATE TRIGGER `pt_osc_xx_xx_ins` AFTER INSERT ON `xx`.`xx` FOR EACH ROW REPLACE INTO `xx`.`_xx_new` (..........)
2018-05-16T17:29:51 Created triggers OK.
2018-05-16T17:29:51 Copying approximately 6788869 rows...
INSERT LOW_PRIORITY IGNORE INTO `xx`.`_xx_new` (............. FROM `xx`.`xx` FORCE INDEX(`PRIMARY`) WHERE ((`recid` >= ?)) AND ((`recid` <= ?)) LOCK IN SHARE MODE /*pt-online-schema-change 36224 copy nibble*/
SELECT /*!40001 SQL_NO_CACHE */ `recid` FROM `xx`.`xx` FORCE INDEX(`PRIMARY`) WHERE ((`recid` >= ?)) ORDER BY `recid` LIMIT ?, 2 /*next chunk boundary*/
Copying `xx`.`xx`: 5% 08:43 remain
Copying `xx`.`xx`: 11% 07:30 remain
Copying `xx`.`xx`: 14% 09:59 remain
Copying `xx`.`xx`: 18% 09:25 remain
Copying `xx`.`xx`: 22% 10:23 remain
Copying `xx`.`xx`: 23% 12:03 remain
Copying `xx`.`xx`: 27% 12:09 remain
Copying `xx`.`xx`: 32% 10:35 remain
Copying `xx`.`xx`: 32% 11:32 remain
Copying `xx`.`xx`: 33% 12:28 remain
Copying `xx`.`xx`: 35% 12:34 remain
Copying `xx`.`xx`: 41% 10:35 remain
Copying `xx`.`xx`: 46% 09:13 remain
Copying `xx`.`xx`: 48% 09:08 remain
Copying `xx`.`xx`: 53% 08:02 remain
Copying `xx`.`xx`: 54% 08:20 remain
Copying `xx`.`xx`: 57% 07:52 remain
Copying `xx`.`xx`: 60% 07:25 remain
Copying `xx`.`xx`: 62% 07:23 remain
Copying `xx`.`xx`: 66% 06:24 remain
Copying `xx`.`xx`: 71% 05:23 remain
Copying `xx`.`xx`: 74% 04:47 remain
Copying `xx`.`xx`: 75% 04:52 remain
Copying `xx`.`xx`: 76% 04:44 remain
Copying `xx`.`xx`: 80% 03:46 remain
Copying `xx`.`xx`: 82% 03:33 remain
Copying `xx`.`xx`: 86% 02:47 remain
Copying `xx`.`xx`: 86% 02:52 remain
Copying `xx`.`xx`: 87% 02:47 remain
Copying `xx`.`xx`: 90% 02:00 remain
Copying `xx`.`xx`: 94% 01:15 remain
Copying `xx`.`xx`: 95% 01:04 remain
Copying `xx`.`xx`: 97% 00:30 remain
2018-05-16T17:53:32 Copied rows OK.
2018-05-16T17:53:32 Analyzing new table...
2018-05-16T17:53:33 Swapping tables...
RENAME TABLE `xx`.`xx` TO `xx`.`_xx_old`, `xx`.`_xx_new` TO `xx`.`xx`
2018-05-16T17:53:33 Swapped original and new tables OK.
2018-05-16T17:53:33 Dropping old table...
DROP TABLE IF EXISTS `xx`.`_xx_old`
2018-05-16T17:53:37 Dropped old table `xx`.`_xx_old` OK.
2018-05-16T17:53:37 Dropping triggers...
DROP TRIGGER IF EXISTS `xx`.`pt_osc_xx_xx_del`;
DROP TRIGGER IF EXISTS `xx`.`pt_osc_xx_xx_upd`;
DROP TRIGGER IF EXISTS `xx`.`pt_osc_xx_xx_ins`;
2018-05-16T17:53:37 Dropped triggers OK.
Successfully altered `xx`.`xx`.
以上就是我在生产环境利用pt-osc做的在线DDL更改,下面我们根据log来分析一下pt-osc的原理
三、原理分析
从上面的log我们可以看出整体流程是这样的
首先根据原表DDL定义创建一张同样表结构的临时表,并且命名规则为_原表名_new
在新建的表上创建需要添加的索引
在原表上创建三个触发器,分别对应曾、改、删
将原表的数据以insert into select …的方式拷贝到新表
数据拷贝完成以后会将原表重命名为_原表名_old,将新表名重命名为原表名
删除表_原表名_old
删除触发器
成功
总的来说,整个过程还是比较清晰的
四、pt-osc使用限制
外键问题,因为涉及到表的重命名,因此如果要修改的表是其他表的外键,那么修改起来是有问题的,幸好pt-osc提供了—alter-foreign-keys-method选项,优先采用rebuild_constrains,也可以指定使用drop_swap
如果表上有触发器,那么我们无法采用pt-osc来做在线ddl,因为MySQL规定同一个表不能存在多个相同定义的触发器
从库延迟,如果需要变更的表数据量比较多的话,会造成从库延迟的问题,生产环境中我们需要注意这个问题
磁盘容量的问题也是我们需要注意的,如果服务器磁盘空间比较紧张,并且需要变更的表又是大表,我们需要提前估算好容量
五、原生online ddl的限制
对于原生的online ddl支持如下
MySQL5.6
辅助索引的创建和删除
改变自增长id值
添加或删除外键
列的重命名
MySQL5.7
增加支持在线更改索引名
增加在线增加varchar列长度
如果涉及到表空间的改变,那就不支持在线online ddl,是在不确定,可以参考下官方手册