背景

MySQL 8.0 原子DDL 是一个复杂的过程,涉及比较多的模块,例如:MDL 锁,表定义缓存,行格式,Row Log,DDL Log,online 属性,表空间物理文件操作等。本文主要通过与MySQL5.7版本的对比讲述原子性相关的实现。

在 8.0 之前的版本中使用了Sever层的Frm文件作为元数据保存的方式,这样做可以让多个存储引擎都使用统一的定义规范。但是也带来了一些问题,InnoDB引擎本身也做了表定义的存储,只给InnoDB引擎使用。那么Frm物理文件的操作和 InnoDB事务性表定义的更改之间如果发生Crash,就会造成Server层的元数据和InnoDB的数据不一致。

例如 Alter table 的过程中,需要产生临时表来存储新定义的表数据,如果在新旧表定义Rename过程(DDL操作的一个环节)中发生Crash,会造成表的不可访问,因为有可能FRM文件是旧的定义,但是InnoDB的同名表却是新的定义。

又例如,如果frm文件被误删除了,导致表无法被打开,如果需要删除表就需要 CDB 的 Drop Table Force功能跳过frm的检查,直接从InnoDB删除表。

MetaData Before 8.0

MySQL 8.0 的元数据结构如下所示:

在 8.0 之前 MySQL 的元数据分散存储在三个不同的地方:物理文件、MyISAM引擎、InnoDB引擎。物理文件主要存储 frm,opt,trg 等定义信息,会存在与InnoDB不一致的情况,没有日志保护。系统表 user/proc/events 等信息存储在MyISAM引擎中,不支持事务。InnoDB引擎则是存储SYS_*系统表,例如SYS_TABLES,SYS_INDEXES 等。由于 物理文件和非事务引擎元数据表的存在很难做到DDL的原子性。

例如,ALTER TABLE 过程中涉及到的元数据信息变化如下所示:

DDL 阶段

FRM 文件

IBD 文件

信息描述

Start

table_test.frm

table_test.ibd

原表

DDL Prepare

table_test.frm

table_test.ibd

原表

#sql-5810_3.frm

#sql-ib37-952053511.ibd

新定义的临时表

DDL Alter

同上

同上

同上

DDL Commit 1(InnoDB Commit)

同上

InnoDB Commit:table_test.ibd --> #sql-ib38-952053512.ibd#sql-ib37-952053511.ibd --> table_test.ibd

将原表ibd和新定义表ibd互换名字

InnoDB Commit:Drop  #sql-ib38-952053512.ibd

删除原定义的表

DDL Commit 2(Server Commit)

table_test.frm --> #sql-2add_3.frmand drop

table_test.ibd

frm文件互换名字

#sql-5810_3.frm --> table_test.frm

Finish

table_test.frm

table_test.ibd

新定义的表

如果 DDL Commit 1 阶段之后发生Crash,那么Server层和InnoDB层的表定义是不同的,表访问会失败。

MetaData After 8.0

在 MySQL 8.0 中Data Dictionary 通过将系统表存储在InnoDB引擎中,构建了一套元数据存储和读取的服务框架,其中包括 DD Client 和 Storage Adaptor。

SQL层的Table Define Cache之前通过读取FRM文件来缓存定义,从而Open Table 进行访问,现在需要通过DD Client访问存储在InnoDB中的元数据。

元数据系统表有了InnoDB事务系统的支持,MySQL 8.0 将之前版本中多个事务完成的一个DDL操作变成一个 DDL Trx 事务去完成(也有其他辅助事务,但不影响DDL Trx 主导的DDL的原子性)。其实现方式就是改造元数据存储方案,将元数据和物理操作统一存储到了 InnoDB 引擎中,通过 DDL 对元数据表操作的事务的原子性,达到DDL操作的原子性。DDL Trx 事务提交则 DDL 完成,如果回滚则 DDL 执行的所有操作都可以回滚,包括:元数据表回滚和文件操作回滚。也就是原子 DDL 需要元数据操作的原子性和文件(物理)操作的原子性。

原子保证(一)InnoDB New DD,解决元数据操作原子性

MySQL8.0 中新的数据字典 Data Dictionary 是基于 InnoDB 存储引擎的事务表实现的,我们可以通过InnoDB提供的接口看到都有哪些元数据表。

通过设置 SET SESSION debug='+d,skip_dd_table_access_check'; 可以访问元数据表。

New Data Dictionary 代替了之前分散在不同地方的元数据,用于保存系统元数据,这些表会伴随着DDL的进行而进行各种操作,例如:创建一个表的时候,会向tables系统表中插入一行,会向 indexes 系统表中插入该table id以及其索引信息,多个索引就插入多个行,也会向column系统表中插入table id以及对应的列信息。值得注意的是,所有这些修改都是通过同一个DDL Trx进行的,如果事务提交则系统表的修改提交,如果DDL回滚,这些修改也会通过UNDO LOG进行回滚。Data Dictioanry 系统表解决的是之前版本Server层和InnoDB层定义不一致的问题,现在的DD tables通过InnoDB事务系统做到了原子性。

DD通过统一的接口设计提供给外层调用,其实现如下图所示:

8.0 Data Dictionary 的设计分为三层:Client 层,接口转换层,存储层。

  • Client层:主要负责对外提供统一访问接口以及缓存管理,SQL层的Table Define Cache就是通过DD Client 接口去获取那些之前需要从FRM文件中读的内容。同样,InnoDB层的Dict Cache也是通过DD Client读取的。
  • 转换层:负责将Client的请求封装成对应系统表的访问方法
  • 存储层:就是InnoDB表的存储和访问方法,和用户表一样。

一个典型的调用堆栈如下图所示:

原子保证(二)DDL Log 解决物理表空间文件操作原子性

DDL 操作会涉及到物理文件的操作,例如Btree的创建和释放,表空间文件ibd的创建和删除等,这样的物理操作也需要能做到可回滚,以保证DDL的原子操作。

DDL Log 被引入进来以解决物理操作的原子性,Create Table、Alter Table、Drop Table、Rename Table、Create Index 等操作都会涉及DDL Log表的修改。

DDL Log 系统表的定义如下:

mysql> show create table mysql.innodb_ddl_log \G*************************** 1. row ***************************       Table: innodb_ddl_logCreate Table: CREATE TABLE `innodb_ddl_log` (  `id` bigint unsigned NOT NULL AUTO_INCREMENT,  `thread_id` bigint unsigned NOT NULL,  `type` int unsigned NOT NULL,  `space_id` int unsigned DEFAULT NULL,  `page_no` int unsigned DEFAULT NULL,  `index_id` bigint unsigned DEFAULT NULL,  `table_id` bigint unsigned DEFAULT NULL,  `old_file_path` varchar(512) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,  `new_file_path` varchar(512) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,  PRIMARY KEY (`id`),  KEY `thread_id` (`thread_id`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC;

DDL Log 用以记录一个DDL事务所做的文件物理更改,有两个方面的作用:

  1. 回滚的时候,为了保证DDL事务的物理文件新增操作可回滚,例如创建的ibd要删除,创建的物理索引树要释放。类似“UNDO LOG”的回滚作用。
  2. 提交之后,为了保证DDL事务的物理文件删除操作可回滚,DDL事务过程中删除操作不能立刻执行,因为一旦真正删除就不能回滚了,所以将其记录到DDL Log中。放到Commit之后再执行。

此外,Rename Log 重命名操作,Alter Table,Rename Table 会使用。

下面举例说明:

Create Table 的 DDL Log 操作(作用1):

[MY-012473] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=7, thread_id=8, space_id=4, old_file_path=./test/test.ibd][MY-012478] [InnoDB] DDL log delete : 7[MY-012477] [InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id=8, thread_id=8, table_id=1066, new_file_path=test/test][MY-012478] [InnoDB] DDL log delete : 8[MY-012472] [InnoDB] DDL log insert : [DDL record: FREE, id=9, thread_id=8, space_id=4, index_id=156, page_no=4][MY-012478] [InnoDB] DDL log delete : 9[MY-012485] [InnoDB] DDL log post ddl : begin for thread id : 8[MY-012486] [InnoDB] DDL log post ddl : end for thread id : 8

从日志看有三种 ddl log type 的日志,日志其实描述了一个逆向操作,DDL 创建的物理文件或者索引树,这些物理操作怎么回滚,那么就写入了一个物理操作的逆向操作。

创建了表空间文件就写DELETE SPACE,创建了索引树就写 FREE TREE,内存中保留这个表的定义就写清除表定义。

值得关注的是,其中还有 DDL log delete 操作,这个其实是删除刚刚写入的 ddl log 日志。因为这些日志需要在DDL事务提交的时候全部删除,不能够保留到COMMIT之后,因为成功提交之后是不能删除这些文件和索引树的,那么这里DDL就用了DDL Trx之外的事务做 ddl log 日志的insert操作,该insert事务立刻提交,DDL trx 读取这个 ddl log record并将其标记删除,如果DDL Trx 成功Commit了,那么删除生效,ddl log 被清理。如果DDL Trx失败回滚了,那么 ddl log 日志保留下来了,按照日志的操作回滚即可。

Drop Table 的 DDL Log 操作(作用2):

[InnoDB] DDL log insert : [DDL record: DROP, id=10, thread_id=8, table_id=1066][InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=11, thread_id=8, space_id=4, old_file_path=./test/test.ibd][InnoDB] DDL log post ddl : begin for thread id : 8[InnoDB] DDL log replay : [DDL record: DELETE SPACE, id=11, thread_id=8, space_id=4, old_file_path=./test/test.ibd][InnoDB] DDL log replay : [DDL record: DROP, id=10, thread_id=8, table_id=1066][InnoDB] DDL log post ddl : end for thread id : 8

如上所述,Drop Table 操作 DDL Log 记录需要在 DDL Trx Commit成功后需要删除的物理操作。Drop Table需要删除独立表空间文件,就写DELETE SPACE并给出路径。和Create Table不同,这里没有delete ddl log操作,因为这些日志是需要留给Commit之后的Post DDL阶段做物理删除操作。

如果 Post DDL 阶段没有来得及做就Crash了,重启之后的会继续读取 DDL Log 表按照日志类型做相应的操作。

Alter Table 的 DDL  Log 操作

// ======================Prepare=============================================[MY-012473] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=15, thread_id=8, space_id=6, old_file_path=./test/#sql-ib1067-850981604.ibd][MY-012478] [InnoDB] DDL log delete : 15[MY-012477] [InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id=16, thread_id=8, table_id=1068, new_file_path=test/#sql-ib1067-850981604][MY-012478] [InnoDB] DDL log delete : 16[MY-012472] [InnoDB] DDL log insert : [DDL record: FREE, id=17, thread_id=8, space_id=6, index_id=158, page_no=4][MY-012478] [InnoDB] DDL log delete : 17// ======================Alter=============================================[MY-000000] [InnoDB] TXSQL: parallel_read_threads with 1 threads, parallel_sort_threads with 1 threads, sql: alter table test add id1 int, algorithm = inplace, sample_step: 1, max_sample_cnt: 0, skip_pk_sort: 1// ======================Commit=============================================[MY-012475] [InnoDB] DDL log insert : [DDL record: DROP, id=18, thread_id=8, table_id=1067][MY-012474] [InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=19, thread_id=8, space_id=5, old_file_path=./test/#sql-ib1068-850981605.ibd, new_file_path=./test/test.ibd][MY-012478] [InnoDB] DDL log delete : 19[MY-012476] [InnoDB] DDL log insert : [DDL record: RENAME TABLE, id=20, thread_id=8, table_id=1067, old_file_path=test/#sql-ib1068-850981605, new_file_path=test/test][MY-012478] [InnoDB] DDL log delete : 20[MY-012474] [InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=21, thread_id=8, space_id=6, old_file_path=./test/test.ibd, new_file_path=./test/#sql-ib1067-850981604.ibd][MY-012478] [InnoDB] DDL log delete : 21[MY-012476] [InnoDB] DDL log insert : [DDL record: RENAME TABLE, id=22, thread_id=8, table_id=1068, old_file_path=test/test, new_file_path=test/#sql-ib1067-850981604][MY-012478] [InnoDB] DDL log delete : 22[MY-012475] [InnoDB] DDL log insert : [DDL record: DROP, id=23, thread_id=8, table_id=1067][MY-012473] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=24, thread_id=8, space_id=5, old_file_path=./test/#sql-ib1068-850981605.ibd][MY-012485] [InnoDB] DDL log post ddl : begin for thread id : 8[MY-012479] [InnoDB] DDL log replay : [DDL record: DELETE SPACE, id=24, thread_id=8, space_id=5, old_file_path=./test/#sql-ib1068-850981605.ibd][MY-012479] [InnoDB] DDL log replay : [DDL record: DROP, id=23, thread_id=8, table_id=1067][MY-012479] [InnoDB] DDL log replay : [DDL record: DROP, id=18, thread_id=8, table_id=1067][MY-012486] [InnoDB] DDL log post ddl : end for thread id : 8

这个是 Alter DDL 的三阶段产生的 DDL Log操作:

  • Prepare 阶段:创建临时名字的表用于存放新的定义的表数据,类似 Create Table。
  • Alter 阶段就是数据的转移,没有使用DDL Log。
  • Commit 阶段:InnoDB 的Alter Commit需要将InnoDB表做重命名,因此有 RENAME SPACE日志。此时的DDL Log日志仍是逆向操作的日志,代表回滚的时候要重新RENAME回来。
  • 最后Commit还需要将原表删掉,此时的原表已经是一个临时的名字了。这部分日志交给 Post DDL 阶段处理。

注意:

目前腾讯云数据库MySQL 8.0特惠活动,不限新老用户,最低5折起

MySQL 8.0 新特性-原子DDL的更多相关文章

  1. Mysql 8.0 新特性测试

    Mysql 8.0 新特性测试 Role MySQL8.0版本添加了role特性,role是一种逻辑概念是权限的集合,可以将一个或以上的权限赋予给role,再将role赋给user.Oracle,Po ...

  2. MySQL 8.0 新特性梳理汇总

    一 历史版本发布回顾 从上图可以看出,基本遵循 5+3+3 模式 5---GA发布后,5年 就停止通用常规的更新了(功能不再更新了): 3---企业版的,+3年功能不再更新了: 3 ---完全停止更新 ...

  3. MySQL 8.0新特性之原子DDL

    文章来源:爱可生云数据库 简介 MySQL8.0 开始支持原⼦ DDL(atomic DDL),数据字典的更新,存储引擎操作,写⼆进制日志结合成了一个事务.在没有原⼦DDL之前,DROP TABLE ...

  4. 干货 | 解读MySQL 8.0新特性:Skip Scan Range

    MySQL从8.0.13版本开始支持一种新的range scan方式,称为Loose Skip Scan.该特性由Facebook贡献.我们知道在之前的版本中,如果要使用到索引进行扫描,条件必须满足索 ...

  5. Mysql 8.0 新特性

    转载:https://www.jianshu.com/p/be29467c2b0c

  6. MySQL8.0新特性——支持原子DDL语句

    MySQL 8.0开始支持原子数据定义语言(DDL)语句.此功能称为原子DDL.原子DDL语句将与DDL操作关联的数据字典更新,存储引擎操作和二进制日志写入组合到单个原子事务中.即使服务器在操作期间暂 ...

  7. 【mysql】mysq8.0新特性

    一.MySQL8.0简介   mysql8.0现在已经发布,2016-09-12第一个DM(development milestone)版本8.0.0发布.新的版本带来很多新功能和新特性,对性能也得到 ...

  8. MySQL8.0新特性实验1

    Server层,选项持久化 mysql> show variables like '%max_connections%';+------------------------+-------+| ...

  9. 跨时代的MySQL8.0新特性解读

    目录 MySQL发展历程 MySQL8.0新特性 秒级加列 性能提升 文档数据库 SQL增强 共用表表达式(CTEs) 不可见索引(Invisible Indexes) 降序索引(Descending ...

  10. Django 2.0 新特性 抢先看!

    一.Python兼容性 Django 2.0支持Python3.4.3.5和3.6.Django官方强烈推荐每个系列的最新版本. 最重要的是Django 2.0不再支持Python2! Django ...

随机推荐

  1. Jmeter之逻辑控制器---while控制器

    while控制器与编程语言中的while语句一样,当条件为真时继续执行,不为真时则跳出while循环体,不再执行. while控制器相对于循环控制器来说多了个条件判断,下面为while控制器使用案例. ...

  2. linux挖矿处置

    挖矿的类型 主动挖矿:用户在个人电脑或服务器使用挖矿程序进行CPU,GPU计算,获取虚拟货币. 被动挖矿:挖矿病毒通过系统漏洞,恶意程序,弱口令等方式入侵服务器,设备感染挖矿病毒后会开始挖掘虚拟货币. ...

  3. 如何在路由绑定中使用 IParsable

    IParsable 是 .Net 7 中新增的接口,它可以将字符串转换为对应的实体.在 Controller 的 Route 绑定中可以使用 IParsable 来绑定复杂的实体. 实验背景 假设有一 ...

  4. CH32V307以太网(芯片内部10M)-针对新固件的Lib库

    沁恒的CH32V307网络库在前段时间做了一个更新,相对于以前的Lib,主要的功能没有什么特别大的变化,但是底层的一些操作仔细看的话,还是不少的区别的. 首先,官方提供的例程,工程结构以及头文件优一些 ...

  5. python之路48 django 视图层、模板层

    视图层之必会三板斧 用来处理请求的视图函数都必须返回HttpResponse对象 完全正确 class HttpResponse: pass return HttpResponse() def ren ...

  6. (转载)零日攻击(Zero-Day Attacks)

    零日攻击是网络安全行业中十分常见的攻击方式之一,其具有很大的突发性与破坏性,那么零日攻击是什么?如何防范零日攻击?以下是详细的内容介绍. 零日攻击是什么? 零日漏洞或零时差漏洞通常是指还没有补丁的安全 ...

  7. Stream流的特点_只能用一次-Stream流中的常用方法_map

    Stream流的特点_只能用一次 Stream流属于管道流,只能被消费(使用)一次 第一个stream流调用完毕方法,数据就会流转到下一个stream上 而这时第一个stream流已经使用完毕,就会关 ...

  8. 【开源】libserial_parse_text:命令行解析的基础库

    借助五一假期,写了一个命令行解析的基础库,一般可用于串口命令解析.TCP命令解析等等. 具有以下几种特点: 不涉及到具体硬件, 纯软件协议,与具体硬件分离. 支持不定长命令行,逐个字符解码,可以支持不 ...

  9. GPIO-CH32x系列芯片GPIO使用注意事项

    一.特殊IO使用注意事项 芯片型号:CH32F203C8T6.CH32V203C8T6 特殊IO:PC13.PC14.PC15 注意事项说明: 1.PC13~PC15的IO功能受限,速度必须限制在2M ...

  10. 我们从 CircleCI 安全事件获得的3个经验教训

    CircleCI 作为业内最受欢迎的 CI/CD 平台提供商之一,有超过20万个 DevOps 团队使用其平台.该公司在今年1月在其官网报告了一起安全事件引起客户恐慌.在此事件中,有身份不明的恶意攻击 ...