MySQL online ddl 与 pt 并发执行导致更新丢失
mysql> use asn_newDatabase changedmysql>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)Byemysql: [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=noSET @@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: tCreate 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=utf81 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 17Successfully altered `test_zk`.`t`.
mysql> show create table t \G*************************** 1. row ***************************Table: tCreate 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=utf81 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=utf8Created 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 \--alter='ADD index idx_b(b);' --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 --executeAltering `test_zk`.`t`...Creating new table......
[root@test ~]# pt-online-schema-change \-u admin -p 3341 -h 127.0.0.1 -P 3341 \--alter='ADD index idx_c(c);' --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
2022-05-08T22:49:13 Dropped triggers OK.# Event Count# ====== =====# INSERT 17Successfully 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
