电商网站开发发展和前景,wordpress中文开发文档,做网站 需要审核么,成都 企业 网站制作前言
在MySQL8.0之前的版本中#xff0c;由于架构的原因#xff0c;mysql在server层使用统一的frm文件来存储表元数据信息#xff0c;这个信息能够被不同的存储引擎识别。而实际上innodb本身也存储有元数据信息。这给ddl带来了一定的挑战#xff0c;因为这种架构无法做到d…前言
在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_logddl操作被记录到这个表中注意对该表的操作产生的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测试:
MySQL很贴心的加了一个选项innodb_print_ddl_logs打开后我们可以从错误日志看到对应的ddl log下面我们通过这个来看下一些典型ddl的过程
root(none) 11:12:19SET GLOBAL innodb_print_ddl_logs 1;
Query OK, 0 rows affected (0.00 sec)root(none) 11:12:22SET GLOBAL log_error_verbosity 3;
Query OK, 0 rows affected (0.00 sec)
CREATE DATABASE
mysql CREATE DATABASE test;
Query OK, 1 row affected (0.02 sec)创建数据库语句没有写log_ddl可能觉得这不是高频操作如果创建database的过程中失败了重启后可能需要手动删除目录。
CREATE TABLE
mysql USE test;
Database changed
mysql CREATE TABLE t1 (a INT PRIMARY KEY, b INT);
Query OK, 0 rows affected (0.06 sec)[InnoDB] DDL log insert : [DDL record: DELETE SPACE, id428, thread_id7, space_id76, old_file_path./test/t1.ibd]
[InnoDB] DDL log delete : by id 428
[InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id429, thread_id7, table_id1102, new_file_pathtest/t1]
[InnoDB] DDL log delete : by id 429
[InnoDB] DDL log insert : [DDL record: FREE, id430, thread_id7, space_id76, index_id190, page_no4]
[InnoDB] DDL log delete : by id 430
[InnoDB] DDL log post ddl : begin for thread id : 7
InnoDB] DDL log post ddl : end for thread id : 7
从日志来看有三类操作实际上描述了如果操作失败需要进行的三项逆向操作删除数据文件释放内存中的数据词典信息删除索引btree。在创建表之前这些数据被写入到ddl_log中在创建完表并commit后再从ddl log中删除这些记录。 另外上述日志中还有DDL log delete日志其实在每次写入ddl log时是单独事务提交的但在提交之后会使用当前事务执行一条delete操作直到操作结束了才会提交。
加列(instant)
mysql ALTER TABLE t1 ADD COLUMN c INT;
Query OK, 0 rows affected (0.08 sec)
Records: 0 Duplicates: 0 Warnings: 0[InnoDB] DDL log post ddl : begin for thread id : 7
[InnoDB] DDL log post ddl : end for thread id : 7注意这里执行的是Instant ddl, 这是8.0.13新支持的特性加列操作可以只修改元数据因此从ddl log中无需记录数据
删列
mysql ALTER TABLE t1 DROP COLUMN c;
Query OK, 0 rows affected (2.77 sec)
Records: 0 Duplicates: 0 Warnings: 0[InnoDB] DDL log insert : [DDL record: DELETE SPACE, id487, thread_id7, space_id83, old_file_path./test/#sql-ib1108-1917598001.ibd]
[InnoDB] DDL log delete : by id 487
[InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id488, thread_id7, table_id1109, new_file_pathtest/#sql-ib1108-1917598001]
[InnoDB] DDL log delete : by id 488
[InnoDB] DDL log insert : [DDL record: FREE, id489, thread_id7, space_id83, index_id200, page_no4]
[InnoDB] DDL log delete : by id 489[InnoDB] DDL log insert : [DDL record: DROP, id490, thread_id7, table_id1108]
[InnoDB] DDL log insert : [DDL record: RENAME SPACE, id491, thread_id7, space_id82, old_file_path./test/#sql-ib1109-1917598002.ibd, new_file_path./test/t1.ibd]
[InnoDB] DDL log delete : by id 491
[InnoDB] DDL log insert : [DDL record: RENAME TABLE, id492, thread_id7, table_id1108, old_file_pathtest/#sql-ib1109-1917598002, new_file_pathtest/t1]
[InnoDB] DDL log delete : by id 492
[InnoDB] DDL log insert : [DDL record: RENAME SPACE, id493, thread_id7, space_id83, old_file_path./test/t1.ibd, new_file_path./test/#sql-ib1108-1917598001.ibd]
[InnoDB] DDL log delete : by id 493
[InnoDB] DDL log insert : [DDL record: RENAME TABLE, id494, thread_id7, table_id1109, old_file_pathtest/t1, new_file_pathtest/#sql-ib1108-1917598001]
[InnoDB] DDL log delete : by id 494
[InnoDB] DDL log insert : [DDL record: DROP, id495, thread_id7, table_id1108]
[InnoDB] DDL log insert : [DDL record: DELETE SPACE, id496, thread_id7, space_id82, old_file_path./test/#sql-ib1109-1917598002.ibd][InnoDB] DDL log post ddl : begin for thread id : 7
[InnoDB] DDL log replay : [DDL record: DELETE SPACE, id496, thread_id7, space_id82, old_file_path./test/#sql-ib1109-1917598002.ibd]
[InnoDB] DDL log replay : [DDL record: DROP, id495, thread_id7, table_id1108]
[InnoDB] DDL log replay : [DDL record: DROP, id490, thread_id7, table_id1108]
[InnoDB] DDL log post ddl : end for thread id : 7
这是个典型的三阶段ddl的过程分为prepare, perform 以及commit三个阶段:
Prepare: 这个阶段会修改元数据创建临时ibd文件#sql-ib1108-1917598001.ibd, 如果发生异常崩溃我们需要能把这个临时文件删除掉 因此和create table类似也为这个idb写了三条日志delete space, remove cache,以及free btreePerform: 执行操作将数据拷贝到上述ibd文件中同时处理online dmllog, 这部分不涉及log ddl操作 Commit: 更新数据词典信息并提交事务, 这里会写几条日志 DROP : table_id1108RENAME SPACE: #sql-ib1109-1917598002.ibd文件被rename成t1.ibdRENAME TABLE: #sql-ib1109-1917598002被rename成t1RENAME SPACE: t1.ibd 被rename成#sql-ib1108-1917598001.ibdRENAME TABLE: t1表被rename成#sql-ib1108-1917598001DROP TABLE: table_id1108DELETE SPACE: 删除#sql-ib1109-1917598002.ibd
实际上这一步写的ddl log描述了commit阶段操作的逆向过程将t1.ibd rename成#sql-ib1109-1917598002, 并将sql-ib1108-1917598001 rename成t1表最后删除旧表。其中删除旧表的操作这里不执行而是到post-ddl阶段执行 Post-ddl: 在事务提交后执行最后的操作replay ddl log, 删除旧文件清理mysql.innodb_dynamic_metadata中相关信息 DELETE SPACE: #sql-ib1109-1917598002.ibdDROP: table_id1108DROP: table_id1108
加索引
mysql ALTER TABLE t1 ADD KEY(b);
Query OK, 0 rows affected (0.14 sec)
Records: 0 Duplicates: 0 Warnings: 0[InnoDB] DDL log insert : [DDL record: FREE, id431, thread_id7, space_id76, index_id191, page_no5]
[InnoDB] DDL log delete : by id 431[InnoDB] DDL log post ddl : begin for thread id : 7
[InnoDB] DDL log post ddl : end for thread id : 7
创建索引采用inplace创建的方式没有临时文件但如果异常发生的话依然需要在发生异常时清理临时索引, 因此增加了一条FREE log用于异常发生时能够删除临时索引.
TRUNCATE TABLE
mysql TRUNCATE TABLE t1;
Query OK, 0 rows affected (0.13 sec)[InnoDB] DDL log insert : [DDL record: RENAME SPACE, id439, thread_id7, space_id77, old_file_path./test/#sql-ib1103-1917597994.ibd, new_file_path./test/t1.ibd]
[InnoDB] DDL log delete : by id 439
[InnoDB] DDL log insert : [DDL record: DROP, id440, thread_id7, table_id1103]
[InnoDB] DDL log insert : [DDL record: DELETE SPACE, id441, thread_id7, space_id77, old_file_path./test/#sql-ib1103-1917597994.ibd]
[InnoDB] DDL log insert : [DDL record: DELETE SPACE, id442, thread_id7, space_id78, old_file_path./test/t1.ibd]
[InnoDB] DDL log delete : by id 442
[InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id443, thread_id7, table_id1104, new_file_pathtest/t1]
[InnoDB] DDL log delete : by id 443
[InnoDB] DDL log insert : [DDL record: FREE, id444, thread_id7, space_id78, index_id194, page_no4]
[InnoDB] DDL log delete : by id 444
[InnoDB] DDL log insert : [DDL record: FREE, id445, thread_id7, space_id78, index_id195, page_no5]
[InnoDB] DDL log delete : by id 445[InnoDB] DDL log post ddl : begin for thread id : 7
[InnoDB] DDL log replay : [DDL record: DELETE SPACE, id441, thread_id7, space_id77, old_file_path./test/#sql-ib1103-1917597994.ibd]
[InnoDB] DDL log replay : [DDL record: DROP, id440, thread_id7, table_id1103]
[InnoDB] DDL log post ddl : end for thread id : 7
Truncate table是个比较有意思的话题在早期5.6及之前的版本中, 是通过删除旧表创建新表的方式来进行的5.7之后为了保证原子性改成了原地truncate文件同时增加了一个truncate log文件如果在truncate过程中崩溃可以通过这个文件在崩溃恢复时重新truncate。到了8.0版本又恢复成了删除旧表创建新表的方式与之前不同的是8.0版本在崩溃时可以回滚到旧数据而不是再次执行。以上述为例主要包括几个步骤
将表t1.ibd rename成#sql-ib1103-1917597994.ibd创建新文件t1.ibdpost-ddl: 将老文件#sql-ib1103-1917597994.ibd删除
RENAME TABLE
mysql RENAME TABLE t1 TO t2;
Query OK, 0 rows affected (0.06 sec)DDL LOG:
[InnoDB] DDL log insert : [DDL record: RENAME SPACE, id450, thread_id7, space_id78, old_file_path./test/t2.ibd, new_file_path./test/t1.ibd]
[InnoDB] DDL log delete : by id 450
[InnoDB] DDL log insert : [DDL record: RENAME TABLE, id451, thread_id7, table_id1104, old_file_pathtest/t2, new_file_pathtest/t1]
[InnoDB] DDL log delete : by id 451[InnoDB] DDL log post ddl : begin for thread id : 7
[InnoDB] DDL log post ddl : end for thread id : 7
这个就比较简单了只需要记录rename space 和rename table的逆操作即可. post-ddl不需要做实际的操作
DROP TABLE
DROP TABLE t2
[InnoDB] DDL log insert : [DDL record: DROP, id595, thread_id7, table_id1119]
[InnoDB] DDL log insert : [DDL record: DELETE SPACE, id596, thread_id7, space_id93, old_file_path./test/t2.ibd][InnoDB] DDL log post ddl : begin for thread id : 7
[InnoDB] DDL log replay : [DDL record: DELETE SPACE, id596, thread_id7, space_id93, old_file_path./test/t2.ibd]
[InnoDB] DDL log replay : [DDL record: DROP, id595, thread_id7, table_id1119]
[InnoDB] DDL log post ddl : end for thread id : 7
先在ddl log中记录下需要删除的数据再提交后再最后post-ddl阶段执行真正的删除表对象和文件操作
代码实现:
主要实现代码集中在文件storage/innobase/log/log0ddl.cc中包含了向log_ddl表中插入记录以及replay的逻辑。
隐藏的innodb_log_ddl表结构如下 def-add_field(0, id, id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT);def-add_field(1, thread_id, thread_id BIGINT UNSIGNED NOT NULL);def-add_field(2, type, type INT UNSIGNED NOT NULL);def-add_field(3, space_id, space_id INT UNSIGNED);def-add_field(4, page_no, page_no INT UNSIGNED);def-add_field(5, index_id, index_id BIGINT UNSIGNED);def-add_field(6, table_id, table_id BIGINT UNSIGNED);def-add_field(7, old_file_path,old_file_path VARCHAR(512) COLLATE UTF8_BIN);def-add_field(8, new_file_path,new_file_path VARCHAR(512) COLLATE UTF8_BIN);def-add_index(0, index_pk, PRIMARY KEY(id));def-add_index(1, index_k_thread_id, KEY(thread_id));
记录类型
根据不同的操作类型可以分为如下几类
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删除参与的tablespaceRENAME_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来恢复操作 #阿里云开年Hi购季#幸运抽好礼 点此抽奖https://www.aliyun.com/acts/product-section-2019/yq-lottery?utm_contentg_1000042901
原文链接 本文为云栖社区原创内容未经允许不得转载。