前言
在MySQL8.0之前的版本中,由于架构的原因,mysql在server层使用统一的frm文件来存储表元数据信息,这个信息能够被不同的存储引擎识别。而实际上innodb本身也存储有元数据信息。这给ddl带来了一定的挑战,因为这种架构无法做到ddl的原子化,我们在线上经常能够看到数据目录下遗留的临时文件,或者类似server层和innodb层列个数不一致之类的错误。甚至某些ddl可能还遗留元数据在innodb内,而丢失了frm,导致无法重建表……(我们为了解决这个问题,实现了一个叫drop table force的功能,去强制做清理….)
(以下所有的讨论都假定使用InnoDB存储引擎)
到了8.0版本,我们知道所有的元数据已经统一用InnoDB来进行管理,这就给实现原子ddl带来了可能,几乎所有的对innodb表,存储过程,触发器,视图或者UDF的操作,都能做到原子化:
- 元数据修改,binlog以及innodb的操作都放在一个事务中
- 增加了一个内部隐藏的系统表
mysql.innodb_ddl_log,ddl操作被记录到这个表中,注意对该表的操作产生的redo会fsync到磁盘上,而不会考虑innodb_flush_log_at_trx_commit的配置。当崩溃重启时,会根据事务是否提交来决定通过这张表的记录去回滚或者执行ddl操作 - 增加了一个post-ddl的阶段,这也是ddl的最后一个阶段,会去:1. 真正的物理删除或重命名文件; 2. 删除innodb_ddl_log中的记录项; 3.对于一些ddl操作还会去更新其动态元数据信息(存储在
mysql.innodb_dynamic_metadata,例如corrupt flag, auto_inc值等) - 一个正常运行的ddl结束后,其ddl log也应该被清理,如果这中间崩溃了,重启时会去尝试重放:1.如果已经走到最后一个ddl阶段的(commit之后),就replay ddl log,把ddl完成掉;2. 如果处于某个中间态,则回滚ddl
由于引入了atomic ddl, 有些ddl操作的行为也发生了变化:
- DROP TABLE: 在之前的版本中,一个drop table语句中如果要删多个表,比如t1,t2, t2不存在时,t1会被删除。但在8.0中,t1和t2都不会被删除,而是抛出错误。因此要注意5.7->8.0的复制问题 (DROP VIEW, CREATE USER也有类似的问题)
- DROP DATABASE: 修改元数据和ddl_log先提交事务,而真正的物理删除数据文件放在最后,因此如果在删除文件时崩溃,重启时会根据ddl_log继续执行drop database
MySQL8.0 新增 innodb_print_ddl_logs,打开后我们可以从错误日志看到对应的ddl log。
mysql> SET GLOBAL innodb_print_ddl_logs = 1;
Query OK, 0 rows affected (0.00 sec)
mysql> SET GLOBAL log_error_verbosity = 3;
Query OK, 0 rows affected (0.00 sec)
CREATE DATABASE
create database testdbddl;
创建数据库语句没有写log_ddl,可能觉得这不是高频操作,如果创建database的过程中失败了,重启后可能需要手动删除目录。
CREATE TABLE
mysql> use testdbddl;
Database changed
mysql> CREATE TABLE t1 (a INT PRIMARY KEY, b INT);
Query OK, 0 rows affected (0.04 sec)
2024-07-02T07:14:58.759841Z 55 [Note] [MY-012473] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=48, thread_id=55, space_id=11, old_file_path=./testdbddl/t1.ibd]
2024-07-02T07:14:58.759942Z 55 [Note] [MY-012478] [InnoDB] DDL log delete : 48
2024-07-02T07:14:58.771476Z 55 [Note] [MY-012477] [InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id=49, thread_id=55, table_id=1068, new_file_path=testdbddl/t1]
2024-07-02T07:14:58.771521Z 55 [Note] [MY-012478] [InnoDB] DDL log delete : 49
2024-07-02T07:14:58.775105Z 55 [Note] [MY-012472] [InnoDB] DDL log insert : [DDL record: FREE, id=50, thread_id=55, space_id=11, index_id=162, page_no=4]
2024-07-02T07:14:58.775147Z 55 [Note] [MY-012478] [InnoDB] DDL log delete : 50
2024-07-02T07:14:58.793439Z 55 [Note] [MY-012485] [InnoDB] DDL log post ddl : begin for thread id : 55
2024-07-02T07:14:58.793479Z 55 [Note] [MY-012486] [InnoDB] DDL log post ddl : end for thread id : 55
所有插入的记录都是单独的事务,已经进行操作的反向操作。对于创建table space来说,它的反向操作就是DELETE_SPACE_LOG。
所得到的ddl log还含有 DDL log delete 操作,它其实也是记录,用来删除ddl log。如果最后DDL事务成功提交,delete操作最后就会起到作用,DDL log被清空,但如果DDL事务中途失败了,delete操作会回滚,insert的记录得到保留,这些ddl log会清理遗留的垃圾文件。
对建表逻辑来说,它包含三类:DELETE SPACE、REMOVE CACHE和FREE。因为建表时对其进行了分区,所以上述三条命令是呈分区倍数出现的。首先建立了第一个分区表,将其写入dictionary cache,再建立索引,然后再对后续的分区表进行同样的操作。ddl log记录的便是这些操作的逆向逻辑:删除数据文件,释放内存中的数据字典信息,删除索引btree。当事务最终提交,ddl log会将这些记录删除。在这里DDL log起到的就是Undo。
加列
mysql> ALTER TABLE t1 ADD COLUMN c INT;
Query OK, 0 rows affected (0.06 sec)
Records: 0 Duplicates: 0 Warnings: 0
2024-07-02T07:19:58.786581Z 55 [Note] [MY-012485] [InnoDB] DDL log post ddl : begin for thread id : 55
2024-07-02T07:19:58.786625Z 55 [Note] [MY-012486] [InnoDB] DDL log post ddl : end for thread id : 55
注意这里执行的是Instant ddl, 这是8.0.13新支持的特性,加列操作可以只修改元数据,因此从ddl log中无需记录数据
删列
mysql> ALTER TABLE t1 DROP COLUMN c;
Query OK, 0 rows affected (0.06 sec)
Records: 0 Duplicates: 0 Warnings: 0
2024-07-02T07:26:05.784364Z 55 [Note] [MY-012473] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=51, thread_id=55, space_id=12, old_file_path=./testdbddl/#sql-ib1068-1754000627.ibd]
2024-07-02T07:26:05.784423Z 55 [Note] [MY-012478] [InnoDB] DDL log delete : 51
2024-07-02T07:26:05.800281Z 55 [Note] [MY-012477] [InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id=52, thread_id=55, table_id=1069, new_file_path=testdbddl/#sql-ib1068-1754000627]
2024-07-02T07:26:05.800326Z 55 [Note] [MY-012478] [InnoDB] DDL log delete : 52
2024-07-02T07:26:05.803262Z 55 [Note] [MY-012472] [InnoDB] DDL log insert : [DDL record: FREE, id=53, thread_id=55, space_id=12, index_id=163, page_no=4]
2024-07-02T07:26:05.803300Z 55 [Note] [MY-012478] [InnoDB] DDL log delete : 53
2024-07-02T07:26:05.803846Z 55 [Note] [MY-012475] [InnoDB] DDL log insert : [DDL record: DROP, id=54, thread_id=55, table_id=1068]
2024-07-02T07:26:05.806933Z 55 [Note] [MY-012474] [InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=55, thread_id=55, space_id=11, old_file_path=./testdbddl/#sql-ib1069-1754000628.ibd, new_file_path=./testdbddl/t1.ibd]
2024-07-02T07:26:05.806972Z 55 [Note] [MY-012478] [InnoDB] DDL log delete : 55
2024-07-02T07:26:05.809076Z 55 [Note] [MY-012476] [InnoDB] DDL log insert : [DDL record: RENAME TABLE, id=56, thread_id=55, table_id=1068, old_file_path=testdbddl/#sql-ib1069-1754000628, new_file_path=testdbddl/t1]
2024-07-02T07:26:05.809115Z 55 [Note] [MY-012478] [InnoDB] DDL log delete : 56
2024-07-02T07:26:05.811078Z 55 [Note] [MY-012474] [InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=57, thread_id=55, space_id=12, old_file_path=./testdbddl/t1.ibd, new_file_path=./testdbddl/#sql-ib1068-1754000627.ibd]
2024-07-02T07:26:05.811113Z 55 [Note] [MY-012478] [InnoDB] DDL log delete : 57
2024-07-02T07:26:05.813277Z 55 [Note] [MY-012476] [InnoDB] DDL log insert : [DDL record: RENAME TABLE, id=58, thread_id=55, table_id=1069, old_file_path=testdbddl/t1, new_file_path=testdbddl/#sql-ib1068-1754000627]
2024-07-02T07:26:05.813311Z 55 [Note] [MY-012478] [InnoDB] DDL log delete : 58
2024-07-02T07:26:05.820229Z 55 [Note] [MY-012475] [InnoDB] DDL log insert : [DDL record: DROP, id=59, thread_id=55, table_id=1068]
2024-07-02T07:26:05.820267Z 55 [Note] [MY-012473] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=60, thread_id=55, space_id=11, old_file_path=./testdbddl/#sql-ib1069-1754000628.ibd]
2024-07-02T07:26:05.836630Z 55 [Note] [MY-012485] [InnoDB] DDL log post ddl : begin for thread id : 55
2024-07-02T07:26:05.836673Z 55 [Note] [MY-012479] [InnoDB] DDL log replay : [DDL record: DELETE SPACE, id=60, thread_id=55, space_id=11, old_file_path=./testdbddl/#sql-ib1069-1754000628.ibd]
2024-07-02T07:26:05.839041Z 55 [Note] [MY-012479] [InnoDB] DDL log replay : [DDL record: DROP, id=59, thread_id=55, table_id=1068]
2024-07-02T07:26:05.839070Z 55 [Note] [MY-012479] [InnoDB] DDL log replay : [DDL record: DROP, id=54, thread_id=55, table_id=1068]
2024-07-02T07:26:05.842012Z 55 [Note] [MY-012486] [InnoDB] DDL log post ddl : end for thread id : 55
这是个典型的三阶段ddl的过程:分为prepare, perform 以及commit三个阶段:
Prepare: 这个阶段会修改元数据,创建临时ibd文件#sql-ib1068-1754000627.ibd, 如果发生异常崩溃,我们需要能把这个临时文件删除掉, 因此和create table类似,也为这个idb写了三条日志:delete space, remove cache,以及free btree
Perform: 执行操作,将数据拷贝到上述ibd文件中,(同时处理online dmllog), 这部分不涉及log ddl操作
Commit: 更新数据词典信息并提交事务, 这里会写几条日志:
DROP : table_id=1068
RENAME SPACE: #sql-ib1069-1754000628.ibd文件被rename成t1.ibd
RENAME TABLE: #sql-ib1069-1754000628被rename成t1
RENAME SPACE: t1.ibd被rename成#sql-ib1068-1754000627.ibd
RENAME TABLE: t1表被rename成#sql-ib1068-1754000627
DROP TABLE: table_id=1068
DELETE SPACE: 删除#sql-ib1069-1754000628.ibd
实际上这一步写的ddl log描述了commit阶段操作的逆向过程:将t1.ibd rename成#sql-ib1069-1754000628, 并将#sql-ib1068-1754000627 rename成t1表,最后删除旧表。其中删除旧表的操作这里不执行,而是到post-ddl阶段执行
Post-ddl: 在事务提交后,执行最后的操作:replay ddl log, 删除旧文件,清理mysql.innodb_dynamic_metadata中相关信息
DELETE SPACE: #sql-ib1069-1754000628.ibd
DROP: table_id=1068
DROP: table_id=1068
alter table有很多种,这里是最复杂的重建表的逻辑。这种情况下DDL log既是redo,也是undo。
执行阶段首先是建立了两个分区表,一开始走了create table的逻辑,然后记录下要删除的原来的表(此时只是记录,留作post-ddl阶段再执行),之后是一系列重命名操作,把旧的表空间和旧的表重命名为新的,这里记录的也是实际执行过程的逆操作。之前的表空间和表名(以A代称)先被重命名成另外一个中间名(以C代称),然后把最初创建的新的表空间和表名(代称为B)重命名为正确的表名,也就是最开始的A名。而被代替的旧表和旧表空间C,先记录下来ddl log,等到post-ddl阶段再做删除。
加列
mysql> ALTER TABLE t1 ADD KEY(b);
2024-07-03T01:14:21.568189Z 60 [Note] [MY-012472] [InnoDB] DDL log insert : [DDL record: FREE, id=61, thread_id=60, space_id=12, index_id=164, page_no=5]
2024-07-03T01:14:21.568254Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 61
2024-07-03T01:14:21.590682Z 60 [Note] [MY-012485] [InnoDB] DDL log post ddl : begin for thread id : 60
2024-07-03T01:14:21.590727Z 60 [Note] [MY-012486] [InnoDB] DDL log post ddl : end for thread id : 60
创建索引采用inplace创建的方式,没有临时文件,但如果异常发生的话,依然需要在发生异常时清理临时索引, 因此增加了一条FREE log,用于异常发生时能够删除临时索引.
TRUNCATE TABLE
mysql> TRUNCATE TABLE t1;
2024-07-03T01:21:37.824789Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 62
2024-07-03T01:21:37.834956Z 60 [Note] [MY-012475] [InnoDB] DDL log insert : [DDL record: DROP, id=63, thread_id=60, table_id=1069]
2024-07-03T01:21:37.834996Z 60 [Note] [MY-012473] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=64, thread_id=60, space_id=12, old_file_path=./testdbddl/#sql-ib1069-1754000629.ibd]
2024-07-03T01:21:37.839661Z 60 [Note] [MY-012473] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=65, thread_id=60, space_id=13, old_file_path=./testdbddl/t1.ibd]
2024-07-03T01:21:37.839709Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 65
2024-07-03T01:21:37.859061Z 60 [Note] [MY-012477] [InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id=66, thread_id=60, table_id=1070, new_file_path=testdbddl/t1]
2024-07-03T01:21:37.859110Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 66
2024-07-03T01:21:37.865007Z 60 [Note] [MY-012472] [InnoDB] DDL log insert : [DDL record: FREE, id=67, thread_id=60, space_id=13, index_id=165, page_no=4]
2024-07-03T01:21:37.865041Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 67
2024-07-03T01:21:37.877203Z 60 [Note] [MY-012472] [InnoDB] DDL log insert : [DDL record: FREE, id=68, thread_id=60, space_id=13, index_id=166, page_no=5]
2024-07-03T01:21:37.877241Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 68
2024-07-03T01:21:37.921780Z 60 [Note] [MY-012485] [InnoDB] DDL log post ddl : begin for thread id : 60
2024-07-03T01:21:37.921835Z 60 [Note] [MY-012479] [InnoDB] DDL log replay : [DDL record: DELETE SPACE, id=64, thread_id=60, space_id=12, old_file_path=./testdbddl/#sql-ib1069-1754000629.ibd]
2024-07-03T01:21:37.926778Z 60 [Note] [MY-012479] [InnoDB] DDL log replay : [DDL record: DROP, id=63, thread_id=60, table_id=1069]
2024-07-03T01:21:37.934573Z 60 [Note] [MY-012486] [InnoDB] DDL log post ddl : end for thread id : 60
Truncate table是个比较有意思的话题,在早期5.6及之前的版本中, 是通过删除旧表创建新表的方式来进行的,5.7之后为了保证原子性,改成了原地truncate文件,同时增加了一个truncate log文件,如果在truncate过程中崩溃,可以通过这个文件在崩溃恢复时重新truncate。到了8.0版本,又恢复成了删除旧表,创建新表的方式,与之前不同的是,8.0版本在崩溃时可以回滚到旧数据,而不是再次执行。以上述为例,主要包括几个步骤:
将表t1.ibd rename成#sql-ib1069-1754000629.ibd
创建新文件t1.ibd
post-ddl: 将老文件#sql-ib1069-1754000629.ibd删除
RENAME TABLE
RENAME TABLE t1 TO t2;
2024-07-03T01:21:37.824789Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 62
2024-07-03T01:21:37.834956Z 60 [Note] [MY-012475] [InnoDB] DDL log insert : [DDL record: DROP, id=63, thread_id=60, table_id=1069]
2024-07-03T01:21:37.834996Z 60 [Note] [MY-012473] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=64, thread_id=60, space_id=12, old_file_path=./testdbddl/#sql-ib1069-1754000629.ibd]
2024-07-03T01:21:37.839661Z 60 [Note] [MY-012473] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=65, thread_id=60, space_id=13, old_file_path=./testdbddl/t1.ibd]
2024-07-03T01:21:37.839709Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 65
2024-07-03T01:21:37.859061Z 60 [Note] [MY-012477] [InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id=66, thread_id=60, table_id=1070, new_file_path=testdbddl/t1]
2024-07-03T01:21:37.859110Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 66
2024-07-03T01:21:37.865007Z 60 [Note] [MY-012472] [InnoDB] DDL log insert : [DDL record: FREE, id=67, thread_id=60, space_id=13, index_id=165, page_no=4]
2024-07-03T01:21:37.865041Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 67
2024-07-03T01:21:37.877203Z 60 [Note] [MY-012472] [InnoDB] DDL log insert : [DDL record: FREE, id=68, thread_id=60, space_id=13, index_id=166, page_no=5]
2024-07-03T01:21:37.877241Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 68
2024-07-03T01:21:37.921780Z 60 [Note] [MY-012485] [InnoDB] DDL log post ddl : begin for thread id : 60
2024-07-03T01:21:37.921835Z 60 [Note] [MY-012479] [InnoDB] DDL log replay : [DDL record: DELETE SPACE, id=64, thread_id=60, space_id=12, old_file_path=./testdbddl/#sql-ib1069-1754000629.ibd]
2024-07-03T01:21:37.926778Z 60 [Note] [MY-012479] [InnoDB] DDL log replay : [DDL record: DROP, id=63, thread_id=60, table_id=1069]
2024-07-03T01:21:37.934573Z 60 [Note] [MY-012486] [InnoDB] DDL log post ddl : end for thread id : 60
2024-07-03T01:27:57.876968Z 60 [Note] [MY-012474] [InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=69, thread_id=60, space_id=13, old_file_path=./testdbddl/t2.ibd, new_file_path=./testdbddl/t1.ibd]
2024-07-03T01:27:57.877071Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 69
2024-07-03T01:27:57.880005Z 60 [Note] [MY-012476] [InnoDB] DDL log insert : [DDL record: RENAME TABLE, id=70, thread_id=60, table_id=1070, old_file_path=testdbddl/t2, new_file_path=testdbddl/t1]
2024-07-03T01:27:57.880048Z 60 [Note] [MY-012478] [InnoDB] DDL log delete : 70
2024-07-03T01:27:57.895132Z 60 [Note] [MY-012485] [InnoDB] DDL log post ddl : begin for thread id : 60
2024-07-03T01:27:57.895172Z 60 [Note] [MY-012486] [InnoDB] DDL log post ddl : end for thread id : 60
这个就比较简单了,只需要记录rename space 和rename table的逆操作即可. post-ddl不需要做实际的操作
DROP TABLE
DROP TABLE t2;
2024-07-03T01:29:34.316324Z 60 [Note] [MY-012475] [InnoDB] DDL log insert : [DDL record: DROP, id=71, thread_id=60, table_id=1070]
2024-07-03T01:29:34.316373Z 60 [Note] [MY-012473] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=72, thread_id=60, space_id=13, old_file_path=./testdbddl/t2.ibd]
2024-07-03T01:29:34.333614Z 60 [Note] [MY-012485] [InnoDB] DDL log post ddl : begin for thread id : 60
2024-07-03T01:29:34.333665Z 60 [Note] [MY-012479] [InnoDB] DDL log replay : [DDL record: DELETE SPACE, id=72, thread_id=60, space_id=13, old_file_path=./testdbddl/t2.ibd]
2024-07-03T01:29:34.339299Z 60 [Note] [MY-012479] [InnoDB] DDL log replay : [DDL record: DROP, id=71, thread_id=60, table_id=1070]
2024-07-03T01:29:34.343238Z 60 [Note] [MY-012486] [InnoDB] DDL log post ddl : end for thread id : 60
先在ddl log中记录下需要删除的数据,再提交后,再最后post-ddl阶段执行真正的删除表对象和文件操作
记录类型
根据不同的操作类型,可以分为如下几类:
FREE_TREE_LOG
目的是释放索引btree,入口函数log_DDL::write_free_tree_log,在创建索引和删除表时会调用到
对于drop table中涉及的删索引操作,log ddl的插入操作放到父事务中,一起要么提交要么回滚
对于创建索引的case, log ddl就需要单独提交,父事务将记录标记删除,这样后面如果ddl回滚了,也能将残留的index删掉。
DELETE_SPACE_LOG
入口函数:Log_DDL::write_delete_space_log
用于记录删除tablespace操作,同样分为两种情况:
drop table/tablespace, 写入的记录随父事务一起提交,并在post-ddl阶段replay
创建tablespace, 写入的记录单独提交,并被父事务标记删除,如果父事务回滚,就通过replay删除参与的tablespace
RENAME_SPACE_LOG
入口函数:Log_DDL::write_rename_space_log
用于记录rename操作,例如如果我们把表t1 rename成t2,在其中就记录了逆向操作t2 rename to t1.
在函数Fil_shard::space_rename()中,总是先写ddl log, 再做真正的rename操作. 写日志的过程同样是独立事务提交,父事务做未提交的删除操作
DROP_LOG
入口函数: Log_DDL::write_drop_log
用于记录删除表对象操作,这里不涉及文件层操作,写ddl log在父事务中执行
RENAME_TABLE_LOG
入口函数: Log_DDL::write_rename_table_log
用于记录rename table对象的逆操作,和rename space类似,也是独立事务提交ddl log, 父事务标记删除
REMOVE_CACHE_LOG
入口函数: Log_DDL::write_remove_cache_log
用于处理内存表对象的清理,独立事务提交,父事务标记删除
ALTER_ENCRYPT_TABLESPACE_LOG
入口函数: Log_DDL::write_alter_encrypt_space_log
用于记录对tablespace加密属性的修改,独立事务提交. 在写完ddl log后修改tablespace page0 中的加密标记
综上,在ddl的过程中可能会提交多次事务,大概分为三类:
独立事务写ddl log并提交,父事务标记删除, 如果父事务提交了,ddl log也被顺便删除了,如果父事务回滚了,那就要根据ddl log做逆操作来回滚ddl
独立事务写ddl log 并提交, (目前只有ALTER_ENCRYPT_TABLESPACE_LOG)
使用父事务写ddl log,在ddl结束时提交。需要在post-ddl阶段处理
## post_ddl
如上所述,有些ddl log是随着父事务一起提交的,有些则在post-ddl阶段再执行, post_ddl发生在父事提交或回滚之后: 若事务回滚,根据ddl log做逆操作,若事务提交,在post-ddl阶段做最后真正不可逆操作(例如删除文件)
入口函数: Log_DDL::post_ddl -->Log_DDL::replay_by_thread_id
根据执行ddl的线程thread id通过innodb_log_ddl表上的二级索引,找到log id,再到聚集索引上找到其对应的记录项,然后再replay这些操作,完成ddl后,清理对应记录
## 崩溃恢复
在崩溃恢复结束后,会调用ha_post_recover接口函数,进而调用innodb内的函数Log_DDL::recover(), 同样的replay其中的记录,并在结束后删除记录。但ALTER_ENCRYPT_TABLESPACE_LOG类型并不是在这一步删除,而是加入到一个数组ts_encrypt_ddl_records中,在之后调用resume_alter_encrypt_tablespace来恢复操作,




