我们知道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 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 | 元数据中记录的该字段添加后的版本 |
# 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, 其实也就2种情况要考虑nullable.
我们直接使用8.0.28和8.0.38来演示把. (5.7的不在本次演示范围内).
不放完整过程了, 太长了.(新增: 60个随机的online ddl)
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 删除。