前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MySQL 8.0 Atomic DDL

MySQL 8.0 Atomic DDL

作者头像
腾讯数据库技术
发布2023-01-30 11:40:28
1.1K0
发布2023-01-30 11:40:28
举报

背景

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

相关WorkLog见:

https://dev.mysql.com/worklog/task/?id=9525??

https://dev.mysql.com/worklog/task/?id=9536

在 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的原子性。

详见:InnoDB INFORMATION_SCHEMA System Tables

https://dev.mysql.com/doc/refman/5.7/en/innodb-information-schema-system-tables.html

例如,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

在 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,解决元数据操作原子性

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

代码语言:javascript
复制
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):

代码语言:javascript
复制
[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):

代码语言:javascript
复制
[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 操作

代码语言:javascript
复制
// ======================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 阶段处理。

参考:

  1. Atomic DDL 官方介绍: https://dev.mysql.com/doc/refman/8.0/en/atomic-ddl.html
  2. InnoDB_New_DD:?? Support crash-safe DDL?https://dev.mysql.com/worklog/task/?id=9536
  3. Bootstrap code for new DD: https://dev.mysql.com/worklog/task/?id=6394
  4. MySQL 8.0 Data Dictionary: Background and Motivation?https://dev.mysql.com/blog-archive/mysql-8-0-data-dictionary-background-and-motivation/
本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-01-19,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 腾讯数据库技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com