背景

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 系统表的定义如下:

  1. 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):

  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):

  1. [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 操作

  1. // ======================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. 【转载】WebBrowser控件的常用方法、属性和事件

    1. 属性 属性 说明 Application 如果该对象有效,则返回掌管WebBrowser控件的应用程序实现的自动化对象(IDispatch).如果在宿主对象中自动化对象无效,这个程序将返回Web ...

  2. Hadoop生态元数据管理平台——Atlas2.3.0发布!

    大家好,我是独孤风. 今天我们来聊一下另一个元数据管理平台Apache Atlas.Atlas其实有一些年头了,是在2015年的时候就开源. 相对于Datahub来说,Atlas显得有一些" ...

  3. Kafka初学习

    Kafka初学习   摘要:在之前的消息队列学习中,我已经了解了消息队列的基本概念以及基本用法,同时也了解到了市面上的几款消息队列中间件,其中我了解到了卡夫卡这款消息队列中间件是一款最为快速的消息队列 ...

  4. test20230109考试总结-2023寒搜索专题

    前言 2023 年的第一篇考试总结-- 赛时得分情况: A B C D E F G \(\texttt{Total}\) \(\texttt{Rank}\) \(40\) \(80\) \(0\) \ ...

  5. 树形 dp 与树上问题

    NFLS 集训笔记 20220802 - 树形 dp 进阶与树上问题综合 \(\text{By DaiRuiChen007}\) I. 洛谷[P2585] - 三色二叉树 \(\text{Link}\ ...

  6. windows11 彻底修改c盘中文用户名

    windows11 彻底修改c盘用户名 由于一开始注册的时候没有注意使用了中文名导致后来再使用一些应用的时候出现问题浪费了大量的时间找不出原因(例如:安装cuda 的时候在使用nvcc编译.cu文件的 ...

  7. _Bool類型

    _Bool類型:布爾變量,其值只有1(真)和0(假).是C語言中的變量名,C語言中所有的非0數字都被視爲真. 給布爾變量取一個能表示真或假值的變量名是一種常見的做法. 1 /*boolean.c--使 ...

  8. py教学 之字符串处理·····

    访问字符串中的值 Python 不支持单字符类型,单字符在 Python 中也是作为一个字符串使用. Python 访问子字符串,可以使用方括号 [] 来截取字符串,字符串的截取的语法格式如下: 变量 ...

  9. JavaScript 检查(Linting)工具的比较

    一个好的检查(linting)工具可以确保一个项目遵循编码规范. 让我们来看看四种流行替代方案的特性和优缺点:JSLint , JSHint , JSCS 和 ESLint . JSLint JSLi ...

  10. 应用容器引擎-Docker

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows操作系统的机器上,也可以实现虚拟化.容器是完全使用沙箱 ...