MySQL中的Online DDL

2024-06-03 11:48
文章标签 mysql ddl online database

本文主要是介绍MySQL中的Online DDL,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

MySQL中的Online DDL

记得有一天快下班的时候,一位开发同事找到我说,需要对一个表做变更,数据量据说有上千万,而当时是使用的MySQL版本是5.5,这可如何是好,对于在线业务要求高的情况下,这种需求真是让人头疼。

而在早期的版本中,这种问题就更让人无语了。在Oracle中这个问题解决的较早,当然在很多技术实现细节上,Oracle和MySQL还是蛮大的差距。Oracle中有在线重定义的方案物化视图prebuilt和在线重定义 (r10笔记第25天),而且本身对于一些DDL的操作代价要比MySQL低。不过在碰到添加字段且加默认值的情况,在Exadata上跑性能测试,问题依旧是很棘手。以至于在有些业务中,对某些核心的大表,有些公司是使用视图来达到这种动态的字段扩展而非直接添加字段。这,应该算是一场技术上的硬仗。

这类问题的根本和数据的存储也密不可分。有兴趣可以看看。MySQL和Oracle的添加字段的处理差别 (r10笔记第73天)

MySQL中这类问题有了一种叫OSC的工具之后,情况有了很大的改观。最早是facebook来做的这件事情,后来Percona进行了改变,使用perl实现,因为功能全面,支持的完善,现在基本上成了标准的行业工具。简称pt-osc。

在MySQL 5.5中,这类问题使用pt-osc来处理就很有效了,在MySQL 5.6推出的online DDL中,已经原生支持,在5.7中已经发展很不错了,如此一来,pt-osc的支持算是一种可选的方式。而也可以由此看出,技术上的重大突破会逐步降低维护的复杂度,所以水航船高,各行各业都有相似之处。

pt工具本身的安装部署很简单,可以参考 Percona-toolkit的安装和配置(r8笔记第86天)

简单的使用pt-table-checksu和pt-table-sync可以参考MySQL主从不一致的修复过程(r10笔记第96天)

首先说明不是所有的DDL都会持续很长时间,比如修改表名,这是一个很有意思的操作,无论表大小,操作效率都很高。

比如我们存在一个表 t_user_login_record,数据量2000万。

-rw-rw---- 1 mysql mysql 8840 Oct 13 17:04 t_user_login_record.frm

-rw-rw---- 1 mysql mysql 3162505216 Oct 13 17:09 t_user_login_record.ibd

如果想修改为newtest

> alter table t_user_login_record rename to newtest;

Query OK, 0 rows affected (0.03 sec)

这个过程本质上就是数据字典信息的修改。如果你可以理解的更通俗一点,就是修改文件名。

-rw-rw---- 1 mysql mysql 8840 Oct 13 17:04 newtest.frm

-rw-rw---- 1 mysql mysql 3162505216 Oct 13 17:09 newtest.ibd

MySQL 5.5原生的DDL代价

为什么MySQL5.5中很多DDL操作的代价很高呢。因为很多场景的处理都是在做数据的复制。

比如我们添加一个字段,添加默认值。

alter table newtest add column newcol varchar(10) default '';

MySQL原生的操作就是创建一个临时的表,开始表数据的复制。

-rw-rw---- 1 mysql mysql 8840 Oct 13 17:04 newtest.frm

-rw-rw---- 1 mysql mysql 3162505216 Oct 13 17:09 newtest.ibd

-rw-rw---- 1 mysql mysql 8874 Dec 5 11:25 #sql-2931_4807af.frm

-rw-rw---- 1 mysql mysql 58720256 Dec 5 11:25 #sql-2931_4807af.ibd

在MySQL5.5中,如果在DDL执行的过程中,在另外一个窗口中做一个insert操作,不好意思,这类操作就会阻塞,持续时间会很长。

insert into newtest(game_type,login_time,login_account,cn_master,client_ip) values(1,'2013-08-16 16:22:10','150581500032','572031626','183.128.143.113');

如果查看show processlist的结果,就会发现临时表复制的信息和锁的信息。

State | Info

--------------------------------+--------------------------------------------------------------

Waiting on empty queue | NULL

copy to tmp table | alter table newtest add column newcol varchar(10) default ''

Waiting for table metadata lock | insert into newtest(game_type,login_time,login_account,cn_mas

NULL | show processlist

如果查看show engine innodb status\G的结果,会发现一些很细致的锁信息。

---TRANSACTION 481BF2, not started

MySQL thread id 4721141, OS thread handle 0x7f99780bc700, query id 18882493 localhost root Waiting for table metadata lock

insert into newtest(game_type,login_time,login_account,cn_master,client_ip) values(1,'2013-08-16 16:22:10','150581500032','572031626','183.128.143.113')

---TRANSACTION 4821A2, ACTIVE 1 sec inserting, thread declared inside InnoDB 253

mysql tables in use 2, locked 2

46 lock struct(s), heap size 6960, 6887 row lock(s), undo log entries 6887

MySQL thread id 4720559, OS thread handle 0x7f93710b9700, query id 18881573 localhost root copy to tmp table

alter table newtest add column newcol varchar(10) default ''

TABLE LOCK table `test`.`newtest` trx id 4821A2 lock mode IS

TABLE LOCK table `test`.`#sql-2931_4807af` trx id 4821A2 lock mode IX

TABLE LOCK table `test`.`#sql-2931_4807af` trx id 4821A2 lock mode AUTO-INC

可以看到锁的信息比我们想的要复杂一些。

当然这个阻塞的时长还是很不乐观的,可能十分钟,数十分钟,取决于DDL的时长。

> insert into newtest(game_type,login_time,login_account,cn_master,client_ip) values(1,'2013-08-16 16:22:10','150581500032','572031626','183.128.143.113');

Query OK, 1 row affected (5 min 33.04 sec)

MySQL 5.7中的DDL对比

在MySQL 5.7中差别就很大了,一模一样的操作,在MySQL 5.7中还是创建一个临时数据表的数据复制。

-rw-r----- 1 mysql mysql 8874 Dec 5 16:47 newtest.frm

-rw-r----- 1 mysql mysql 3900702720 Dec 5 17:05 newtest.ibd

-rw-r----- 1 mysql mysql 8840 Dec 5 17:33 #sql-6273_9989e.frm

-rw-r----- 1 mysql mysql 46137344 Dec 5 17:33 #sql-ib276-3638407390.ibd

同样的DML语句全然没有压力。

> insert into newtest(game_type,login_time,login_account,cn_master,client_ip) values(1,'2013-08-16 16:22:10','150581500032','572031626','183.128.143.113');

Query OK, 1 row affected (0.01 sec)

查看show engine innodb status\G的结果就有很大的差别。

mysql tables in use 1, locked 1

0 lock struct(s), heap size 1136, 0 row lock(s)

MySQL thread id 628894, OS thread handle 140140882102016, query id 9565763 localhost root altering table

alter table newtest drop column newcol

Trx read view will not see trx with id >= 909683, sees < 909682 怎么去理解online DDL的一些实现原理呢。我们还是可以使用pt-osc来做。

我们就配置一个用户,在5.7下面的语句有了改进,最好使用create user的方式。

GRANT ALL ON *.* TO 'pt_osc'@'test%' identified by 'pt_osc';

然后使用pt-online-schema-change来完成。这里我们需要给表newtest添加一个索引,基于login_time字段

./pt-online-schema-change --host=10.11.128.99 -u pt_osc -p pt_osc --alter='add index ind_login_time_newtest(login_time)' --print --execute D=test,t=newtest

这个时候看看数据目录,内容就很丰富了。-rw-r----- 1 mysql mysql 8840 Dec 5 17:33 newtest.frm

-rw-r----- 1 mysql mysql 3527409664 Dec 5 17:37 newtest.ibd

-rw-r----- 1 mysql mysql 8840 Dec 5 18:13 _newtest_new.frm

-rw-r----- 1 mysql mysql 15728640 Dec 5 18:13 _newtest_new.ibd

-rw-r----- 1 mysql mysql 1213 Dec 5 18:13 newtest.TRG

-rw-r----- 1 mysql mysql 39 Dec 5 18:13 pt_osc_test_newtest_del.TRN

-rw-r----- 1 mysql mysql 39 Dec 5 18:13 pt_osc_test_newtest_ins.TRN

-rw-r----- 1 mysql mysql 39 Dec 5 18:13 pt_osc_test_newtest_upd.TRN

可以很明显看到创建了3个触发器(针对增删改操作),创建了临时的表复制数据。

命令的部分输出如下:

Altering `test`.`newtest`...

Creating new table...

CREATE TABLE `test`.`_newtest_new` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键,自增长',

。。。,

PRIMARY KEY (`id`),

KEY `ind_tmp_account1` (`login_account`)

) ENGINE=InnoDB AUTO_INCREMENT=22681849 DEFAULT CHARSET=utf8

Created new table test._newtest_new OK.

Waiting forever for new table `test`.`_newtest_new` to replicate to teststd.test.com...

Altering new table...

ALTER TABLE `test`.`_newtest_new` add index ind_login_time_newtest(login_time)

Altered `test`.`_newtest_new` OK.

2016-12-05T18:13:31 Creating triggers...

CREATE TRIGGER `pt_osc_test_newtest_del` AFTER DELETE ON `test`.`newtest` FOR EACH ROW DELETE IGNORE FROM `test`.`_newtest_new` WHERE `test`.`_newtest_new`.`id` <=> OLD.`id`

。。。创建INSERT,DELETE TRIGGER

2016-12-05T18:13:31 Created triggers OK.

2016-12-05T18:13:31 Copying approximately 22571280 rows...

INSERT LOW_PRIORITY IGNORE INTO `test`.`_newtest_new` (`id`, `game_type`, `login_time`, `login_account`, `cn_master`, `client_ip`) SELECT `id`, `game_type`, `login_time`, `login_account`, `cn_master`, `client_ip` FROM `test`.`newtest` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) AND ((`id` <= ?)) LOCK IN SHARE MODE /*pt-online-schema-change 5214 copy nibble*/

SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`newtest` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) ORDER BY `id` LIMIT ?, 2 /*next chunk boundary*/

Copying `test`.`newtest`: 1% 27:54 remain

Copying `test`.`newtest`: 3% 27:43 remain

Copying `test`.`newtest`: 98% 00:25 remain

2016-12-05T18:45:16 Copied rows OK.

2016-12-05T18:45:16 Analyzing new table...

2016-12-05T18:45:16 Swapping tables...

RENAME TABLE `test`.`newtest` TO `test`.`_newtest_old`, `test`.`_newtest_new` TO `test`.`newtest`

2016-12-05T18:45:16 Swapped original and new tables OK.

2016-12-05T18:45:16 Dropping old table...

DROP TABLE IF EXISTS `test`.`_newtest_old`

2016-12-05T18:45:17 Dropped old table `test`.`_newtest_old` OK.

2016-12-05T18:45:17 Dropping triggers...

DROP TRIGGER IF EXISTS `test`.`pt_osc_test_newtest_del`;

DROP TRIGGER IF EXISTS `test`.`pt_osc_test_newtest_upd`;

DROP TRIGGER IF EXISTS `test`.`pt_osc_test_newtest_ins`;

2016-12-05T18:45:17 Dropped triggers OK.

Successfully altered `test`.`newtest`.

这个过程用Percona的一张图来说明,用流程化的方式来解读。

简单题几个问题来加深对于online DDL的理解。

1.如果创建索引,这个过程中创建的索引是在源表上还是新表上?

答:要简单来论证可以使用strings来解读临时创建的数据表,这里是_newtest_new.frm,新创建的索引ind_login_time_newtest赫然在列。

# strings _newtest_new.frm

PRIMARY

ind_tmp_account1

ind_login_time_newtest

InnoDB ....

2. pt-osc在系统层面文件的变化情况是怎么样的?

答:我们可以去一些临界点来验证。

开始pt-osc的操作时,文件的情况如下。

-rw-r----- 1 mysql mysql 8840 Dec 5 17:33 newtest.frm

-rw-r----- 1 mysql mysql 3527409664 Dec 5 18:36 newtest.ibd

-rw-r----- 1 mysql mysql 8840 Dec 5 18:13 _newtest_new.frm

-rw-r----- 1 mysql mysql 4345298944 Dec 5 18:45 _newtest_new.ibd

-rw-r----- 1 mysql mysql 1213 Dec 5 18:13 newtest.TRG

-rw-r----- 1 mysql mysql 39 Dec 5 18:13 pt_osc_test_newtest_del.TRN

-rw-r----- 1 mysql mysql 39 Dec 5 18:13 pt_osc_test_newtest_ins.TRN

-rw-r----- 1 mysql mysql 39 Dec 5 18:13 pt_osc_test_newtest_upd.TRN

在变更完成前的一瞬间,文件情况如下,可以看到newtest.ibd和_newtest_new.ibd的切换。

-rw-r----- 1 mysql mysql 8840 Dec 5 17:33 newtest.frm

-rw-r----- 1 mysql mysql 3527409664 Dec 5 18:36 newtest.ibd

-rw-r----- 1 mysql mysql 8840 Dec 5 18:13 _newtest_new.frm

-rw-r----- 1 mysql mysql 4345298944 Dec 5 18:45 _newtest_new.ibd

-rw-r----- 1 mysql mysql 1213 Dec 5 18:13 newtest.TRG

-rw-r----- 1 mysql mysql 39 Dec 5 18:13 pt_osc_test_newtest_del.TRN

-rw-r----- 1 mysql mysql 39 Dec 5 18:13 pt_osc_test_newtest_ins.TRN

-rw-r----- 1 mysql mysql 39 Dec 5 18:13 pt_osc_test_newtest_upd.TRN

再次查看,触发器都会一一删除。

-rw-r----- 1 mysql mysql 8840 Dec 5 18:13 newtest.frm

-rw-r----- 1 mysql mysql 4353687552 Dec 5 18:45 newtest.ibd

通过这个过程可以加深对于online DDL的实现原理的理解,不过MySQL 5.7中原生的online DDL原理和pt-osc还是有一些差别,仅仅作为一个参考。

所测试的场景都是使用了默认的选项copy而非inplace

ALTER TABLE的补充语法为:ALGORITHM [=] {DEFAULT|INPLACE|COPY}

对于online DDL的操作,更多的细节稍后再来一篇继续补充完善。

欢迎关注我的公众号(jianrong-notes),真知灼见谈不上,重在技术分享交流。

本文为头条号作者发布,不代表今日头条立场。

这篇关于MySQL中的Online DDL的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1026906

相关文章

SQL中的外键约束

外键约束用于表示两张表中的指标连接关系。外键约束的作用主要有以下三点: 1.确保子表中的某个字段(外键)只能引用父表中的有效记录2.主表中的列被删除时,子表中的关联列也会被删除3.主表中的列更新时,子表中的关联元素也会被更新 子表中的元素指向主表 以下是一个外键约束的实例展示

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

如何去写一手好SQL

MySQL性能 最大数据量 抛开数据量和并发数,谈性能都是耍流氓。MySQL没有限制单表最大记录数,它取决于操作系统对文件大小的限制。 《阿里巴巴Java开发手册》提出单表行数超过500万行或者单表容量超过2GB,才推荐分库分表。性能由综合因素决定,抛开业务复杂度,影响程度依次是硬件配置、MySQL配置、数据表设计、索引优化。500万这个值仅供参考,并非铁律。 博主曾经操作过超过4亿行数据

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份

[MySQL表的增删改查-进阶]

🌈个人主页:努力学编程’ ⛅个人推荐: c语言从初阶到进阶 JavaEE详解 数据结构 ⚡学好数据结构,刷题刻不容缓:点击一起刷题 🌙心灵鸡汤:总有人要赢,为什么不能是我呢 💻💻💻数据库约束 🔭🔭🔭约束类型 not null: 指示某列不能存储 NULL 值unique: 保证某列的每行必须有唯一的值default: 规定没有给列赋值时的默认值.primary key:

MySQL-CRUD入门1

文章目录 认识配置文件client节点mysql节点mysqld节点 数据的添加(Create)添加一行数据添加多行数据两种添加数据的效率对比 数据的查询(Retrieve)全列查询指定列查询查询中带有表达式关于字面量关于as重命名 临时表引入distinct去重order by 排序关于NULL 认识配置文件 在我们的MySQL服务安装好了之后, 会有一个配置文件, 也就

Java 连接Sql sever 2008

Java 连接Sql sever 2008 /Sql sever 2008 R2 import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class TestJDBC

Mysql BLOB类型介绍

BLOB类型的字段用于存储二进制数据 在MySQL中,BLOB类型,包括:TinyBlob、Blob、MediumBlob、LongBlob,这几个类型之间的唯一区别是在存储的大小不同。 TinyBlob 最大 255 Blob 最大 65K MediumBlob 最大 16M LongBlob 最大 4G