原来 pt-osc 改表是这样实现的!原理详解【附场景案例】

pt工具的使用(4) pt-osc的使用 --alter = s 模式修改,不带ALTER TABLE关键字 --alter-foreign-keys-method = s 如何修改外键以便它们引用新表 -[no]交换前分析在新表上执行ANALYZE TABLE 与旧的交换之前(默认 是) --ask-pass ... 阅读详情

pt-osc原理探索及其触发器的深入分析

作者:莫善,某互联网公司高级 DBA。

爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

本文约 6000 字,预计阅读需要 20 分钟。

背景

自工作以来就开始接触 pt-osc,至今差不多 8 年了,过去那么多次接触都只是停留在使用及简单了解原理,并没有对原理进行更深的思考,简而言之就是没有吃透其原理,后面使用改表平台以后,连编写 pt-osc 命令都省去了,感觉更加的生疏了,趁着这次机会就来梳理总结一下。

本文主要是梳理一下 pt-osc 的工作原理,以及对自己的一些疑惑进行解惑,最后是一点使用建议,仅供参考。

测试环境

  • pt-osc 3.0.13
  • MySQL 5.7.26

问题梳理

用过 pt-osc 改表的朋友都应该非常熟悉这工具的工作流程了,这里简单过一遍:

  • 创建一张与原表结构一致的新表,然后对新表完成改表需求。
  • 创建触发器。这时候增量数据就已经开始同步。
  • 同步全量数据。遍历原表,通过 INSERT IGNORE INTO 将数据拷贝到新表。
  • 等全量同步完毕后切表,清理触发器。
触发器映射的 SQL 语句
INSERT 触发器REPLACE INTO
UPDATE 触发器DELETE IGNORE + REPLACE INTO
DELETE 触发器DELETE IGNORE

关于这个流程,之前并没有太多的思考,也就是简单的记忆一下,但是前段时间给公司分享我们改表平台的时候有研发的同事问了我几个问题,有些我能解答,有些不能解答,这里就统一梳理出来供大家思考一下:

  • 问题一:这里的触发器为什么要这么设计,能不能 INSERT 用 INSERT,UPDATE 用 UPDATE?
  • 问题二:改表工单明明暂停了,但是集群负载不会降下来。
  • 问题三:使用 pt-osc 为什么会产生死锁?
  • 问题四:改表准备结束阶段,切表和删除触发器是串行执行,既存在先后顺序也没法保证原子性,会不会有问题?

上述问题的前三个问题是在分享时问我的。其中 问题一 在现场其实回答的不够好,对于 UPDATE 的触发器就给我问住了,后来通过查资料及测试才搞明白,然后 问题四 是我在后面查资料及测试的时候出现的一个疑问。

下面就逐一来看看这几个问题。

问题解惑

通过 binlog 来抓取一下 pt-osc 的一次完整改表操作。

准备一个测试表

写入两条测试数据,表结构定义如下:

(dba:3306)@[dbzz_new_dbversion1]>show create table t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'pk',
  `name` char(4) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'name',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
1 row in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>select * from t1;
+----+------+
| id | name |
+----+------+
|  1 | 1    |
|  2 | 2    |
+----+------+
2 rows in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>

改表需求是用 pt-osc 将 name 字段的长度 char(4) 改为 char(10)。

pt-online-schema-change --alter=" modify column name char(10) not null default '' comment 'name'; " --chunk-size=2000 --max-lag=31 --check-interval=5 --no-check-replication-filters --check-alter --max-load='Threads_running=64' --critical-load='Threads_running=4000' --recurse=0 --ask-pass --execute --pause-file=/opt/soft/zzonlineddl/logs/mark/dd2fb8319b658bb43fb1e579ed2fc7fd_pause_6098 --print --check-slave-lag=192.168.168.1.2:3306 -p123456 h=192.168.1.1,P=3306,u=inception,D=dbzz_new_dbversion1,t=t1,A=utf8

执行完上述的 pt-osc 命令后,将 binlog 解析出来可以得到下面的操作步骤:

为了简洁,所有操作仅保留 SQL,其他信息已经去掉了。

1. 创建一张新表,表结构与旧表相同

CREATE TABLE `dbzz_new_dbversion1`.`_t1_new` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'pk',
  `name` char(4) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'name',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

2. 对新表执行改表需求

ALTER TABLE `dbzz_new_dbversion1`.`_t1_new`  modify column name char(10) not null default '' comment 'name'

3. 创建触发器

CREATE DEFINER=`inception`@`192.168.%.%` TRIGGER `pt_osc_dbzz_new_dbversion1_t1_del` AFTER DELETE ON `dbzz_new_dbversion1`.`t1` FOR EACH ROW DELETE IGNORE FROM `dbzz_new_dbversion1`.`_t1_new` WHERE `dbzz_new_dbversion1`.`_t1_new`.`id` <=> OLD.`id`

CREATE DEFINER=`inception`@`192.168.%.%` TRIGGER `pt_osc_dbzz_new_dbversion1_t1_upd` AFTER UPDATE ON `dbzz_new_dbversion1`.`t1` FOR EACH ROW BEGIN DELETE IGNORE FROM `dbzz_new_dbversion1`.`_t1_new` WHERE !(OLD.`id` <=> NEW.`id`) AND `dbzz_new_dbversion1`.`_t1_new`.`id` <=> OLD.`id`;REPLACE INTO `dbzz_new_dbversion1`.`_t1_new` (`id`, `name`) VALUES (NEW.`id`, NEW.`name`);END

CREATE DEFINER=`inception`@`192.168.%.%` TRIGGER `pt_osc_dbzz_new_dbversion1_t1_ins` AFTER INSERT ON `dbzz_new_dbversion1`.`t1` FOR EACH ROW REPLACE INTO `dbzz_new_dbversion1`.`_t1_new` (`id`, `name`) VALUES (NEW.`id`, NEW.`name`)

这里的触发器为什么要这么设计?其实很值得我们去细想,想明白以后会发现真的很巧妙。

DELETE 触发器

没啥好介绍的,就跳过吧。

INSERT 触发器

这里为什么要替换成 REPLACE 操作?如果用 INSERT 操作会怎么样?答案是数据可能不一致。

如果业务使用 INSERT 写入记录,这种场景是没问题的,正常触发该触发器。但是如果业务是通过 REPLACE 操作,这种场景也是会触发 INSERT 触发器,那在原表执行 REPLACE 操作,新表执行 INSERT 操作。如果目标记录不存在则没问题,但是目标记录存在的话就会导致数据不一致,即原表的记录被更新了,新表的记录还是原来的记录。所以说直接使用 INSERT 操作是存在数据不一致的风险的。

只有当业务的 REPLACE 的目标记录存在,且这条记录已经被拷贝到新表,这种场景才会导致数据不一致,如果该记录还没拷贝到新表也是没问题的。

UPDATE 触发器

我们都知道这个会先 DELETE,然后 REPLACE,那为什么要这么设计?能不能直接使用 UPDATE,或者说能不能不 DELETE,直接 REPLACE,又或者说其他方案?

关于这个触发器我们可以好好讨论一下。

我们先来讨论一下,为什么不能直接使用 UPDATE 操作?

毕竟 gh-ost 在回放 UPDATE 操作的时候是直接使用 UPDATE,而不是替换成其他操作。如果 pt-osc 也直接使用 UPDATE 会有什么问题?答案是可能会导致数据丢失。

下面我们来测试验证一下:

drop table if exists t1;
drop table if exists _t1_new;
create table t1 (id int not null auto_increment primary key comment 'pk',name char(4) not null default '' comment 'name');
create table _t1_new like t1;

insert into t1 values(1,1),(10,10);
insert into _t1_new values(1,1);
delimiter //

CREATE TRIGGER `pt_osc_dbzz_new_dbversion1_t1_upd` AFTER UPDATE ON `dbzz_new_dbversion1`.`t1` FOR EACH ROW BEGIN UPDATE `dbzz_new_dbversion1`.`_t1_new`  set `id` = NEW.`id`, `name` = NEW.`NAME` where `id` = OLD.`id` and `name` = OLD.`NAME`;END //

delimiter ;

CREATE TRIGGER `pt_osc_dbzz_new_dbversion1_t1_ins` AFTER INSERT ON `dbzz_new_dbversion1`.`t1` FOR EACH ROW REPLACE INTO `dbzz_new_dbversion1`.`_t1_new` (`id`, `name`) VALUES (NEW.`id`, NEW.`name`);

CREATE TRIGGER `pt_osc_dbzz_new_dbversion1_t1_del` AFTER DELETE ON `dbzz_new_dbversion1`.`t1` FOR EACH ROW DELETE IGNORE FROM `dbzz_new_dbversion1`.`_t1_new` WHERE `dbzz_new_dbversion1`.`_t1_new`.`id` <=> OLD.`id`;

准备了两个测试表,来模拟改表。

  • t1 写入两条记录 (1,1),(10,10)。
  • _t1_new 写入一条记录(1,1)。
  • UPDATE 触发器是直接用 UPDATE 来操作。

假设拷贝全量数据的任务拷贝到 id=9 的记录,id=10 还没拷贝到新表。

这时候如果在原表执行了一条 UPDATE t1 SET id = 2 , name = 2 WHERE id = 10 结果会怎么样?

(dba:3306)@[dbzz_new_dbversion1]>select * from _t1_new;
+----+------+
| id | name |
+----+------+
|  1 | 1    |
+----+------+
1 row in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>select * from t1;
+----+------+
| id | name |
+----+------+
|  1 | 1    |
| 10 | 10   |
+----+------+
2 rows in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>update t1 set id = 2 , name = 2 where id = 10;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

(dba:3306)@[dbzz_new_dbversion1]>select * from t1;
+----+------+
| id | name |
+----+------+
|  1 | 1    |
|  2 | 2    |
+----+------+
2 rows in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>select * from _t1_new;
+----+------+
| id | name |
+----+------+
|  1 | 1    |
+----+------+
1 row in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>

我们测试的有个前提假设,那就是在执行 UPDATE 的时候,目标记录还没有被同步到新表,且 UPDATE 后的 id 要小于当前正在拷贝的 id。可以看到测试结果是 id=2 这条记录就会被丢失,因为 id=2 这条记录不会被同步到新表。

通过上面的测试我们知道,UPDATE 触发器 不能使用 UPDATE 操作来完成。

那么问题又来了,gh-ost 的 UPDATE 操作是直接使用 UPDATE 进行回放,为什么没问题,按说采用上面的测试用例也应该是数据不一致的。关于这个问题我也做了测试,但是由于 gh-ost 工具的输出非常多,这里就不贴输出结果了,有兴趣的朋友可以自行测试一下,这里直接提供测试结果。

从测试结果看,其实gh-ost 并不是完全的使用 UPDATE 操作进行回放,应该是还有其他线程在监听 binlog,如果当前的 UPDATE 是修改主键或者唯一键,且是从大改到小,且修改后的主键或者唯一键小于当前正在拷贝的数据块的值,这时候就会执行一条 REPLACE 操作,如果不满足条件就不做处理。

下面我们再来讨论第二个疑问。

既然已经删除了这里为什么还要使用 REPLACE 而不是 INSERT?

关于这一点我没想明白,我认为 DELETE + REPLACE 和 DELETE + INSERT 是等效的。那为什么要先删除再 REPLACE?这是为了避免因为修改唯一键或者主键的场景,如果没有 DELETE,那就会在新表多出记录。比如将主键等于1改成主键等于 2,在原表只有一条记录,但是新表会有两条记录,所以要先 DELETE 再 REPLACE。

同样我们通过测试来验证一下为什么要先 DELETE 再 REPLACE,不能直接 REPLACE 操作:

drop table if exists t1;
drop table if exists _t1_new;
create table t1 (id int not null auto_increment primary key comment 'pk',name char(4) not null default '' comment 'name');
create table _t1_new like t1;

DROP TRIGGER `pt_osc_dbzz_new_dbversion1_t1_upd`;
DROP TRIGGER `pt_osc_dbzz_new_dbversion1_t1_ins`;
DROP TRIGGER `pt_osc_dbzz_new_dbversion1_t1_del`;

insert into t1 values(1,1),(9,9);
insert into _t1_new values(1,1);
delimiter //

CREATE TRIGGER `pt_osc_dbzz_new_dbversion1_t1_upd` AFTER UPDATE ON `dbzz_new_dbversion1`.`t1` FOR EACH ROW BEGIN REPLACE INTO `dbzz_new_dbversion1`.`_t1_new` (`id`, `name`) VALUES (OLD.`id`, OLD.`name`);END //

delimiter ;

CREATE TRIGGER `pt_osc_dbzz_new_dbversion1_t1_ins` AFTER INSERT ON `dbzz_new_dbversion1`.`t1` FOR EACH ROW REPLACE INTO `dbzz_new_dbversion1`.`_t1_new` (`id`, `name`) VALUES (NEW.`id`, NEW.`name`);

CREATE TRIGGER `pt_osc_dbzz_new_dbversion1_t1_del` AFTER DELETE ON `dbzz_new_dbversion1`.`t1` FOR EACH ROW DELETE IGNORE FROM `dbzz_new_dbversion1`.`_t1_new` WHERE `dbzz_new_dbversion1`.`_t1_new`.`id` <=> OLD.`id`;

准备了两个测试表,来模拟改表。

  • t1 写入两条记录 (1,1),(9,9)。
  • _t1_new 写入一条记录(1,1)。
  • UPDATE 触发器是直接用 REPLACE 来操作。

假设拷贝全量数据的任务拷贝到 id=8 的记录,即 id=9 这条记录还没有拷贝到新表。

这时候如果在原表执行了一条 INSERT INTO t1(id,name) VALUE(9,9) ON DUPLICATE KEY UPDATE id = 10, name = 10; 结果会怎么样?

INSERT INTO ON DUPLICATE KEY UPDATE 这种操作在目标记录存在时会触发 UPDATE 触发器。

(dba:3306)@[dbzz_new_dbversion1]>select * from t1;
+----+------+
| id | name |
+----+------+
|  1 | 1    |
|  9 | 9    |
+----+------+
2 rows in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>select * from _t1_new;
+----+------+
| id | name |
+----+------+
|  1 | 1    |
+----+------+
1 row in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>insert into t1(id,name) value(9,9) ON DUPLICATE KEY UPDATE id=10, name = 10;
Query OK, 2 rows affected (0.01 sec)

(dba:3306)@[dbzz_new_dbversion1]>select * from _t1_new;
+----+------+
| id | name |
+----+------+
|  1 | 1    |
|  9 | 9    |
+----+------+
2 rows in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>select * from t1;
+----+------+
| id | name |
+----+------+
|  1 | 1    |
| 10 | 10   |
+----+------+
2 rows in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>

我们可以看到测试结果是在 _t1_new 表中写进了 (9,9) 这条记录,而不是预期内的 (10,10)。那么问题就来了,此时全量拷贝任务的进度是拷贝到 id = 8,那么后面 t1 表的 id = 10 这条记录也会被拷贝到新表。所以最终 _t1_new 的记录会比 t1 多一条记录 (9,9),最终数据不一致。

通过上面的测试我们知道,UPDATE 触发器 不能直接使用 REPLACE 操作来完成。

我们再来思考一下,UPDATE 触发器 还有没有其他可以实现数据最终一致的方案?

经过测试验证发现,其实 INSERT IGNORE + UPDATE,或者 DELETE + INSERT,都是可以实现最终数据一致。那么 pt-osc 官方为什么会选择 DELETE + REPLACE 呢?

这点我猜测可能是因为出于成本考虑,毕竟 INSERT 可能会多占用一次 IO。

有兴趣的小伙伴可以自行测试一下。不过需要注意一下,这里 INSERT IGNORE + UPDATE 里面的 INSERT 是 OLD.ID 的数据。DELETE + REPLACE 或者 DELETE + INSERT 是 NEW.ID 的数据。

那么 DELETE + REPLACE 和 DELETE + INSERT 为什么选择前者呢?这两个操作放在一个事务里面的话,最终 binlog 记录的是一样的,所以我暂时想不通为什么选择前者。

下面我们做一个测试,看看 DELETE + REPLACE 和 DELETE + INSERT 是不是等效?

首先准备一些测试数据:

drop table if exists t1;
create table t1 (id int not null auto_increment primary key comment 'pk',name char(4) not null default '' comment 'name');
insert into t1 values(1,1),(2,2);
set binlog_rows_query_log_events = 1;
flush binary logs;

这里要开启 binlog_rows_query_log_events 参数,目的是为了记录原始语句。

现在做如下测试,然后查看 binlog 的记录。

begin;
delete from t1 where id = 1 and name = 1;
insert into t1 value(1,1);
commit;

begin;
delete from t1 where id = 1 and name = 1;
replace into t1 value(1,1);
commit;

会发现解析 binlog 后的记录是下面这样:

BEGIN
# delete from t1 where id = 1 and name = 1

### DELETE FROM `dbzz_new_dbversion1`.`t1`
### WHERE
###   @1=1 /* INT meta=0 nullable=0 is_null=0 */
###   @2='1' /* STRING(16) meta=65040 nullable=0 is_null=0 */

# insert into t1 value(1,1)

### INSERT INTO `dbzz_new_dbversion1`.`t1`
### SET
###   @1=1 /* INT meta=0 nullable=0 is_null=0 */
###   @2='1' /* STRING(16) meta=65040 nullable=0 is_null=0 */
COMMIT/*!*/;

BEGIN
# delete from t1 where id = 1 and name = 1

### DELETE FROM `dbzz_new_dbversion1`.`t1`
### WHERE
###   @1=1 /* INT meta=0 nullable=0 is_null=0 */
###   @2='1' /* STRING(16) meta=65040 nullable=0 is_null=0 */

# replace into t1 value(1,1)

### INSERT INTO `dbzz_new_dbversion1`.`t1`
### SET
###   @1=1 /* INT meta=0 nullable=0 is_null=0 */
###   @2='1' /* STRING(16) meta=65040 nullable=0 is_null=0 */
COMMIT/*!*/;
  • 第一个事务:原始语句是 DELETE + INSERT,实际执行的也是 DELETE + INSERT。
  • 第二个事务:原始语句是 DELETE + REPLACE,但实际执行的是 DELETE + INSERT。

所以说同一个事务内,DELETE + REPLACE 和 DELETE + INSERT 应该是等效的。

4. 拷贝全量数据

# INSERT LOW_PRIORITY IGNORE INTO `dbzz_new_dbversion1`.`_t1_new` (`id`, `name`) SELECT `id`, `name` FROM `dbzz_new_dbversion1`.`t1` LOCK IN SHARE MODE /*pt-online-schema-change 10363 copy table*/

### INSERT INTO `dbzz_new_dbversion1`.`_t1_new`
### SET
###   @1=1 /* INT meta=0 nullable=0 is_null=0 */
###   @2='1' /* STRING(40) meta=65064 nullable=0 is_null=0 */
### INSERT INTO `dbzz_new_dbversion1`.`_t1_new`
### SET
###   @1=2 /* INT meta=0 nullable=0 is_null=0 */
###   @2='2' /* STRING(40) meta=65064 nullable=0 is_null=0 */

这个操作需要解释一下,因为测试表数据较少,如果目标表数据行很多,超过 chunk-size 的大小,就会根据 主键(非空唯一键) 去遍历,每一次是按 chunk-size的大小。

另外这里可能会有疑问,pt-osc 采用 LOW_PRIORITY 用法来降低 INSERT 操作的优先级,在读密集型的系统可能比较友好,但是 InnoDB 是行级锁,能更好的控制读写并发问题,所以不要被迷惑了这里的限制是不生效,这里可能仅仅是为了兼容非 InnoDB 引擎。

下面这段话是官方文档的描述,说的是仅表级锁的存储引擎有效。

If you use the LOW_PRIORITY modifier, execution of the INSERT is delayed until no other clients are reading from the table. This includes other clients that began reading while existing clients are reading, and while the INSERT LOW_PRIORITY statement is waiting. It is possible, therefore, for a client that issues an INSERT LOW_PRIORITY statement to wait for a very long time.

LOW_PRIORITY affects only storage engines that use only table-level locking (such as MyISAM, MEMORY, and MERGE).

另外,用过 pt-osc 的朋友应该都了解,在并发比较高的环境下改表,很容易产生死锁,在这里我们就简单分析一下死锁问题。

时间会话1会话2
t1BEGIN;
UPDATE old WHERE id = 5;
t2INSERT LOW_PRIORITY IGNORE INTO new SELETE * FROM old WHERE id > 1 and id < 10 LOCK IN SHARE MODE;
t3REPLACE INTO new xxx;
ROLLBACK;

通过上面的表我们可以知道:

  • t1 时刻,因为 old 表有触发器,所以对 old 表进行 UPDATE 会起一个事务,这时候 会话1 执行 UPDATE 操作会持有 old 表的 X 锁。
  • t2 时刻,拷贝全量数据的任务正好包含业务需要更新的记录,这时候 会话2 持有 new 表的自增锁,但是需要等待 old 的 S 锁。
  • t3 时刻,UPDATE 触发器 被触发,REPLACE 操作需要等待 new 的自增锁,死锁形成,MySQL 认为 会话1 事务开销小,所以被回滚,对业务来说很不友好。

我们可以发现,产生死锁是需要两个条件:业务 UPDATE SQL 执行结束后到触发器的 REPLACE 被触发之前,正在开启下一次 拷贝全量数据任务,且业务更新的记录正好在 拷贝全量数据任务 的拷贝的区间。

由此可见,我们通过改小 chunk-size 降低死锁被触发的概率,但是无法完全避免。

5. 更新统计信息

ANALYZE TABLE `dbzz_new_dbversion1`.`_t1_new` /* pt-online-schema-change */

不知道大家有没有疑问,为什么要做这个操作?

看起来这个操作是强制更新统计信息,其目的就是为了帮助优化器更好的选择执行计划。如果不做这个操作,改表结束以后,新表被业务流量访问,可能会因为统计信息不准,导致业务的查询选择了错误的执行计划出现大量的慢查询,进而影响到业务。

但是我们都知道,当目标表的数据行发生重大变化后统计信息一般都是自动重新计算,阈值是表中行数的 10%(默认情况下),所以这个操作看起来又有点多余了。

gh-ost 切表后没有这个操作。

还有一点,如果是大表,这个操作会不会阻塞原表的 DML?

(dba:3306)@[dbzz_new_dbversion1]>select count(*) from sbtest1;
+----------+
| count(*) |
+----------+
|  4999999 |
+----------+
1 row in set (0.16 sec)

(dba:3306)@[dbzz_new_dbversion1]>ANALYZE TABLE sbtest1;
+-----------------------------+---------+----------+----------+
| Table                       | Op      | Msg_type | Msg_text |
+-----------------------------+---------+----------+----------+
| dbzz_new_dbversion1.sbtest1 | analyze | status   | OK       |
+-----------------------------+---------+----------+----------+
1 row in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>

经过测试发现,对一个近 500w 行的表做这个操作很快就返回结果了,最后也查了官方文档 确认是不会阻塞 DML。

ANALYZE TABLE removes the table from the table definition cache, which requires a flush lock. If there are long running statements or transactions still using the table, subsequent statements and transactions must wait for those operations to finish before the flush lock is released. Because ANALYZE TABLE itself typically finishes quickly, it may not be apparent that delayed transactions or statements involving the same table are due to the remaining flush lock. 

6. 切表

RENAME TABLE `dbzz_new_dbversion1`.`t1` TO `dbzz_new_dbversion1`.`_t1_old`, `dbzz_new_dbversion1`.`_t1_new` TO `dbzz_new_dbversion1`.`t1`

关于这一个操作,我原来是有一个很大的疑问的,也就是上面描述的 问题四。

首先,t1 表是存在三个触发器,切表后 _t1_new 表已经不存在。这时候切表后如果对 t1 做 DML 操作,触发器工作应该会出现异常,最终导致 t1 的 DML 操作失败。

但是我们都知道,实际上并不会出现这个问题。我就在想,是不是因为切表跟删除触发器之间隔的时间太短,且每个操作都是很快才没有触发这个问题的。后来经过测试发现并不是,真正的原因是 rename 的时候触发器也一并切走,即原表的触发器会变成 _t1_old 这个表的触发器。

(dba:3306)@[dbzz_new_dbversion1]>show triggers;
+-----------------------------------+--------+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------------------------------------------+----------+----------------------+----------------------+--------------------+
| Trigger                           | Event  | Table | Statement                                                                                                                                                                                                                                | Timing | Created                | sql_mode                                                                                                              | Definer  | character_set_client | collation_connection | Database Collation |
+-----------------------------------+--------+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------------------------------------------+----------+----------------------+----------------------+--------------------+
| pt_osc_dbzz_new_dbversion1_t1_ins | INSERT | t1    | REPLACE INTO `dbzz_new_dbversion1`.`_t1_new` (`id`, `name`) VALUES (NEW.`id`, NEW.`name`)                                                                                                                                                | AFTER  | 2024-05-11 11:56:08.21 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION | dba@10.% | utf8mb4              | utf8mb4_0900_ai_ci   | utf8mb4_general_ci |
| pt_osc_dbzz_new_dbversion1_t1_upd | UPDATE | t1    | BEGIN INSERT IGNORE INTO `dbzz_new_dbversion1`.`_t1_new` (`id`, `name`) VALUES (OLD.`id`, OLD.`name`);UPDATE `dbzz_new_dbversion1`.`_t1_new`  set `id` = NEW.`id`, `name` = NEW.`NAME` where `id` = OLD.`id` and `name` = OLD.`NAME`;END | AFTER  | 2024-05-11 11:56:08.20 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION | dba@10.% | utf8mb4              | utf8mb4_0900_ai_ci   | utf8mb4_general_ci |
| pt_osc_dbzz_new_dbversion1_t1_del | DELETE | t1    | DELETE IGNORE FROM `dbzz_new_dbversion1`.`_t1_new` WHERE `dbzz_new_dbversion1`.`_t1_new`.`id` <=> OLD.`id`                                                                                                                               | AFTER  | 2024-05-11 11:56:08.21 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION | dba@10.% | utf8mb4              | utf8mb4_0900_ai_ci   | utf8mb4_general_ci |
+-----------------------------------+--------+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------------------------------------------+----------+----------------------+----------------------+--------------------+
3 rows in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>rename table t1 to _t1_old,_t1_new to t1;
Query OK, 0 rows affected (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>show triggers;
+-----------------------------------+--------+---------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------------------------------------------+----------+----------------------+----------------------+--------------------+
| Trigger                           | Event  | Table   | Statement                                                                                                                                                                                                                                | Timing | Created                | sql_mode                                                                                                              | Definer  | character_set_client | collation_connection | Database Collation |
+-----------------------------------+--------+---------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------------------------------------------+----------+----------------------+----------------------+--------------------+
| pt_osc_dbzz_new_dbversion1_t1_ins | INSERT | _t1_old | REPLACE INTO `dbzz_new_dbversion1`.`_t1_new` (`id`, `name`) VALUES (NEW.`id`, NEW.`name`)                                                                                                                                                | AFTER  | 2024-05-11 11:56:08.21 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION | dba@10.% | utf8mb4              | utf8mb4_0900_ai_ci   | utf8mb4_general_ci |
| pt_osc_dbzz_new_dbversion1_t1_upd | UPDATE | _t1_old | BEGIN INSERT IGNORE INTO `dbzz_new_dbversion1`.`_t1_new` (`id`, `name`) VALUES (OLD.`id`, OLD.`name`);UPDATE `dbzz_new_dbversion1`.`_t1_new`  set `id` = NEW.`id`, `name` = NEW.`NAME` where `id` = OLD.`id` and `name` = OLD.`NAME`;END | AFTER  | 2024-05-11 11:56:08.20 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION | dba@10.% | utf8mb4              | utf8mb4_0900_ai_ci   | utf8mb4_general_ci |
| pt_osc_dbzz_new_dbversion1_t1_del | DELETE | _t1_old | DELETE IGNORE FROM `dbzz_new_dbversion1`.`_t1_new` WHERE `dbzz_new_dbversion1`.`_t1_new`.`id` <=> OLD.`id`                                                                                                                               | AFTER  | 2024-05-11 11:56:08.21 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION | dba@10.% | utf8mb4              | utf8mb4_0900_ai_ci   | utf8mb4_general_ci |
+-----------------------------------+--------+---------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------------------------------------------+----------+----------------------+----------------------+--------------------+
3 rows in set (0.00 sec)

(dba:3306)@[dbzz_new_dbversion1]>

下面一段是官方文档 的描述,虽然没有明确写到 rename 会切触发器,但是侧面反应了 rename 操作其实是会连带触发器一起切走。

If a table has triggers, attempts to rename the table into a different database fail with a Trigger in wrong schema (ER_TRG_IN_WRONG_SCHEMA) error.

(dba:4202)@[dbzz_new_dbversion1]>rename table t1 to 58monitor.t1;
ERROR 1435 (HY000): Trigger in wrong schema
(dba:4202)@[dbzz_new_dbversion1]>

如果有修改库名的需求要十分注意,要提前检查好是否存在触发器,自定义函数,存储过程。

7. 删除旧表

DROP TABLE IF EXISTS `_t1_old` /* generated by server */

根据需求使用该功能,对于可能存在改表风险的场景,建议可以先保留 old 表。

8. 删除触发器

DROP TRIGGER IF EXISTS `dbzz_new_dbversion1`.`pt_osc_dbzz_new_dbversion1_t1_del`

DROP TRIGGER IF EXISTS `dbzz_new_dbversion1`.`pt_osc_dbzz_new_dbversion1_t1_upd`

DROP TRIGGER IF EXISTS `dbzz_new_dbversion1`.`pt_osc_dbzz_new_dbversion1_t1_ins`

至此一次完整的改表结束。

总结

经过上面的梳理分析,我们知道 pt-osc 在改表过程中做了很多事情,现在我们做个简单的总结,主要是想梳理一下这个工具的缺点,毕竟使用这个工具的朋友应该都知道这工具很优秀,很好用。但是往往缺点容易被忽视,希望能帮大家提供一些思路以及规避一些问题。

  • 可能出现死锁,这个问题上面也分析了,这里就不过多介绍了。
  • 触发器会导致业务的dml流量翻倍放大,对负载比较大或者并发比较高的环境可能存在很大的风险,这点需要提前跟业务沟通好,做好风险评估。
  • 支持暂停,但是功能有限。我们知道增量是通过触发器去完成,所以暂停只能暂停拷贝全量数据的任务,对于触发器产生的流量没法暂停。
  • 需要多次获取元数据锁。创建触发器、删除触发器、更新统计信息以及最后的切表都需要获取元数据锁。虽然元数据锁不会阻塞dml,但是获取元数据锁被阻塞,目标表后续的读写都会被阻塞,如果多次获取元数据锁无疑会加大了目标表被阻塞的风险。

以上问题可能还会互相影响,比如当前数据库正在使用 pt-osc 进行改表,这时候如果负载上来了,就可能出现大量慢查询。这种情况下要么暂停改表,要么终止改表。

  • 暂停改表。对于业务的dml流量还是会继续触发触发器,可能不会缓解压力。
  • 终止改表。终止会删除触发器,这时候系统又有大量慢查询,删除触发器可能会被阻塞,进而阻塞目标表的读写操作。

所以对于这种场景,进行干预操作的时候一定要十分谨慎,最好是改表前就准备好操作文档。

最后分享一下我们在改表中遇到的三个案例,也算是使用建议吧。

案例

我们的改表平台在改表上的方案是:满足 Only Modifies Metadata 的就优先使用原生的改表命令进行改表,其次出于风险考虑会优先使用 gh-ost*,对于没法使用 *gh-ost 的场景才会使用 pt-osc。

虽然 pt-osc 是被放在最后才会选择的方案,但是在使用中会发现很多场景使用 gh-ost 并不适用,或者说时间成本更高,对于部分场景在确保稳定及安全的前提下是可以使用 pt-osc 进行的,比如下面的 案例一 和 案例二。

案例一

执行改表命令所在的机器负载很高,使用 gh-ost 就会触发 throttled,然后一直卡着,直到负载降下来了才会继续。所以后来我们加了一个逻辑,如果在改表过程中,执行改表命令所在的机器负载很高就使用 pt-osc 进行。

我们在 DBA 管理机执行改表命令,管理机会有些跑批任务,可能会把机器负载拉起来。

案例二

对于定时改表工单,然后定的是凌晨 2-6 点执行,如果目标表很大,那么执行时间之外的时间就会被暂停,然后第二天这个时间会继续。然后这可能会出现一个问题,这个工单会执行很多天,甚至永远不会完成。

原因是 gh-ost 会优先完成增量数据部分。所以在暂停期间堆积了大量未应用 binlog*,只有当堆积的 *binlog 应用完了才会继续拷贝全量数据。所以这种定时工单场景建议使用 pt-osc 来完成。

案例三

对于添加唯一索引的工单,如果目标表本身存在重复数据,改表后会出现数据丢失,所以需要十分注意。建议关闭 pt-osc 添加唯一索引的功能,使用 gh-ost + hook 的方式添加唯一索引。

以上,仅供参考。

更多技术文章,请访问:https://opensource.actionsky.com/

关于 SQLE

SQLE 是一款全方位的 SQL 质量管理平台,覆盖开发至生产环境的 SQL 审核和管理。支持主流的开源、商业、国产数据库,为开发和运维提供流程自动化能力,提升上线效率,提高数据质量。

✨ Github:https://github.com/actiontech/sqle

📚 文档:https://actiontech.github.io/sqle-docs/

💻 官网:https://opensource.actionsky.com/sqle/

👥 微信群:请添加小助手加入 ActionOpenSource

🔗 商业支持:https://www.actionsky.com/sqle

pt-osc工具原理与实践 MySQL在5.7版本对于online ddl支持的并不是非常优化,比如说将大表int字段类型修改成bigint或者对大表进行字符编码的改造。对于业务来说都是需要停业去处理的,对于高速发展的互联网行业来说,时间就是金钱,所以合理的应用pt-osc工具可以让你的工作效率得到有效的提升 阅读详情

相关推荐

Linux操作文档——pt-osc工具

文章目录一、pt-osc的工作原理二、pt-osc工具的限制三、安装 一、pt-osc的工作原理 1、创建一个和源表一样表结构的新表 2、在新表执行DDL语句 3、在源表创建三个触发器分别对应insert、update、delete操作 4、从源表拷贝数据到新表,拷贝过程中源表通过触发器把新的DML操作更新到新表中 5、rename源表到old表中,把新表rename为源表,默认最后删除源表 二、pt-osc工具的限制 1、源表不能有触发器存在(insert、update、delete) 2、源表必须要

生发真人的博客 1659

pt-osc原理、限制、及与原生online-ddl比较

1. pt-osc工作过程 创建一个和要执行 alter 操作的表一样的新的空表结构(是alter之前的结构) 在新表执行alter table 语句(速度应该很快) 在原表中创建触发器3个触发器分别对应insert,update,delete操作 以一定块大小从原表拷贝数据到临时表,拷贝过程中通过原表上的触发器在原表进行的写操作都会更新到新建的临时表 Rename 原表到old表中,在...

sinat_41780498的博客 1389

pt-osc vs gh-ost:MySQL在线改表工具选型指南(附千万级用户表加索引案例)

本文深度对比了pt-osc与gh-ost两大MySQL在线改表工具的核心原理与适用场景。针对千万级用户表加索引的实战需求,重点分析了gh-ost如何通过基于Binlog的异步复制策略,在最小化业务影响的前提下完成大表结构变更,为高并发场景下的安全操作提供了选型指南。

c6d7e8f9g的博客 983

pt-osc详解

MySQL原生的Online DDL还是有很多限制的,还是会遇到data meta lock的问题等诸多不便,然后就有了我们今天的话题,通过pt-osc在线执行DDL。  一、pt-osc的工作原理 1、创建一个和源表一样表结构的新表2、在新表执行DDL语句(空表嘛,所以。。。)3、在源表创建三个触发器分别对应insert、update、delete操作4、从源表拷贝数据到新表,拷贝过程中源表通过...

richard的专栏(bigdata.han@gmail.com) 6356

使用pt-osc进行无损DDL操作

pt-online-schema-change(以下简称pt-osc)是一款优秀的数据库操作工具。可实现在线修改表结构功能,操作期间不会对阻塞对原表的读写。

掌勺者的博客 215

3种MySQL大表加索引方案对比:Online DDL vs pt-osc vs gh-ost,选型决策树

随着数据量的增长,传统的索引添加方式可能导致长时间锁表,进而引发业务中断。直接在原表上构建索引,而非传统的COPY算法需要创建临时表。pt-online-schema-change(pt-osc)是Percona Toolkit中的明星工具,它通过。这些数据表明,对于千万级表,Online DDL在速度上具有明显优势,而gh-ost在系统资源消耗上表现更为均衡。MySQL 5.6引入的Online DDL功能彻底改变了索引添加的方式。的机制实现真正的无锁表结构变更。,长时间的表锁会阻塞业务请求;

weixin_33691817的博客 351

pt-osc的思考

pt-online-schema-change的几个问题

一条大河 837

BOM 数据模型设计

字段名数据类型必填说明物料编码 (Part Number)字符串是唯一标识,主键。建议包含分类前缀(如STD-GEN-CUS-物料名称 (Part Name)字符串是版本 (Revision)字符串是如AB1.0,与编码组合构成唯一实例。状态 (Status)枚举是设计中(Draft)已发布(Released)已归档(Archived)已作废(Obsoleted)类型 (Type)枚举是专用件(Custom)借用件(Borrowed)通用件(Generic)标准件(Standard)

Davina_yu的博客 1757

【MySQL】执行DDL选择Online DDL还是PT-OSC?

MySQL DDL表结构变更,主要支持Online DDL和PT-OSC模式,但是即使知道两者的工作原理,在什么情况下选择什么模式新增或者更改mysql的表结构,感觉能完整清楚表述出的文章并不太多。因此站在巨人的肩膀上,将大佬的总结,进行理解、补充和记录。以下文章以 MySQL 5.7.24 版本作为讲述依据。

Dan的博客 2760

CodeBuddy 接入 MCP,一键生成网站!

2024 年 11 月,Anthropic 公司搞了个挺有意思的新玩意 - Model Context Protocol(模型上下文协议)简称为 MCP 协议。简单来说,它就是给 AI 和各类工具数据之间搭了个标准化的”桥梁”,让开发者不用再为对接问题头疼了。mcp.so如下图所示,这是 mcp.so 网站中的 MCP Server。使用百度/高德地图分析旅线计算时间接 Puppeteer 自动操作网页使用 Github/Gitlab 让大模型接管代码仓库。

悟空聊架构的专栏 1946

ptrade从零开始学习量化交易第166期【ptrade量化策略研究参考--三个因子挖掘思路】

三个因子挖掘思路

GJ57017_wx的博客 1249

MySQL大表结构修改神器:pt-online-schema-change(pt-osc)全攻略

在MySQL数据库运维中,修改大表结构是一个经典痛点——传统ALTER TABLE命令会锁表(尤其是InnoDB引擎在MySQL 5.7及更早版本),导致业务读写阻塞、服务不可用。为解决这一问题,Percona Toolkit提供的pt-online-schema-change(简称pt-osc)工具应运而生,它能在不锁表的前提下安全修改表结构。本文将从工具选型、环境准备、原理解析到实操避坑,全面讲解pt-osc的使用。

专注Java和MySQL领域知识分享 1916

MPN – 制造商物料编号(延伸)

此外,用户还可以在物料主数据中定义阻止MPN材料的原因,以及在采购订单创建时,是否可以将现有的MPN材料修改为其他MPN材料,从而实现对MPN材料的精细控制和管理。通过使用AMPL,用户可以指定每个MPN材料是否仅能为特定工厂或整个企业所有工厂采购、采购的时间段、是否必须属于特定修订级别、是否只能从特定制造商工厂采购以及是否被阻止及其原因。通过AMPL管理,企业可以规范采购流程,避免使用未经批准的材料,从而提高采购的透明度和效率,同时也为质量控制提供了保障‌。3、QM中的制造商物料。质量信息记录QI01。

yanzibing99的博客 2487

百万级表加字段踩坑实录:pt-osc与gh-ost原理深度对比

讲述一次生产环境大表ALTER失败的踩坑经历,梳理MySQL 8.0 Instant DDL、Online DDL、pt-osc、gh-ost四种方案的原理与差异,附生产环境实操建议和避坑清单

「一个在数据库赛道上边学边分享的学妹」——聚焦国产数据库/信创方向、分享学习笔记、踩坑经验和行业观察。 478

Vue2 - 详细实现解析Markdown内容生成侧边栏Toc目录索引,识别文章内容根据大标题/小标题自动生成多级文章目录,点击目录导航时跳转到文章内容对应的锚点位置带过渡动画,滑过锚点时高亮对应标题

vue2,nuxt2,Markdown,目录,文章目录锚点,滚动高亮导航文字,根据页面滚动高亮显示目录的侧边栏,自动提取标题生成右侧目录,vuemarkdown解析并生成目录,滚动页面的时候文章目录跟着选中效果,侧边栏滚动跟随页面,右边目录固定,左边内容可以滚动并且与左边目录联动,实现监听滚动,锚点定位,文章目录滚动跟随高亮,右边菜单点击目录滚动到对应标题,报错,渲染失败,文章内容,读取#标题符号,自动生成一个目录树,自动生成Toc目录,博客内容生成目录索引,资讯文章,生成锚点目录树TOC,详细示例代码教

🏆 前端领域优质创作者 3670

uniapp [全端兼容] - 实现集成配置百度语音识别转文字,长按或开启录音并将录音文件实时转换为文字,百度智能云API实现上传录音文件并转换成文字案例(苹果安卓App、微信小程序、移动端H5网页)

uniapp对接百度智能云语音识别,uniapp中实现语音识别(app+小程序),百度语音识别录音转文字_百度智能云,uniapp使用百度语音识别语音转文字,uniapp小程序调用百度语音识别,uni-app 百度语音识别文字并展示功能,uniapp配置自定义界面百度语音识别转文字和原生使用方式,uniapp使用百度语音,uni-app语音转文字功能,使用UniApp技术实现录音并将录音转换为文字,uniapp如何实现语音输入功能功能,Uniapp集成百度智能云API实现录音转文字详细教程完整示例代码dem

🏆 前端领域优质创作者 1万+

pt-osc在线重建表导致死锁的分析及对应的优化方案

目录背景先简单介绍一下pt-osc的工作原理环境表的DDL死锁日志分析4.1 生产环境中死锁现场的日志4.2 死锁的分析4.3 根据分析死锁日志和pt-osc原理得到事务的执行次序4.4...

老叶茶馆 1917

【软考高级架构】论文范文16——论分布式缓存架构设计及其应用

随着互联网业务的快速发展,高并发访问和海量数据处理对系统响应时间提出了极高要求,传统基于磁盘的数据库存储已难以满足毫秒级的性能需求。分布式缓存作为一种高性能、可扩展的数据存储中间件,能够有效减轻后端数据库压力、加速热点数据访问、提升系统吞吐量,已成为高并发系统架构中的核心组件。本文以笔者主导的某大型电商平台秒杀系统建设项目为案例,围绕分布式缓存架构设计展开论述。笔者担任系统架构师,主导了缓存选型、数据分片策略、缓存一致性方案、高可用架构及缓存穿透/雪崩/击穿防护机制的设计与落地。

qq837993702的博客 163

PT-Online-Schema-Change 详解

PT-Online-Schema-Change 详解&lt;更多精彩!欢迎关注"小强聊架构" 微信公众号&gt;名字pt-online-schema-change - ALTER tables without locking them. 在线改表工具下载地址https://www.percona.com/doc/percona-toolkit/LATEST/pt-online-schema-cha...

xq blog 1798

工业级稳定!C# 实现 Modbus RTU 多设备并发通信实战

摘要:本文提出一种基于C#的Modbus RTU多设备并发通信方案,解决工业自动化中单线程通信的串口抢占、数据阻塞等问题。采用任务队列+线程安全锁+异步调度的三层架构,实现16台及以上RTU设备的稳定并发读写。方案包含串口管理单例、Modbus通信核心和任务调度层,支持超时重试、异常捕获等工业级容错机制。通过NModbus库和.NET框架,提供可直接用于工业现场的代码实现,已在工控项目中长期验证,有效提升多设备通信效率和稳定性。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 581

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

[小技巧60]深入解析 MySQL Online DDL:MySQL Online DDL、pt-osc 与 gh-ost 机制与最佳实践

深入解析 MySQL Online DDL:MySQL Online DDL、pt-osc 与 gh-ost 机制与最佳实践

分享工作中解决的问题,希望能帮到你 1281

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Mysql-场景篇-2-线上高频访问的Mysql表,如何在线修改表结构影响最小?-5--INSTANT DDL,gh-ost,pt-osc三种在线DDL方案对比分析

仅支持InnoDB存储引擎。不支持修改主键、索引结构、外键约束。修改列类型必须通过重建表实现(如gh-ost或pt-osc)。优先使用INSTANT DDL:对于支持的操作(如添加/删除列),原生方案性能最优。复杂DDL操作:选择gh-ost(高并发场景)或pt-osc(低并发场景)。始终测试与备份:任何DDL操作前需在测试环境验证,并保留旧表或数据备份。向阳前行,Dare To Be!!!

weisian的博客 3万+

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

MySQL在线DDL操作与pt-osc工具

对于中小型表且对锁表容忍度较高的场景,推荐使用 MySQL 原生的 Online DDL;对于大型表、高并发系统或不允许停机维护的生产环境,优先考虑使用 pt-osc 工具;实施前务必做好备份和测试,尤其是使用 pt-osc 时要注意磁盘空间和负载压力;可结合自动化平台实现灰度发布、自动回滚等高级能力,进一步提升变更的安全性。在保证数据一致性的前提下,实现表结构变更的平滑过渡,最大程度减少对业务的影响。

zevjay的博客 680

pt-osc使用限制和风险(优缺点)

原理: 根据原表结构创建一个新表; 按照pt-osc的alter语句修改新表; 将原表中的数据copy到新表中去; 通过触发器将原表copy数据期间的数据更新应用到新表中去; 将原表重命名,将新表重命名成原表,然后删除原表。 pt-osc的优点: 执行alter阶段不阻塞读和写。 pt-osc的缺点: 1.原表不能存在触发器,因为pt-osc需要通过触发器将原表copy数据阶段产生的数据#应用...

qq_40932740的博客 894

3种MySQL大表索引变更方案对比:Online DDL vs pt-osc vs gh-ost

本文深度对比了MySQL大表索引变更的三大方案:Online DDL、pt-osc和gh-ost,分析了各自的核心原理、性能表现及适用场景。通过实际案例和技术细节,帮助开发者选择最适合的索引变更策略,提升数据库操作效率并降低业务风险。特别推荐MySQL 8.0的INSTANT算法和gh-ost的无触发器架构,适用于高并发和大数据量场景。

weixin_34258838的博客 371
上一篇: MySQL 核心模块揭秘 | 19 期 | 锁模块里有什么?什么样?
下一篇: 一个 Clickhouse 集群磁盘损坏恢复数据的案例
爱可生开源社区
博客等级 码龄8年 1937粉丝 1004原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值