前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >MYSQL INNODB ibd文件详解 (2) 提取DDL和DML

MYSQL INNODB ibd文件详解 (2) 提取DDL和DML

原创
作者头像
大大刺猬
发布于 2023-04-24 13:55:29
发布于 2023-04-24 13:55:29
1.1K10
代码可运行
举报
文章被收录于专栏:大大刺猬大大刺猬
运行总次数:0
代码可运行

上一章学习了一些管理页.... 这一张来看看数据(INDEX_PAGE)页

基础知识

mysql数据和索引是放一起的, 主键索引记录主键值和剩余字段值, 二级索引(普通索引)记录 索引值和主键值.

FIL_PAGE_INDEX

FIL_PAGE_INDEX 结构如下

名字

大小

描述

FIL_HEADER

38

PAGE_HEADER

56

数据页信息, 比如有多少字段之类的

PAGE_DATA

用户记录(record)

PAGE_DIRECTORY

目录信息, 相当于用户记录的部分索引.

FIL_TRAILER

8

page_data里存具体的字段,含最大值和最小值

page_directory 每个slot2字节, 指向record的第一位(record按4-8个分一组, 组第一个成员记录组大小(owned))

注: page_directory记录的offset是不包含record及其之前的部分的.

看起来有点怪,但就是这样的....
看起来有点怪,但就是这样的....

FIL_PAGE_SDI

从上面知道, 要解析字段还需要字段可变字段数量才行, 也就是表字典信息, 8.0的字段信息在ibd文件里面就有, 就是FIL_PAGE_SDI. 所以我们只要解析SDI_PAGE就行.

REC_N_FIELDS_ONE_BYTE_MAX = 0x7F #超过(>)这个值, 就使用2字节 (就是第 1 bit位 标记是否使用2字节)

我并没有找到SDI_PAGE的结构信息, 不过不要紧, 官网写了SDI使用了压缩存储. 那我们就直接暴力解析获取字典信息吧....

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def sdi_page_data(bdata):
        _sdi_offset = []
        isok = False
        for i in range(120,16384):
                if isok:
                        break
                for j in range(i,16384):
                        if isok:
                                break
                        try:
                                _test = zlib.decompress(bdata[i:j])
                                _sdi_offset.append(i)
                                i += len(_test)
                                if len(_sdi_offset) == 2:
                                        isok = True #break 2
                                break
                        except:
                                pass
        sdi_info = [json.loads(zlib.decompress(bdata[_sdi_offset[0]:]).decode()), json.loads(zlib.decompress(bdata[_sdi_offset[1]:]).decode())]
        return sdi_info

能获取字段信息了, 那就可以拼接处DDL了. 方向我给了代码的.文末(def get_ddl(data))

PYTHON解析出DDL和DML

不多数了, 直接看效果吧.

为了简单, 我只解析的cluster_index的叶子节点. 非叶子节点并没有解析(我又不查询数据...). 二级索引也没有解析(因为cluster index才记录了完整数据的...)

数据类型, 目前只支持 int 和 varchar, 其它类型需要读者自己去解析(比如date有3字节,年月日对应9:4:5 bit)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import innodb_index
filename = '/data/mysql_3314/mysqldata/db1/t20230424_666.ibd'
page_size = 16384
aa = innodb_index.rec_data_cluster(filename) #我默认就print DDLprint(len(aa))

for x in aa[:10]:
    print(x)
图中数据均为随机生成
图中数据均为随机生成

然后我们去数据库验证下吧

DDL对得上

数据量也对得上

忘记解析字符集了, 不过不要紧....
忘记解析字符集了, 不过不要紧....

前10行数据也对得上

数据是随机生成的哈
数据是随机生成的哈

来点猛的, 把原数据删掉, 用我们这个导入进去看下 (不要在生产环境试哈)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
python>> len(aa)
python>> with open('/tmp/t20230424_666.sql','w') as f:
python>>     for x in aa:
python>>         _size = f.write(x)

shell>> mysql -h192.168.101.21 -P3314 -p123456 -Ddb1 -e 'checksum table t20230424_666;'
shell>> mysql -h192.168.101.21 -P3314 -p123456 -Ddb1 -e 'drop table t20230424_666;'
shell>> mysql -h192.168.101.21 -P3314 -p123456 -Ddb1 -e "
CREATE Table db1.t20230424_666( 
id int not null,
name varchar(50),
addr varchar(100),
email varchar(100) 
,PRIMARY KEY(id)) engine=InnoDB comment='';"
shell>> mysql -h192.168.101.21 -P3314 -p123456 -Ddb1 < /tmp/t20230424_666.sql
shell>> mysql -h192.168.101.21 -P3314 -p123456 -Ddb1 -e 'checksum table t20230424_666;'
为了方便,就使用的checksum
为了方便,就使用的checksum

总结

1. recorde按照4-8个为一组, 每组的第一个记录该组的大小, 并在slot记录自己(本组第一个)的位置(都是为的快速查询...)

2. mysql默认的int类型第一bit是记录正负的, 解析的时候要注意...

3. 解析sdi也可以使用官方的工具 sdi2ibd

4. 本文给的工具只支持部分数据类型. 解析的时候也没有用并发, 如果数据量大, 可以使用并发

附python源码

innodb_page_type.py 在上一篇文章

innodb_file.py 也在上一篇文章(注,均为大端, 有些地方可能写成小端了)

innodb_index.py 如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#解析 FIL_PAGE_INDEX
import struct
import innodb_page_type
import innodb_file

import zlib,json

PAGE_SIZE = 16384

FIL_PAGE_DATA = 38
FIL_PAGE_DATA_END = 8

PAGE_NEW_INFIMUM = 99
PAGE_NEW_SUPREMUM = 112


PAGE_DIR_SLOT_MAX_N_OWNED = 8
PAGE_DIR_SLOT_MIN_N_OWNED = 4

REC_N_FIELDS_ONE_BYTE_MAX = 0x7F #超过(>)这个值, 就使用2字节 (就是第 1 bit位 标记是否使用2字节)

innodb_page_name = {}
for x in dir(innodb_page_type):
	if x[:2] != '__':
		innodb_page_name[getattr(innodb_page_type,x)] = x

def fseg_header(bdata):
	return struct.unpack('>LLH',bdata[:10])

def read_var_len(bdata,start):
	pass


#没找到sdi的结构, 但是发现它是压缩的, 所以我们使用暴力解压获取sdi数据吧....
#However, SDI data is compressed to reduce the storage footprint  https://dev.mysql.com/doc/refman/8.0/en/serialized-dictionary-information.html
#TODO sdi2ibd
def sdi_page_data(bdata):
	_sdi_offset = []
	isok = False
	for i in range(120,16384):
		if isok:
			break
		for j in range(i,16384):
			if isok:
				break
			try:
				_test = zlib.decompress(bdata[i:j])
				_sdi_offset.append(i)
				i += len(_test)
				if len(_sdi_offset) == 2:
					isok = True #break 2
				break
			except:
				pass
	sdi_info = [json.loads(zlib.decompress(bdata[_sdi_offset[0]:]).decode()), json.loads(zlib.decompress(bdata[_sdi_offset[1]:]).decode())]
	return sdi_info

#根据sdi信息返回表DDL语句
def get_ddl(data):
	ddl = f"CREATE {data[1]['dd_object_type']} {data[1]['dd_object']['schema_ref']}.{data[1]['dd_object']['name']}( \n"
	pk_list = []
	auto_key = None
	col_len = len(data[1]['dd_object']['columns']) - 2
	_col_len = 0
	coll = []
	for col in data[1]['dd_object']['columns']:
		if col['name'] in ['DB_TRX_ID','DB_ROLL_PTR']:
			continue
		if col['is_nullable']:
			ddl += f"{col['name']} {col['column_type_utf8']}"
		else:
			ddl += f"{col['name']} {col['column_type_utf8']} not null"
		_col_len += 1
		coll.append(col['name'])
		if col['is_auto_increment']:
			ddl += ' auto_increment,\n'
		elif _col_len < col_len:
			ddl += ',\n'
		else:
			ddl += ' \n'

	index_ddl = ''
	for i in data[1]['dd_object']['indexes']:
		name = "PRIMARY KEY" if i['name'] == 'PRIMARY' else  f"index {i['name']}"
		index_ddl += f",{name}("
		for x in i['elements']:
			if x['length'] < 4294967295:
				index_ddl += f"{coll[x['column_opx']]},"
		index_ddl = index_ddl[:-1] #去掉最后一个,
		index_ddl += ")"
	
	ddl += index_ddl
	ddl += f") engine={data[1]['dd_object']['engine']} comment='{data[1]['dd_object']['comment']}';"	
	return ddl

def get_rec_data(bdata,columns,index):
	rdata = []
	page0 = page_index(bdata)
	index_ = [] 
	for x in index['elements']:
		if x['length'] < 4294967295:
			index_.append(x['column_opx'])
	len_column = len(columns) - 2 #去掉DB_TRX_ID(6)  DB_ROLL_PTR(7)
	for x in page0.records:
		rec_header = rec_extra_header(bdata[x-5:x])
		if rec_header.deleted:
			#print('deleted')
			continue #删除的数据就不要了
		tdata = [ None for x in range(len_column) ]
		null_bitmask_size = int((len_column+7)/8)
		null_bitmask = int.from_bytes(bdata[x-null_bitmask_size-5:x-5],'big')
		toffset = x-5-null_bitmask_size
		doffset = x
		#print(x,toffset,doffset,null_bitmask)
		#read_key:
		for k in index_: #遍历索引字段
			ktype = columns[k]['type']
			if ktype in [16,]:#变长...
				ksize = bdata[toffset-1:toffset] #懒得考虑超过128的情况了...
				if ksize > REC_N_FIELDS_ONE_BYTE_MAX:
					ksize = bdata[toffset-2:toffset]
					toffset -= 1
				toffset -= 1
				#print(toffset)
				tdata[k] = bdata[doffset:doffset+ksize].decode()
				doffset += ksize
			elif ktype in [15,]: #date
				tdata[k] = bdata[doffset:doffset+3]
				doffset += 3

			elif ktype in [4,]: #DATA_BINARY 4字节 第一bit是记录正负
				_t = struct.unpack('>L',bdata[doffset:doffset+4])[0]
				tdata[k] = (_t&((1<<31)-1)) if _t&(1<<31) else -(_t&((1<<31)-1))
				#tdata[k] = bdata[doffset:doffset+4]
				doffset += 4

		doffset += 6 + 7
		#遍历其它数据,
		for k in range(len_column):
			if k in index_: #索引已经记录了数据了
				continue
			if null_bitmask&(1<<k): #空字段
				continue
			ktype = columns[k]['type']
			#print(columns[k]['name'],toffset)
			if ktype in [16,]:#变长...
				ksize = struct.unpack('>B',bdata[toffset-1:toffset])[0] #懒得考虑超过128的情况了...
				if ksize > REC_N_FIELDS_ONE_BYTE_MAX:
					ksize = struct.unpack('>H',bdata[toffset-2:toffset])[0]
					toffset -= 1
				toffset -= 1
				tdata[k] = bdata[doffset:doffset+ksize].decode()
				doffset += ksize
			elif ktype in [15,]: #date
				tdata[k] = bdata[doffset:doffset+3]
				doffset += 3

			elif ktype in [4,]: #DATA_BINARY 4字节
				_t = struct.unpack('>L',bdata[doffset:doffset+4])[0]
				tdata[k] = (_t&((1<<31)-1)) if _t&(1<<31) else -(_t&((1<<31)-1))
				#tdata[k] = bdata[doffset:doffset+4]
				doffset += 4
		rdata.append(tdata)
		#break
		
	return rdata
			

#数据类型 storage/innobase/include/data0type.h
def rec_data_cluster(filename): #主键索引必须显示主键, 
	"""
	filename: 文件名
	"""
	f = open(filename,'rb')
	f.seek(PAGE_SIZE*3,0)
	sdi_info = sdi_page_data(f.read(PAGE_SIZE))
	ddl = get_ddl(sdi_info)
	print(ddl)
	db_table = f'{sdi_info[1]["dd_object"]["schema_ref"]}.{sdi_info[1]["dd_object"]["name"]}'
	index = sdi_info[1]['dd_object']['indexes'][0]
	columns = sdi_info[1]['dd_object']['columns']
	root_page = int(index['se_private_data'].split('root=')[1].split(';')[0])
	#f.seek(PAGE_SIZE*root_page,0)
	#只需要找到第一个叶子节点, 然后解析叶子节点的数据即可. (inode不是就记录了第一个叶子节点么.....)
	f.seek(2*PAGE_SIZE,0)
	inode = innodb_file.inode(f.read(PAGE_SIZE)[38:PAGE_SIZE-8])
	leaf_page = 0
	for x in inode.index:
		if x['no_leaf'] == root_page:
			leaf_page = x['leaf']
			break
	
	rdata = [] #[(col1,col2),(col1,col2)] 
	next_page_number = leaf_page
	while True:
		if next_page_number == 4294967295:
			break
		#print('PAGE_NUM:',next_page_number)
		f.seek(next_page_number*PAGE_SIZE,0)
		page0 = page(f.read(PAGE_SIZE))
		next_page_number = page0.FIL_PAGE_NEXT
		rdata += get_rec_data(page0.bdata,columns,index)
		#break

	rdata_sql = []
	for x in rdata:
		_v = ''
		for j in x:
			_v += f'{j},' if isinstance(j,int) else f'"{j}",'
		_v = _v[:-1]
		_sql = f'insert into {db_table} values({_v});'
		rdata_sql.append(_sql)
	return rdata_sql


#storage/innobase/rem/rec.h
REC_INFO_MIN_REC_FLAG = 0x10
REC_INFO_DELETED_FLAG = 0x20
REC_N_OWNED_MASK = 0xF
REC_HEAP_NO_MASK = 0xFFF8
REC_NEXT_MASK = 0xFFFF
#REC_STATUS_ORDINARY 0
#REC_STATUS_NODE_PTR 1
#REC_STATUS_INFIMUM 2
#REC_STATUS_SUPREMUM 3
class rec_extra_header(object):
	def __init__(self,bdata):
		if len(bdata) != 5:
			return False
		fb = struct.unpack('>B',bdata[:1])[0]
		self.deleted = True if fb&REC_INFO_DELETED_FLAG else False  #是否被删除
		self.min_rec = True if fb&REC_INFO_MIN_REC_FLAG else False #if and only if the record is the first user record on a non-leaf
		self.owned = fb&REC_N_OWNED_MASK # 大于0表示这个rec是这组的第一个, 就是地址被记录在page_directory里面
		self.heap_no = struct.unpack('>H',bdata[1:3])[0]&REC_HEAP_NO_MASK #heap number, 0 min, 1 max other:rec
		self.record_type = struct.unpack('>H',bdata[1:3])[0]&((1<<3)-1) #0:rec 1:no-leaf 2:min 3:max
		self.next_record = struct.unpack('>H',bdata[3:5])[0]
	def __str__(self):
		return f'deleted:{self.deleted}  min_rec:{self.min_rec}  owned:{self.owned}  heap_no:{self.heap_no}  record_type:{self.record_type}  next_record:{self.next_record}'

class page(object):
	def __init__(self,bdata):
		if len(bdata) != PAGE_SIZE:
			return None

		self.FIL_PAGE_SPACE_OR_CHKSUM, self.FIL_PAGE_OFFSET, self.FIL_PAGE_PREV, self.FIL_PAGE_NEXT, self.FIL_PAGE_LSN, self.FIL_PAGE_TYPE, self.FIL_PAGE_FILE_FLUSH_LSN = struct.unpack('>4LQHQ',bdata[:34])
		self.FIL_PAGE_SPACE_ID = struct.unpack('>L',bdata[34:38])[0]
		
		self.CHECKSUM, self.FIL_PAGE_LSN = struct.unpack('>2L',bdata[-8:])
		self.bdata = bdata

	def fil_header(self):
		return f'PAGE_SPACE_ID:{self.FIL_PAGE_SPACE_ID}  PAGE_TYPE:{innodb_page_name[self.FIL_PAGE_TYPE]} PREV:{self.FIL_PAGE_PREV}  NEXT:{self.FIL_PAGE_NEXT}'


	def fil_trailer(self):
		return f'CHECKSUM:{self.CHECKSUM}  PAGE_LSN:{self.FIL_PAGE_LSN}'


class page_index(page):
	def __init__(self,bdata):
		super().__init__(bdata)


		self.cols = [] #字段类型列表.

		#PAGE_HEADER
		bdata = self.bdata[FIL_PAGE_DATA:PAGE_SIZE-FIL_PAGE_DATA_END]
		self.PAGE_N_DIR_SLOTS, self.PAGE_HEAP_TOP, self.PAGE_N_HEAP, self.PAGE_FREE, self.PAGE_GARBAGE, self.PAGE_LAST_INSERT, self.PAGE_DIRECTION, self.PAGE_N_DIRECTION, self.PAGE_N_RECS, self.PAGE_MAX_TRX_ID, self.PAGE_LEVEL, self.PAGE_INDEX_ID = struct.unpack('>9HQHQ',bdata[:36])
		self.PAGE_BTR_SEG_LEAF = fseg_header(bdata[36:46])
		self.PAGE_BTR_SEG_TOP = fseg_header(bdata[46:56])


		#PAGE_DIRECTORY (这两字节指向的数据位置, 不包含数据前面的 5-byte header  就是REC_N_NEW_EXTRA_BYTES)
		page_directorys = []
		for x in range(int(PAGE_SIZE/2)):
			tdata = struct.unpack('>H',self.bdata[-(2+FIL_PAGE_DATA_END+x*2):-(FIL_PAGE_DATA_END+x*2)])[0]
			page_directorys.append(tdata)
			if tdata == PAGE_NEW_SUPREMUM: 
				break #slot遍历完成
		self.page_directorys = page_directorys



		#RECORDS(PAGE_DATA)  innodb_default_row_format
		offset = self.page_directorys[:1][0] #第一字段, 虚拟的...
		records = []
		while True:
			record_type = self.bdata[offset-3:offset-2]
			if record_type == b'\x03' or record_type == b'': #00 普通rec(leaf),  01 no_leaf   02 min_rec  03 max_rec
				break
			records.append(offset)
			offset += struct.unpack('>H',self.bdata[offset-2:offset])[0]
		records.remove(PAGE_NEW_INFIMUM) #去掉第一个页(虚拟的页,你把握不住)
		self.records = records


	def get_record(self,rec_offset):
		"""
		根据用户给的偏移量返回对于的数据
		"""
		pass

	def find_data_with_index(index_value):
		"""
		根据用户给的index值查找数据 找page, 然后通过二分法找rec(利用slot)
		"""
		pass


	def record(self):
		return f'RECORDS:{len(self.records)}'

	def page_header(self):
		return f'SLOTS:{self.PAGE_N_DIR_SLOTS}  PAGE_LEVEL:{self.PAGE_LEVEL}  INDEX_ID:{self.PAGE_INDEX_ID}  RECORDS:{self.PAGE_N_RECS}  PAGE_HEAP_TOP:{self.PAGE_HEAP_TOP}  PAGE_GARBAGE(deleted):{self.PAGE_GARBAGE}  PAGE_FREE:{self.PAGE_FREE}'


	def page_directory(self):
		return f'SLOTS:{len(self.page_directorys)}   MAX:{self.page_directorys[-1:]}  MIN:{self.page_directorys[:1]}'

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

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

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

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

评论
登录后参与评论
1 条评论
热度
最新
太牛逼了 给大佬跪了
太牛逼了 给大佬跪了
回复回复1举报
推荐阅读
编辑精选文章
换一批
[MYSQL] 自定义mysql脱敏中间件 -- 对指定连接进行指定字段的数据脱敏
昨天群友有这么一个需求: 对应特定的连接查询特定的表字段的时候,对其进行脱敏. 比如: select name, phone from tblname where id=xxx 查询出来的phone需要是脱敏的.即显示为:152****6666这种样子.
大大刺猬
2025/04/08
2080
[MYSQL] 自定义mysql脱敏中间件 -- 对指定连接进行指定字段的数据脱敏
[ibd2sql] mysql数据恢复案例003 -- 有坏块的表怎么解析?
数据文件是5.7环境的, 现在ibd2sql不再需要转换到8.0环境即可解析. 于是我们使用如下命令解析:
大大刺猬
2025/04/22
1560
[ibd2sql] mysql数据恢复案例003 -- 有坏块的表怎么解析?
MYSQL INNODB ibd文件详解 (3) FIL_PAGE_SDI
虽然上一章已经提取了DDL, 但是存储DDL的sdi页还没有讲.... 现在补上呗..
大大刺猬
2023/04/25
9342
MYSQL INNODB ibd文件详解 (3) FIL_PAGE_SDI
MYSQL INNODB ibd文件详解 (1)
每个ibd文件包含1个(不考虑ibdata)表空间(一张表), 每个表空间包含若干个segment. 每个segment对应一个索引的叶子节点/非叶子节点. 也就是每2个segment对于一个索引. 每个segment对于n个区(空间分配是按照区来的). 每个区(extent)对于n个page. 为了方便管理区, 每256个区会使用一个page(XDES:EXTENT DESCRIPTOR)来记录相关信息. (是不是都晕了... 不慌,后面有图)
大大刺猬
2023/04/22
2.9K0
MYSQL INNODB ibd文件详解 (1)
[MYSQL] REDUNDANT行格式的数据解析
mysql的行格式有4种,REDUNDANT,COMPACT,DYNAMIC和COMPRESSED. 最常用的就是DYNAMIC, 也是mysql默认的行格式(很早只有REDUNDANT). 该行格式虽然复杂一点, 但是支持的索引前缀可达3072字节.(REDUNDANT只支持到768字节).
大大刺猬
2024/12/06
2150
[MYSQL] REDUNDANT行格式的数据解析
[MYSQL] mysql undo文件解析(1)
之前解析过mysql的各种文件, 比如:ibd,redo,binlog,frm,myd. 貌似漏了个undo文件没有解析... 现在来补上 -_-
大大刺猬
2024/08/02
1810
[MYSQL] mysql undo文件解析(1)
[MYSQL] mysql undo文件解析(2)
上一章讲了基础的undo文件结构.我们知道了undo文件和ibd文件一样. 只不过把index_page换成了 21(FIL_PAGE_TYPE_RSEG_ARRAY) 6(FIL_PAGE_TYPE_SYS) 2(FIL_PAGE_UNDO_LOG) , 所有本章主要讲这两仨儿.
大大刺猬
2024/08/08
3130
[MYSQL] mysql undo文件解析(2)
mysql提升10倍count(*)的神器
之前做数据迁移之后, 关于数据的一致性校验, 我们是使用checksum来做的, 也可以使用count(*), 但是都比较慢. 而数据校验的时候, 数据实际上是静态的, 没有业务使用的, 欸, 那我们是不是就可以自己来统计行数呢?
大大刺猬
2025/03/21
3360
mysql提升10倍count(*)的神器
简单学习一下ibd数据文件解析
这是尝试使用Golang语言简单解析MySQL 8.0的数据文件(*.ibd)过程的一个简单介绍,解析是反序列化的一个过程,或者叫解码的过程。
GreatSQL社区
2022/03/06
7630
[ibd2sql] ibd2sql v1.0 发布 & ibd文件结构说明
修复了一些之前的问题, 比如做过online ddl (instant)的表解析的时候就需要注意record header的第2bit 标记位.
大大刺猬
2024/01/09
1K0
[ibd2sql] ibd2sql v1.0 发布 & ibd文件结构说明
[MYSQL] show engine innodb status中的死锁 分析
很久以前(也才2年)写过一个解析innodb_status的脚本. 看起来像那么回事, 其实就是做了个翻译和总结.
大大刺猬
2024/08/26
7220
[MYSQL] show engine innodb status中的死锁 分析
[MYSQL] mysql.ibd 文件解析 (sdi page) (非debug模式下查看隐藏系统表)
在mysql 8.0版本,系统表的存储引擎由myisam改为了innodb, @@datadir/mysql目录下一堆的数据文件通通放到@@datadir/mysql.ibd文件中了. 但很多表在非debug模式下是无法查看里面的数据的. 这TM就很恼火. (刚学完innodb的磁盘结构, 我能受这气?). 所以我们现在来解析下mysql.ibd文件. (也顺便为 ibd2sql 2.0 做准备)
大大刺猬
2024/09/18
7990
[MYSQL] mysql.ibd 文件解析 (sdi page) (非debug模式下查看隐藏系统表)
[Linux&MYSQL] xfs文件系统浅析 -- 恢复drop的表
我们知道ibd2sql可以解析ibd文件从而恢复mysql的数据, 但没得ibd文件的时候又该怎么办呢? (哎呀, 不小心drop了表, 又没得备份!)
大大刺猬
2024/10/15
3282
[Linux&MYSQL]  xfs文件系统浅析 -- 恢复drop的表
MYSQL REDO LOG文件解析
一般备份恢复都是用的binlog, redo log好像从来没去管过, 就跟不会坏似的...(这跟redo设计有关).
大大刺猬
2023/04/14
3.2K0
MYSQL REDO LOG文件解析
InnoDB表聚集索引层高什么时候发生变化
有个选项 innodb_fill_factor 用于定义InnoDB page的填充率,默认值是100,但其实最高只能填充约15KB的数据,因为InnoDB会预留1/16的空闲空间。在InnoDB文档中,有这么一段话
田帅萌
2019/09/05
8250
[ibd2sql] MYSQL ibd文件解析 (6) BLOB/TEXT 页如何存储在磁盘上的 -- FIL_PAGE_TYPE_LOB_FIRST
虽然ibd2sql已经支持了 大字段(BLOB), 但还不支持溢出页(extra page), 也就是对大字段支持不完全. 是时候表演正在的技术了 是时候来完善大字段溢出页了.
大大刺猬
2024/05/13
2240
[ibd2sql] MYSQL ibd文件解析 (6)  BLOB/TEXT 页如何存储在磁盘上的 -- FIL_PAGE_TYPE_LOB_FIRST
[MYSQL] mysql 5.7 溢出页 FIL_PAGE_TYPE_BLOB
昨天发现之前为了支持超多字段的时候, 给sdi传递了个filename参数, 但是忘记5.7环境传递这个参数了...
大大刺猬
2025/02/21
1070
一种探究 InnoDB 的存储格式的新方式
文件的存储结构包含了系统大量的实现细节,比如 java 的 class 文件结构,rocksdb 的存储结构。MySQL InnoDB 的存储格式比较复杂,但确实我们理解 MySQL 技术内幕不必可少的一环。传统的分析的方法有下面这两个
挖坑的张师傅
2022/05/13
4610
一种探究 InnoDB 的存储格式的新方式
[ibd2sql] mysql frm 文件结构解析
准备给ibd2sql加个解析 mysql 5.7 的ibd文件功能. mysql 8.0的元数据信息是存储在ibd文件的sdi page里面的. 但是mysql 5.7 的表结构信息是存储在 frm 文件的, 所以就得解析下这个frm文件了. 本以为它是文本文件, 很遗憾, 还是二进制的....
大大刺猬
2024/04/15
5200
[ibd2sql] mysql frm 文件结构解析
[MYSQL] lz4压缩数据结构并使用Python解析
上一篇文章我们介绍了mysql压缩页的存储格式以及解析方法. 但只考虑了zlib的情况. 对于lz4压缩的就没管它的死活了. 现在来补充下lz4格式的解析.
大大刺猬
2024/09/24
4210
[MYSQL] lz4压缩数据结构并使用Python解析
推荐阅读
相关推荐
[MYSQL] 自定义mysql脱敏中间件 -- 对指定连接进行指定字段的数据脱敏
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验