在生产上对6400万数据的大表修改了表结构,记录一下当时的处理流程,下面我会带大家一步步操作当时的步骤
pt-online-schema-change是percona公司开发的一个工具,在percona-toolkit包里面可以找到这个功能,它可以在线修改表结构
原理
yum install perl-DBI
yum install perl-DBD-MySQL
yum install perl-Time-HiRes
yum install perl-IO-Socket-SSL
yum install perl-TermReadKey
wget https://downloads.percona.com/downloads/percona-toolkit/3.4.0/binary/redhat/8/x86_64/percona-toolkit-3.4.0-3.el8.x86_64.rpm&&rpm -ivh percona-toolkit-3.4.0-3.el8.x86_64.rpm
./bin/pt-online-schema-change --help 可以查看参数的使用,我们只是要修改个表结构,只需要知道几个简单的参数就可以了
参数 | 说明 |
---|---|
–user | 连接mysql的用户名 |
–password | 连接mysql的密码 |
–host | 连接mysql的地址 |
P=3306 | 连接mysql的端口号 |
D= | 连接mysql的库名 |
t= | 连接mysql的表名 |
–alter | 修改表结构的语句 |
–execute | 执行修改表结构 |
–charset=uft8 | 使用utf8编码,避免中文乱码 |
–no-version-check | 不检查版本,在阿里云服务器中一般加入此参数,否则会报错 |
#!/bin/bash
# use gh-ost to migrate tables with target table file
function print(){
echo "`date +\"%Y-%m-%d %H:%M:%S\"` -- " $1
}
function help(){
echo " bash $0 -T[file stores the target tables] -u[user] -p[passowrd] -P[dbport] -h[dbhost] -a[action:check|run_gh-ost|run_pt] -t[table_prefix] -d[database] -S[excutel_sql][action=check not required] -D[debug_mode]"
}
function is_continue(){
read -p "Press [y] to continue..." go_on
if [[ "${go_on}"x != "y"x ]];then print "user stop" && exit 1;fi
}
## parse command line option
while getopts 'T:u:h:p:P:a:t:d:S:D' c
do
case $c in
T) tfile=${OPTARG};;
u) user=${OPTARG};;
p) password=${OPTARG};;
h) host=${OPTARG};;
P) port=${OPTARG};;
a) action=${OPTARG};;
t) table_prefix=${OPTARG};;
d) database=${OPTARG};;
S) sql=${OPTARG};;
D) debug=true;;
esac
done
## check input params
if [[ $# -lt 3 ]];then
help
exit 1
fi
## set default value
tfile=${tfile-"t"}
user=${user-"root"}
password=${password-"root"}
host=${host-"127.0.0.1"}
port=${port-3306}
action=${action-"check"}
table_prefix=${table_prefix-"t"}
database=${database-"t"}
sql=${sql-"select version()"}
print "input params: tfile=$tfile, user=$user, password=$password, host=$host, port=$port, action=$action, table_prefix=$table_prefix, database=$database"
print "input params: sql=$sql"
if [[ $debug ]];then
read -p "Press [y] to confirm..." go_on
if [[ "${go_on}"x != "y"x ]];then print "user stop" && exit 1;fi
fi
## do actually work
count=0
cat $tfile|while read TABLE
do
count=`echo $count+1|bc`
if [[ "$action"x == "run_gh-ost"x ]];then
COMMAND="\
./gh-ost --max-load=Threads_connected=1000 --critical-load=Threads_connected=4000 --chunk-size=10000 --max-lag-millis=1500 --initially-drop-ghost-table --initially-drop-socket-file --ok-to-drop-table "\
"--verbose --aliyun-rds=true --allow-on-master --execute --allow-nullable-unique-key --cut-over-lock-timeout-seconds=1200 --default-retries=10000 "\
"--host=$host "\
"--port=$port "\
"--user=$user "\
"--password=${password} "\
"--database=$database "\
"--table=$TABLE "\
"--alter=\"$sql\""
print "running gh-ost with params:"
echo "$COMMAND"
#if [[ $debug ]];then
# read -p "Press [y] to continue..." go_on
# if [[ "${go_on}"x != "y"x ]];then print "user stop" && exit 1;fi
#fi
./gh-ost --max-load=Threads_connected=1000 --critical-load=Threads_connected=4000 --chunk-size=10000 --max-lag-millis=1500 --initially-drop-ghost-table --initially-drop-socket-file --ok-to-drop-table \
--verbose --aliyun-rds=true --allow-on-master --execute --allow-nullable-unique-key --cut-over-lock-timeout-seconds=1200 --default-retries=10000 --verbose \
--host=$host \
--port=$port \
--user=$user \
--password="${password}" \
--database="${database}" \
--table=$TABLE \
--alter="$sql"
elif [[ "${action}"x == "run_pt"x ]];then
COMMAND="\
pt-online-schema-change --max-load=Threads_running=70 --critical-load=Threads_running=120 --max-lag=50 --check-interval=2 --chunk-size=300 --charset=utf8mb3\
D=$database,t=$TABLE,P=$port,h=$host,u=$user,p="${password}" \
--alter=\"$sql\" \
--execute"
print "running pt-online with params:"
echo "$COMMAND"
# is_continue
pt-online-schema-change --max-load=Threads_running=70 --critical-load=Threads_running=120 --max-lag=50 --check-interval=2 --chunk-size=300 --charset=utf8mb3 --recursion-method=none \
D=$database,t=$TABLE,P=$port,h=$host,u=$user,p="${password}" \
--alter="$sql" \
--execute | tee -a running.log
elif [[ "${action}"x == "check"x ]];then
sql="desc $TABLE"
COMMAND="mysql -h$host -P$port -u$user -p\"$password\" $databases -A -e \"$sql\""
echo "before alter: "
mysql -h$host -P$port -u$user -p"$password" $database -A -e "$sql"
pt-online-schema-change --max-load=Threads_running=70 --critical-load=Threads_running=120 --max-lag=50 --check-interval=2 --chunk-size=300 \
D=$database,t=$TABLE,P=$port,h=$host,u=$user,p="${password}" \
--alter="$sql" \
--dry-run |tee -a running.log
echo "before alter: after alter"
mysql -h$host -P$port -u$user -p"$password" $database -A -e "$sql"
# check option exit on the first time
exit 0
else:
echo "unknown action=${action}, exists"
fi
done
测试数据表结构如下所示:
mysql> show create table test2;
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| test2 | CREATE TABLE `test2` (
`phoneno` varchar(30) NOT NULL,
`product_id` varchar(20) DEFAULT NULL,
PRIMARY KEY (`phoneno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 |
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select count(*) from test2;
+----------+
| count(*) |
+----------+
| 3243582 |
+----------+
1 row in set (0.15 sec)
nohup ./pt_db.sh -T tables.txt -hlocalhost -P3306 -uroot -p'gbase' -d test -a run_pt -S "modify column phoneno varchar(40);" &
日志如下所示:
[root@hadoop1 ~]# tail -f nohup.out
swap_tables, 10, 1
update_foreign_keys, 10, 1
Altering `test`.`test2`...
Creating new table...
Created new table test._test2_new OK.
Altering new table...
Altered `test`.`_test2_new` OK.
2022-09-14T13:58:08 Creating triggers...
2022-09-14T13:58:08 Created triggers OK.
2022-09-14T13:58:08 Copying approximately 3234777 rows...
Copying `test`.`test2`: 21% 01:47 remain
Copying `test`.`test2`: 44% 01:13 remain
Copying `test`.`test2`: 67% 00:42 remain
Copying `test`.`test2`: 90% 00:12 remain
2022-09-14T14:00:20 Copied rows OK.
2022-09-14T14:00:20 Analyzing new table...
2022-09-14T14:00:20 Swapping tables...
2022-09-14T14:00:20 Swapped original and new tables OK.
2022-09-14T14:00:20 Dropping old table...
2022-09-14T14:00:20 Dropped old table `test`.`_test2_old` OK.
2022-09-14T14:00:20 Dropping triggers...
2022-09-14T14:00:20 Dropped triggers OK.
Successfully altered `test`.`test2`.
修改成功:
mysql> show create table test2;
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| test2 | CREATE TABLE `test2` (
`phoneno` varchar(40) NOT NULL,
`product_id` varchar(20) DEFAULT NULL,
PRIMARY KEY (`phoneno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 |
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
第一次执行报错了
Cannot chunk the original table `test`.`test2`: There is no good index and the table is oversized. at /usr/bin/pt-online-schema-change line 6012.
增加索引后解决:
alter table test2 add primary key(phoneno);
表数据量 6400w ,变更时长:1h23m, 锁表时间4分钟,大家一定要注意主从同步的延迟时间!!!
文档
1.官方参考:https://docs.percona.com/percona-toolkit/pt-online-schema-change.html
2.参考文章:https://segmentfault.com/a/1190000014924677