前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[ibd2sql] mysql做过online ddl(instant)的数据应该怎么解析?

[ibd2sql] mysql做过online ddl(instant)的数据应该怎么解析?

原创
作者头像
大大刺猬
发布于 2024-07-09 09:01:13
发布于 2024-07-09 09:01:13
26700
代码可运行
举报
文章被收录于专栏:大大刺猬大大刺猬
运行总次数:0
代码可运行

ibd2sql的诞生过程

  1. 最开始解析ibd文件的时候, 只是一个脚本, 方便了解ibd文件的结构的:MYSQL INNODB ibd文件详解 (1)-腾讯云开发者社区-腾讯云 (tencent.com)
  2. 既然都能解析结构了, 那就顺便提取下数据(ddl+dml):MYSQL INNODB ibd文件详解 (2) 提取DDL和DML-腾讯云开发者社区-腾讯云 (tencent.com) 这时候还只是3个脚本而已.
  3. 接着再更新下元数据信息:MYSQL INNODB ibd文件详解 (3) FIL_PAGE_SDI-腾讯云开发者社区-腾讯云 (tencent.com)
  4. v0.1 信息都解析得差不多了, 那就整一套完整的工具吧. 能解析基础的数据类型就行.(这时候只支持基础数据类型):使用ibd2sql解析ibd文件生成 DDL和DML-腾讯云开发者社区-腾讯云 (tencent.com)
  5. v0.2 然后支持了更多的数据类型
  6. v0.3 对于从5.7升级上来的库, sdi page不是固定的位置, 而是记录在第一页里面的: mysql 寻找SDI PAGE-腾讯云开发者社区-腾讯云 (tencent.com)
  7. v1.0 增加了debug功能, 主要是方便调试的:ibd2sql ibd2sql v1.0 发布 & ibd文件结构说明-腾讯云开发者社区-腾讯云 (tencent.com)
  8. v1.1 修复一些BUG v1.2支持空间坐标, 也算是完善了数据类型, v1.3 支持mysql 5.x v1.4 支持溢出页和子分区. 基本上算是全部支持了.
  9. v1.5 对做过ONLINE DDL的字段重新做了个解析,(之前遇到的相关BUG,都是临时修一下.), 也是本文的重点. 项目地址: https://github.com/ddcw/ibd2sql

简介

我们知道mysql在8.0.12引入了INSTANT(online ddl),可以快速的插入列.

在8.0.29 完善了INSTANT(使用row version flag代替instant flag), 使其支持drop列.

原理比较好理解, 就是只修改元数据信息. 读取数据的时候, 根据元数据信息判断改字段是否做过online ddl.

最开始只有个instant flag, 用来标记该行数据是否是在online ddl之后更新的数据(若为online ddl之后操作的, 则存在完整的数据, 若为online ddl之前操作的,则读取字段的默认值, nullable也不考虑该字段.)

该方式使用太有限了, 毕竟只支持快速add column, 如果要删除某一列, 还是得重建表. 于是在8.0.29又引入了row version, 用来标记该行数据的版本, 元数据里面也记录数据行版本. 如果行数据里面的row version比元数据里面的版本信息, 则行数据记录更完整(要考虑nullable和数据). 文字描述还是比较枯燥的, 我们来使用图和代码表示吧.

instant实现逻辑

先介绍几个概念:

对象

描述

INSTANT FLAG

标记是否存在instant 在record第1bit位置, 仅存在于8.0.12-8.0.28版本中

ROW VERSION FLAG

标记是否存在row version 在record第2bit位置, 存在于8.0.29及其以上版本

COLUMN COUNT

字段数量, 当存在INSTANT FLAG时存在, 使用1-2字节表示. 位于null bitmask和record header之间. 包含rowid,trx,rollptr.

ROW VERSION

数据行版本, 当存在ROW VERSION FLAG时存在, 使用1字节表示. 每次add/drop时+1, 最大64. 用来记录该行数据是否为最新的

VERSION_DROPPED

元数据中记录的该字段被删除后的版本

VERSION_ADDED

元数据中记录的该字段添加后的版本

代码逻辑参考

代码语言:python
代码运行次数:0
运行
AI代码解释
复制
# ROW VERSION/COLUMN_COUNT判断
if self.table.mysqld_version_id < 80029 and self.table.mysqld_version_id >=80012 and rheader.instant_flag:
  _COLUMN_COUNT = self._read_innodb_varsize()
elif if rheader.row_version_flag:
  ROW_VERSION = self._read_innodb_varsize()

# NULL BITMASK计算
for _phno,colno in self.table.column_ph:
 col = self.table.column[colno]
 if rheader.row_version_flag:
    if (ROW_VERSION >= col['version_added'] and (col['version_dropped'] == 0 or col['version_dropped'] > ROW_VERSION)) or (col['version_dropped'] > ROW_VERSION and ROW_VERSION >= col['version_added']):
       null_bitmask_count += 1 if col['is_nullable'] else 0
    else:
       null_bitmask_count += 1 if col['is_nullable'] else 0

# 读取索引(注意前缀索引问题)
                        if self.haveindex: #有索引的时候
                                for colno,prefix_key,order in self.table.index[self.idxno]['element_col']:
                                        col = self.table.column[colno]
                                        self.debug(f"\tREAD KEY COLNO:{colno} NAME:{col['name']}")

                                        #pk 不需要判断null
                                        if prefix_key == 0:
                                                _data[colno],_expage[colno] = self._read_field(col)
                                                col_count -= 1
                                                self.debug(f"\tREAD KEY NO:{colno} NAME:{col['name']} FINISH. VALUES: {_data[colno]}")
                                        else:
                                                _,__ = self._read_field(col)
                                                self.debug(f'前缀索引数据为: {_} . SKIP IT.' )

                        else:
                                self.debug("\tNO CLUSTER KEY.WILL READ 6 bytes(ROWID)",)
                                _row_id = self._read_uint(6) #ROW_ID
                                self.debug(f"\t ROW_ID:{_row_id}")
 
# 读取TRX&ROLLPTR
_row['trx'] = self._read_uint(6)
_row['rollptr'] = self._read_uint(7)

# 读取数据. 存在多种条件
      for _phno,colno in self.table.column_ph:
        if colno in _data: # KEY
          continue
        col = self.table.column[colno]
        self.debug(f"\tNAME: {col['name']}  VERSION_ADDED:{col['version_added']}  VERSION_DROPED:{col['version_dropped']} COL_INSTANT:{col['instant']} ROW VERSION:{ROW_VERSION}")
        if rheader.row_version_flag: # >=8.0.29 的online ddl
          if (ROW_VERSION >= col['version_added'] and (col['version_dropped'] == 0 or col['version_dropped'] > ROW_VERSION)) or (col['version_dropped'] > ROW_VERSION and ROW_VERSION >= col['version_added']):
            if col['is_nullable']:
              _nullable_count += 1
            if col['is_nullable'] and null_bitmask&(1<<_nullable_count):
              _data[colno],_expage[colno] = None,None
              self.debug(f"######## DDCW FLAG 1 ########")
            else:
              _data[colno],_expage[colno] = self._read_field(col)
              self.debug(f"######## DDCW FLAG 2 ########")
          elif ROW_VERSION < col['version_added']:
            _data[colno] = col['default'] if not col['instant_null'] else None
            _expage[colno] = None
            self.debug(f"######## DDCW FLAG 3 ########")
          #elif col['version_dropped'] <= ROW_VERSION:
          else:
            _data[colno],_expage[colno] = None,None
            self.debug(f"######## DDCW FLAG 4 ########")
        elif ( not rheader.instant_flag and col['instant']): # 8.0.12-28 的online ddl
          self.debug(f'mysql 8.0.12-28 版本的add column (instant) 读默认值 (实际版本:{self.table.mysqld_version_id}) ')
          _data[colno] = col['default'] if not col['instant_null'] else None
          _expage[colno] = None
          self.debug(f"######## DDCW FLAG 5 ########")
        elif not rheader.instant and col['version_dropped'] > 0: # 删除的字段就不读了(无instant情况下)
          _data[colno],_expage[colno] = None,None
          self.debug(f"######## DDCW FLAG 6 ########")
        #elif col['instant'] and rheader.instant_flag:
        else:
          #self.debug(f"NULLABLE {col['name']}:  is_nullable:{col['is_nullable']} null_bitmask:{null_bitmask} _nullable_count:{_nullable_count}")
          if col['is_nullable']:
            _nullable_count += 1
          if col['is_nullable'] and null_bitmask&(1<<_nullable_count):
            _data[colno],_expage[colno] = None,None
            self.debug(f"######## DDCW FLAG 7 ########")
          else:
            _data[colno],_expage[colno] = self._read_field(col)
            self.debug(f"######## DDCW FLAG 8 ########")

看起来读取数据的逻辑比较复杂, 主要是涉及到 ROW_VERSION,VERSION_DROPPED,VERSION_ADDED之间的判断. 即一个字段 可能是instant add的, 然后写入n行数据后, 又被instant drop了. 也就是那一列的数据可能得读元数据, 也可能是记录在data位置的. 要看row version, verion dropped/added之间的关系了.

逻辑如下(不考虑nullable)

较为复杂. 实际读取的时候,还得考虑nullable, 其实也就2种情况要考虑nullable.

  1. 非instant情况
  2. 行数据记录了instant之后的数据(即使已经被删除了)

演示

我们直接使用8.0.28和8.0.38来演示把. (5.7的不在本次演示范围内).

不放完整过程了, 太长了.(新增: 60个随机的online ddl)

8.0.28测试结果如下:

8.0.38如下

9.0.0的还会做vector的解析.

总结

当解析做过instant的字段时, 主要就是考虑row version和元数据版本的关系, 若row version版本更高, 基本上都不考虑, 因为行数据就是最高版本了.主要是考虑row version版本低的时候, 如果低于version_added, 就说明要读元数据里面的默认值了(毕竟做instant的时候,只修改了元数据信息). 如果低于version_dropped, 还是正常读, 只不过读出来的字段不要放到sql里面(毕竟已经在逻辑上被删除了).

instant的相关BUG基本上就算解决了.

ibd2sql下载地址: https://github.com/ddcw/ibd2sql

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
[ibd2sql] mysql数据恢复案例003 -- 有坏块的表怎么解析?
数据文件是5.7环境的, 现在ibd2sql不再需要转换到8.0环境即可解析. 于是我们使用如下命令解析:
大大刺猬
2025/04/22
1740
[ibd2sql] mysql数据恢复案例003 -- 有坏块的表怎么解析?
[MYSQL] show engine innodb status中的死锁 分析
很久以前(也才2年)写过一个解析innodb_status的脚本. 看起来像那么回事, 其实就是做了个翻译和总结.
大大刺猬
2024/08/26
7660
[MYSQL] show engine innodb status中的死锁 分析
[ibd2sql] ibd2sql v1.0 发布 & ibd文件结构说明
修复了一些之前的问题, 比如做过online ddl (instant)的表解析的时候就需要注意record header的第2bit 标记位.
大大刺猬
2024/01/09
1.1K0
[ibd2sql] ibd2sql v1.0 发布 & ibd文件结构说明
MySQL 灾难恢复利器:ibd2sql
ibd2sql 是一个使用纯 Python 3 编写的工具,用于离线解析 MySQL InnoDB 存储引擎的 IBD 文件,并将其转换为 SQL 语句。该工具无需任何第三方依赖包,使用 GPL-3.0 许可证发布。
DBA实战
2024/10/10
2260
MySQL 灾难恢复利器:ibd2sql
[ibd2sql] mysql frm 文件结构解析
准备给ibd2sql加个解析 mysql 5.7 的ibd文件功能. mysql 8.0的元数据信息是存储在ibd文件的sdi page里面的. 但是mysql 5.7 的表结构信息是存储在 frm 文件的, 所以就得解析下这个frm文件了. 本以为它是文本文件, 很遗憾, 还是二进制的....
大大刺猬
2024/04/15
5560
[ibd2sql] mysql frm 文件结构解析
MySQL 8.0.29 instant DDL 数据腐化问题分析
DDL 相对于数据库的 DML 之类的其他操作,相对来说是比较耗时、相对重型的操作; 因此对业务的影比较严重。MySQL 从5.6版本开始一直在持续改进其DDL性能:引入了 online DDL,inplace DDL,instant DDL 等实用性极强的功能, DDL 目前对业务的影响持续降低。
GreatSQL社区
2023/08/11
3810
MySQL 8.0.29 instant DDL 数据腐化问题分析
[MYSQL] 数据恢复, 无备份, 只剩一个 ibd 文件 怎么恢复数据?
不小心删除了mysql数据目录, 但还剩个.ibd文件在. 没得备份, 没得binlog , 要恢复这个ibd文件里面的数据.
大大刺猬
2024/04/10
3.3K1
[MYSQL] 数据恢复, 无备份, 只剩一个 ibd 文件 怎么恢复数据?
新特性解读 | MySQL8.0 ALTER TABLE … ALGORITHM=INSTANT
MySQL 8.0.29 之前,在线 DDL 操作中即时添加列只能添加在表的最后一列,对于在某个具体列后面快速添加列很不方便,MySQL 8.0.29 扩展了对 ALTER TABLE … ALGORITHM=INSTANT 的支持:用户可以在表的任何位置即时添加列、即时删除列、添加列时评估行大小限制。
爱可生开源社区
2022/09/26
2.4K0
新特性解读 | MySQL8.0 ALTER TABLE … ALGORITHM=INSTANT
[MYSQL] REDUNDANT行格式的数据解析
mysql的行格式有4种,REDUNDANT,COMPACT,DYNAMIC和COMPRESSED. 最常用的就是DYNAMIC, 也是mysql默认的行格式(很早只有REDUNDANT). 该行格式虽然复杂一点, 但是支持的索引前缀可达3072字节.(REDUNDANT只支持到768字节).
大大刺猬
2024/12/06
2250
[MYSQL] REDUNDANT行格式的数据解析
MySQL 8.0 之 Online DDL快速加列
前几天同事问了我一个问题:业务A从MySQL迁移到MongoDB的原因是什么?
AsiaYe
2021/06/09
2.5K0
MySQL 8.0 之 Online DDL快速加列
MySQL Online DDL与DML并发阻塞关系总结
1,INPLACE,在进行DDL操作时,不影响表的读&写,可以正常执行表上的DML操作,避免与COPY方法相关的磁盘I/O和CPU周期,从而最小化数据库的总体负载。
星哥玩云
2022/08/18
1.1K0
MySQL Online DDL与DML并发阻塞关系总结
[ibd2sql] mysql数据恢复案例002 -- 解析mysql 5.7的表超过38个字段之后的小BUG(已修复)
前不久, 我们的ibd2sql支持直接解析mysql 5.7的数据文件了, 不需要建个8.0环境提供sdi信息了, 我们直接解析frm文件来得到元数据信息. 噢, 这么方便? 那么代价是什么呢? 可能会遇到一些小小的bug... 呆胶布, 已修复.
大大刺猬
2025/04/04
830
[ibd2sql] mysql数据恢复案例002 -- 解析mysql 5.7的表超过38个字段之后的小BUG(已修复)
MySQL谬误集02: DDL锁表
导语 | 本文是MySQL谬误集系列文章的第二篇,该系列旨在纠正一系列似是而非的说法。比如关于MySQL DDL操作,有很多同学认为会锁表,那是不是一定会锁表呢?是锁读还是锁写呢?锁多长时间?不同的DDL操作有差别吗?MySQL从5.5到8.0,对这个问题有什么改进呢?本文做了一个简单的总结。
DBA成江东
2023/08/19
1.6K0
MySQL谬误集02: DDL锁表
ibd2sql v0.2 解析ibd文件为SQL
源码: https://github.com/ddcw/ibd2sql/archive/refs/tags/v0.2.tar.gz
大大刺猬
2023/08/30
1.5K2
ibd2sql v0.2  解析ibd文件为SQL
[pymysqlbinlog] v0.1 发布 离线 解析/分析 MYSQL BINLOG
pymysqlbinlog 是分析/解析binlog的开源工具, 使用GPL-3.0 license
大大刺猬
2024/05/04
3680
[pymysqlbinlog] v0.1 发布  离线 解析/分析 MYSQL BINLOG
64次更改极限!MySQL DBA如何巧妙规避即时DDL操作的陷阱?
我们在MySQL 8.0.12版本中引入了一种新的 DDL 算法,当更改表定义时不会阻塞表。第一个即时操作是由腾讯游戏团队贡献的--在表的末尾添加列。
用户1278550
2024/04/10
2600
64次更改极限!MySQL DBA如何巧妙规避即时DDL操作的陷阱?
MYSQL INNODB ibd文件详解 (2) 提取DDL和DML
mysql数据和索引是放一起的, 主键索引记录主键值和剩余字段值, 二级索引(普通索引)记录 索引值和主键值.
大大刺猬
2023/04/24
1.2K1
MYSQL INNODB ibd文件详解 (2)  提取DDL和DML
[ibd2sql] myisam MYD文件存储格式
现在基本上已经没有使用myisam存储引擎的了, 毕竟它已经没啥优势了.... 那为啥还要来看这个存储引擎的存储格式呢? 闲? 只是最近再看MySQL Internals Manual, 虽然官网已经无法打开这个链接了. 而且该手册基于mysql 5.0 比较老(5.7都停止更新了). 但也有不少能用的东西的.
大大刺猬
2024/05/31
3270
[ibd2sql] myisam MYD文件存储格式
[ibd2sql] ibd2sql v1.7发布(支持drop表的恢复)
备份是十分重要的, 但往往存在没得备份/备份失效的场景, 这时候就是可以使用ibd2sql 来恢复数据了.
大大刺猬
2024/10/29
4430
[ibd2sql] ibd2sql v1.7发布(支持drop表的恢复)
MySQL-8.0 | 数据字典最强解读
数据字典(Data Dictionary)中存储了诸多数据库的元数据信息如图1所示,包括基本Database, table, index, column, function, trigger, procedure,privilege等;以及与存储引擎相关的元数据,如InnoDB的tablespace, table_id, index_id等。MySQL-8.0在数据字典上进行了诸多优化,本文将对其进行逐一介绍。
数据和云
2019/05/13
4K0
推荐阅读
相关推荐
[ibd2sql] mysql数据恢复案例003 -- 有坏块的表怎么解析?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档