MySQL online ddl 与 pt 并发执行导致更新丢失
mysql> use asn_new
Database changed
mysql>
mysql> select quota_occupy_process from book_detail limit 1;
ERROR 1054 (42S22): Unknown column 'quota_occupy_process' in 'field list'
EXECUTE START AT 2022-05-07 12:26:14
--------------
create table open_preempt_book_sup
(
id int unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键'
primary key,
sup_no varchar(255) not null comment '供应商编码',
status tinyint not null comment '启用状态',
create_user_id varchar(255) null comment '创建人',
create_time datetime null comment '创建时间',
update_user_id varchar(255) null comment '更新人',
update_time datetime null comment '更新时间',
yn tinyint default 1 not null comment '删除标志',
ts timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP COMMENT '时间戳'
)
comment '配额预占表'
--------------
Query OK, 0 rows affected (0.01 sec)
--------------
alter table book_detail add column quota_occupy_process tinyint default 1 null comment '占用配额方式(流程),对应BookQuotaOccupyProcessEnum' after occupy_quota_flag
--------------
Query OK, 0 rows affected (6 min 31.14 sec)
Records: 0 Duplicates: 0 Warnings: 0
--------------
insert into open_preempt_book_sup (sup_no, status, create_user_id, create_time, yn, ts)
values ("*", "*", "*", now(), 1, now() )
--------------
Query OK, 1 row affected (0.00 sec)
Bye
mysql: [Warning] Using a password on the command line interface can be insecure.
EXECUTE DONE AT 2022-05-07 12:32:45
mysqlbinlog -R --raw --host=127.0.0.1 --user='admin' -p --port=3358 mysql-bin.008012
[root@DB zhangkai321]# ll -h mysql-bin.008012
-rw-r----- 1 root root 517M May 7 14:18 mysql-bin.008012
[ ]
[root@DB zhangkai321]# cat 127.0.0.1.sql.sql | grep -C 5 "quota_occupy_process"
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=33/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
alter table book_detail add column quota_occupy_process tinyint default 1 null comment '占用配额方式(流程),对应BookQuotaOccupyProcessEnum' after occupy_quota_flag
/*!*/;
# at 493
#220507 12:32:45 server id 1682448990 end_log_pos 541 CRC32 0xb913a91e GTID last_committed=0 sequence_number=0 rbr_only=no
SET @@SESSION.GTID_NEXT= '2760f282-629c-11ea-ab35-fa163e99b3fe:364578288'/*!*/;
# at 541
RENAME TABLE `asn_new`.`book_detail` TO `asn_new`.`_book_detail_old`,
`asn_new`.`_book_detail_new` TO `asn_new`.`book_detail`
DROP TABLE IF EXISTS `asn_new`.`_book_detail_old`;
|
|
|
|
|
|
|
|
|
mysql> select count(*) from t;
+----------+
| count(*) |
+----------+
| 1000000 |
+----------+
1 row in set (0.13 sec)
mysql> show create table t \G
*************************** 1. row ***************************
Table: t
Create Table: CREATE TABLE `t` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`a` int(10) DEFAULT NULL COMMENT 'a',
`name` varchar(20) DEFAULT NULL,
`column` int(10) DEFAULT '0',
`current_time` int(10) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> alter table t add column b int(10) DEFAULT NULL COMMENT 'b';
Query OK, 0 rows affected (5.82 sec)
Records: 0 Duplicates: 0 Warnings: 0
[root@test test_zk]# pwd
/export/zhangkai321/mysql/3341/data/test_zk
[root@test test_zk]#
[root@test test_zk]# ll -rth
...
-rw-r----- 1 mysql mysql 8.5K 5月 8 19:33 t.frm
-rw-r----- 1 mysql mysql 88M 5月 8 19:33 t.ibd
-rw-r----- 1 mysql mysql 8.6K 5月 8 19:33 #sql-4ae5_2d.frm
-rw-r----- 1 mysql mysql 88M 5月 8 19:33 #sql-ib219-33873579.ibd
alter table t drop column b;
Query OK, 0 rows affected (4.84 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> alter table t add column b int(10) DEFAULT NULL COMMENT 'b';
[root@test zhangkai321]# pt-online-schema-change \
-u admin -p 3341 -h 127.0.0.1 -P 3341 \
--alter='ADD index idx_a(a);' --execute D=test_zk,t=t --charset=utf8 \
--nocheck-replication-filters --max-load=Threads_running=50 \
--critical-load=Threads_running=500,Threads_connected=800 \
--recursion-method=hosts --max-lag=500 --check-interval=30 \
--statistics --print --execute
Query OK, 0 rows affected (5.56 sec)
Records: 0 Duplicates: 0 Warnings: 0
# Event Count
# ====== =====
# INSERT 17
Successfully altered `test_zk`.`t`.
mysql> show create table t \G
*************************** 1. row ***************************
Table: t
Create Table: CREATE TABLE `t` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`a` int(10) DEFAULT NULL COMMENT 'a',
`name` varchar(20) DEFAULT NULL,
`column` int(10) DEFAULT '0',
`current_time` int(10) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_name` (`name`),
KEY `idx_a` (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
CREATE TABLE `test_zk`.`__t_new` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`a` int(10) DEFAULT NULL COMMENT 'a',
`name` varchar(20) DEFAULT NULL,
`column` int(10) DEFAULT '0',
`current_time` int(10) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8
Created new table test_zk.__t_new OK.
Altering new table...
ALTER TABLE `test_zk`.`__t_new` ADD index idx_a(a);
建立一个临时文件,扫描表 A 主键的所有数据页;
用数据页中表 A 的记录生成 B+ 树,存储到临时文件中;
生成临时文件的过程中,将所有对 A 的操作记录在一个日志文件(row log)中,对应的是图中 state2 的状态;
临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表 A 相同的数据文件,对应的就是图中 state3 的状态;
用临时文件替换表 A 的数据文件。
持有 EXCLUSIVE-MDL 锁,禁止DML操作,即禁止读写
根据DDL类型,确定执行方式(Copy、Online-rebuild、Online-no-rebuild)
add column 操作属于 Online-rebuild
创建新的 frm 和 ibd 临时文件(ibd临时文件仅rebuild类型需要)
分配 row_log 空间,用来记录 DDL Execute 阶段产生的DML操作(仅rebuild类型需要)
降级 EXCLUSIVE-MDL 锁,允许DML语句读写
扫描原表主键以及二级索引的所有数据页,生成B+树,存储到临时文件中
将 DDL Execute 阶段产生的DML操作记录到 row_log(仅rebuild类型需要)
升级到 EXCLUSIVE-MDL 锁,禁止DML语句读写
将 row_log 中记录的DML操作应用到临时文件,得到一个逻辑数据上与原表相同的数据文件(仅rebuild类型需要)
重命名 frm 和 idb 临时文件,替换原表,将原表文件删除
提交事务(刷事务的redo日志),变更完成
Prepare阶段和Commit阶段都禁止读写,主要是为了保证数据一致性。
在真正拷贝数据之前,MDL 锁从写锁降级为读锁是原因是避免阻塞增删改操作;
不直接获取MDL读锁的原因是禁止其他线程对这个表同时做DDL;
row_log 空间中仅记录 DDL Execute 阶段产生的DML操作,不记录DDL操作;
DDL创建临时文件,因此磁盘剩余空间必须大于表空间,否则将执行失败。这里磁盘的占用与binlog没关系,因为DDL对应statement类型的binlog,不会产生大量binlog;
MySQL 5.6版本以后支持 online ddl;
MySQL5.5版本以前实现中临时表tmp_table是在server层创建的,而在MySQL5.6版本以后实现中临时文件tmp_file是在InnoDB内部创建的,对于server层来说,没有将数据挪动到临时表中,是一个“原地”操作,也就是所谓的inplace。
Create new table,CREATE TABLE test_zk.__t_new
Alter new table,ALTER TABLE test_zk.__t_new ADD index idx_a(a);
Create triggers
Copy rows
Analyze new table
Swap tables,RENAME TABLE test_zk.t TO test_zk._t_old, test_zk.__t_new TO test_zk.t
Drop old table,DROP TABLE IF EXISTS test_zk._t_old
Drop triggers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mysql> alter table t add column b int(10) DEFAULT NULL COMMENT 'b';
mysql> alter table t add column c int(10) DEFAULT NULL COMMENT 'c';
mysql> SHOW PROCESSLIST;
+----+-------+-----------------+---------+---------+------+---------------------------------+-------------------------------------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-------+-----------------+---------+---------+------+---------------------------------+-------------------------------------------------------------+
| 45 | admin | 127.0.0.1:53675 | test_zk | Query | 4 | altering table | alter table t add column b int(10) DEFAULT NULL COMMENT 'b' |
| 46 | admin | 127.0.0.1:55677 | test_zk | Query | 3 | Waiting for table metadata lock | alter table t add column c int(10) DEFAULT NULL COMMENT 'c' |
| 56 | admin | 127.0.0.1:41007 | NULL | Query | 0 | starting | SHOW PROCESSLIST |
+----+-------+-----------------+---------+---------+------+---------------------------------+-------------------------------------------------------------+
3 rows in set (0.00 sec)
mysql> select object_type,object_schema,object_name,lock_type,lock_duration,lock_status,owner_thread_id from performance_schema.metadata_locks;
+-------------+--------------------+----------------+---------------------+---------------+-------------+-----------------+
| object_type | object_schema | object_name | lock_type | lock_duration | lock_status | owner_thread_id |
+-------------+--------------------+----------------+---------------------+---------------+-------------+-----------------+
| GLOBAL | NULL | NULL | INTENTION_EXCLUSIVE | STATEMENT | GRANTED | 70 |
| SCHEMA | test_zk | NULL | INTENTION_EXCLUSIVE | TRANSACTION | GRANTED | 70 |
| TABLE | test_zk | t | SHARED_UPGRADABLE | TRANSACTION | GRANTED | 70 |
| GLOBAL | NULL | NULL | INTENTION_EXCLUSIVE | STATEMENT | GRANTED | 71 |
| SCHEMA | test_zk | NULL | INTENTION_EXCLUSIVE | TRANSACTION | GRANTED | 71 |
| TABLE | test_zk | t | SHARED_UPGRADABLE | TRANSACTION | PENDING | 71 |
| TABLE | performance_schema | metadata_locks | SHARED_READ | TRANSACTION | GRANTED | 81 |
+-------------+--------------------+----------------+---------------------+---------------+-------------+-----------------+
7 rows in set (0.00 sec)
Query OK, 0 rows affected (5.18 sec)
Records: 0 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (9.64 sec)
Records: 0 Duplicates: 0 Warnings: 0
[root@test zhangkai321]# pt-online-schema-change \
-u admin -p 3341 -h 127.0.0.1 -P 3341 \
'ADD index idx_b(b);' --execute D=test_zk,t=t --charset=utf8 \ --alter=
--nocheck-replication-filters --max-load=Threads_running=50 \
--critical-load=Threads_running=500,Threads_connected=800 \
--recursion-method=hosts --max-lag=500 --check-interval=30 --statistics \
print --execute --
Altering `test_zk`.`t`...
Creating new table...
...
[root@test ~]# pt-online-schema-change \
-u admin -p 3341 -h 127.0.0.1 -P 3341 \
'ADD index idx_c(c);' --execute D=test_zk,t=t --charset=utf8 \ --alter=
--nocheck-replication-filters --max-load=Threads_running=50 \
--critical-load=Threads_running=500,Threads_connected=800 \
--recursion-method=hosts --max-lag=500 --check-interval=30 --statistics \
print --execute --
2022-05-08T22:49:13 Dropped triggers OK.
# Event Count
# ====== =====
# INSERT 17
Successfully altered `test_zk`.`t`.
The table `test_zk`.`t` has triggers but --preserve-triggers was not specified.
Please read the documentation for --preserve-triggers.
定位SQL,至少有两种方法,查询慢SQL或查询binlog,建议使用前者;
并发执行会引入额外的复杂度,学习的过程中需要关注。
online ddl 详细流程(copy & inplace)
pt 详细流程
Online DDL Operations
https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html#online-ddl-column-operations
Online DDL Performance and Concurrency
https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-performance.html
关于mysql在线ddl的原理和实验-干货!
https://www.modb.pro/db/56128
为什么表数据删掉一半,表文件大小不变?
https://time.geekbang.org/column/article/72388
技术分享 | Online DDL 工具 pt-osc
https://segmentfault.com/a/1190000024481446