前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[ibd2sql] myisam MYD文件存储格式

[ibd2sql] myisam MYD文件存储格式

原创
作者头像
大大刺猬
发布2024-05-31 11:27:41
1430
发布2024-05-31 11:27:41
举报
文章被收录于专栏:大大刺猬大大刺猬

导读

现在基本上已经没有使用myisam存储引擎的了, 毕竟它已经没啥优势了.... 那为啥还要来看这个存储引擎的存储格式呢? 闲? 只是最近再看MySQL Internals Manual, 虽然官网已经无法打开这个链接了. 而且该手册基于mysql 5.0 比较老(5.7都停止更新了). 但也有不少能用的东西的.

MYISAM 存储引擎

mysql5.5 之前默认存储引擎就是myisam. 但该存储引擎不支持事务. 这也就注定他要被替代了, 当然它的存储结构也注定了会被取代(不像innodb/bdb 使用page).

我们随便创建一个myisam存储引擎的表, 并插入几行数据(我这里直接引用官方的例子).

注:我这里测试环境是 5.7.38

代码语言:sql
复制
CREATE TABLE Table1 (column1 CHAR(1), column2 CHAR(1), column3 CHAR(1)) engine=myisam;
INSERT INTO Table1 VALUES ('a', 'b', 'c');
INSERT INTO Table1 VALUES ('d', NULL, 'e');

然后我们就可以在@@datadir/DBNAME目录下面找到对应的文件

代码语言:shell
复制
SHELL> ls -l /data/mysql_3308/mysqldata/ibd2sql_myisam/table1*
-rw-r----- 1 mysql mysql 8638 May 31 09:41 /data/mysql_3308/mysqldata/ibd2sql_myisam/table1.frm
-rw-r----- 1 mysql mysql   20 May 31 09:50 /data/mysql_3308/mysqldata/ibd2sql_myisam/table1.MYD
-rw-r----- 1 mysql mysql 1024 May 31 09:50 /data/mysql_3308/mysqldata/ibd2sql_myisam/table1.MYI

frm 是记录元数据信息的, 之前有讲过, 感兴趣的可以看 https://cloud.tencent.com/developer/article/2409341

MYD 是数据文件, 本文的重点

MYI 是索引文件, 和innodb不同,它索引是单独的文件. 本文不讲.

MYD 又分为3种格式 fixed, dynamic, packed. 我们就看前2种, 第三种涉及到哈夫曼,比较复杂.

注: 所有整型或浮点数都使用小端字节序.

fixed

看名字就知道这种是固定格式的. 也就是只有定长字符时才使用这种格式. 格式也很简单. 就是header+data, header只有nullbitmask. data都是定长的. 读起来也是嘎嘎快. 要注意的是header的第一bit是表示这行数据是否被删除.

删除标记: 0 表示被删除 , 1表示未被删除

nullbitmask: 0 表示不为空, 1表示为空.

不足的补1

所以header的大小就是 int((1+column_no+7)/8)

直接演示吧

代码语言:hex
复制
00000000  f1 61 20 20 62 20 20 63  20 20 f5 64 20 20 20 20  |.a  b  c  .d    |
00000010  20 65 20 20                                       | e  |
00000014

第一行数据

我们只有3个字段, 所以header只有1字节, 即为 b'0xf1' 转为binary就是11110001

第1bit是 1 表示该行数据未被删除.

第2,3,4bit均为0 表示 1,2,3 列数据都不为空.

由于是 char(1), 使用的utf8mb3编码, 所以实际上是占用的3字节.

即第一行数据为

61 20 20 --> a

62 20 20 --> b

63 20 20 --> c

第二行数据

header是 b'0xf5' 转为binary就是 11110101

第1bit 是1, 表示数据未被删除

第2,4bit为0, 表示第1,3列都不为空, 第3bit是1 表示 第二列是空值.

所以第二行数据是

64 20 20 --> d

20 20 20 --> ' ' 这里虽然已经记录是空值了, 但还是占用了3个字节

65 20 20 --> e

我们再来删除第一行数据看看

代码语言:hex
复制
00000000  00 ff ff ff ff ff ff 63  20 20 f5 64 20 20 20 20  |.......c  .d    |
00000010  20 65 20 20                                       | e  |
00000014

我们发现第一字节变为了00 即第1bit也为0, 表示这行数据被删除了. 由于是定长的, 我们只需要后移 1*3 + 1*3 + 1*3 字节就能到第二行数据.

dynamic

这是一种动态格式, 就是有varchar(有时候会被视为char), blob之类的类型时,使用的的格式.

也是header+data, 只不过header部分比较麻烦

代码语言:dynamic
复制
start of header  起始头
actual length    实际长度
unused length    未使用的长度(留一部分给update?)
flags 
overflow pointer 溢出指针:这段空间不够的时候,指向剩余的数据位置
data             数据行
unused bytes     未使用的空间(通常填充\x00)
next row starts  下一行数据

我们建一个带有varchar字段的表

代码语言:sql
复制
create table t20240531(id int, aa varchar(200), bb datetime) engine=myisam;
insert into t20240531 values(1,'ddcw',now());
insert into t20240531 values(2,'https://github.com/ddcw',now());
代码语言:shell
复制
SHELL> hexdump -C /data/mysql_3308/mysqldata/ibd2sql_myisam/t20240531.MYD
00000000  03 00 10 00 00 f8 01 00  00 00 04 64 64 63 77 99  |...........ddcw.|
00000010  b3 7e ab 58 03 00 23 01  00 f8 02 00 00 00 17 68  |.~.X..#........h|
00000020  74 74 70 73 3a 2f 2f 67  69 74 68 75 62 2e 63 6f  |ttps://github.co|
00000030  6d 2f 64 64 63 77 99 b3  7e ab 58 00              |m/ddcw..~.X.|
0000003c

字符串类型的基本上直接就能看出来了.

第一行:

03 00 --> start of header

10 --> 16 这行数据16字节

00 --> 无 未使用的空间(即和下一行数据连在一起的)

00 --> flag

f8 --> 溢出指针

01 00 00 00 --> 第一列, int, 小端字节序, 即值为 1

04 --> varchar的大小为4字节

64 64 63 77 ---> 第二列: ddcw

99 b3 73 ab 58 --> 第三列datetime类型(5字节), 计算方式参考ibd2sql 这里为: 2024-5-31 10:45:24

4 + 1 + 4 + 5

第二行

03 00 --> start of header

23 --> 35字节

01 --> 空了1字节出来

00 --> flag

f8 --> 溢出指针

02 00 00 00 --> 第一列 2

17 --> varcahr大小为23字节

68 74 74 70 73 3a 2f 2f 67 69 74 68 75 62 2e 63 6f 6d 2f 64 64 63 77 第二列: https://github.com/ddcw

99 b3 7e ab 58 --> 第三列 2024-5-31 10:45:24

00 --> 预留的1字节

演示

人工解析太慢了, 我们可以写个脚本来解析, 还能解析更多的数类型. 数据类型和innodb是差不多的, 这里就不讲了.

构造数据:

代码语言:sql
复制
CREATE TABLE `ddcw_alltype_table` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `int_col` int(11) DEFAULT NULL,
  `tinyint_col` tinyint(4) DEFAULT '1',
  `smallint_col` smallint(6) DEFAULT NULL,
  `mediumint_col` mediumint(9) DEFAULT NULL,
  `bigint_col` bigint(20) DEFAULT NULL,
  `float_col` float DEFAULT NULL,
  `double_col` double DEFAULT NULL,
  `date_col` date DEFAULT NULL,
  `datetime_col` datetime DEFAULT NULL,
  `timestamp_col` timestamp NULL DEFAULT NULL,
  `time_col` time DEFAULT NULL,
  `year_col` year(4) DEFAULT NULL,
  `char_col` char(100) DEFAULT NULL,
  `varchar_col` varchar(100) DEFAULT NULL,
  `binary_col` binary(10) DEFAULT NULL,
  `varbinary_col` varbinary(20) DEFAULT NULL,
  `enum_col` enum('A','B','C') DEFAULT NULL,
  `set_col` set('X','Y','Z') DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM;
INSERT INTO `ibd2sql_myisam`.`ddcw_alltype_table` VALUES (1, 5245664, -11, -2411, -239, -4662, 8269.0, -9274.0, '2019-9-23', '1995-3-19 18:4:32', '2003-10-25 6:18:37.', '7:53:17', 1995, 'rXRdHKnyQJgZqZqAIOAGZlTmYDdCAyBxUQcAQYFUwB', 'kiCeqGmIqeiCrOCTGJMNUWOz', 0x33340000000000000000, '80', 'B', 'Y');
INSERT INTO `ibd2sql_myisam`.`ddcw_alltype_table` VALUES (2, 6509620, -35, 1401, -1595, 8053, -3272.0, -3689.0, '2013-1-31', '2007-12-30 8:28:20', '2009-2-27 16:37:2.', '23:28:16', 1996, 'hivYGHpSQmcUcnVeigncbeqrStDoXjTxGUsmNTpysYDCFMiSbxmdZymOIkJAJjsdzIlKFSBfPhfKRGLVV', 'InmDSmiAdWvEENQLbuEZjWtuzGMieiGWeRWAww', 0x35360000000000000000, '14', 'A', 'Y');
INSERT INTO `ibd2sql_myisam`.`ddcw_alltype_table` VALUES (3, 730742, -40, -6559, 2987, 7571, 2274.0, 6372.0, '1995-5-16', '1995-8-18 6:6:32', '2001-9-25 7:33:15.', '17:39:45', 2008, 'TEjBXuuaJOibTcvXaMQZJlEBbUZQNTljmYrhquwpEWGTDXRhhqOmKZJIFKIVWqczpWhxOIpBwFjvupWAyYlidHxFfkzTpBHZ', 'NBGJ', 0x35380000000000000000, '12', 'B', 'X');
INSERT INTO `ibd2sql_myisam`.`ddcw_alltype_table` VALUES (4, 6583237, -116, 3525, -3783, -8103, -2000.0, 567.0, '2022-6-23', '2001-1-1 23:46:15', '2010-3-19 3:50:20.', '12:52:56', 2018, 'sUhhjcFeeFBUCwnIYKJggNJmRYVwDYBRqq', 'bOzyzYMmpnzAmXqJoJbgkyycXwildpWGVvYkXcWS', 0x31380000000000000000, '19', 'A', 'Y');
INSERT INTO `ibd2sql_myisam`.`ddcw_alltype_table` VALUES (5, 954597, -16, 1904, -311, 8410, -4793.0, 2966.0, '1996-12-4', '1997-9-15 18:55:56', '1997-4-1 18:21:59.', '12:33:1', 2004, 'SlNRuZbmtDDZXThHpcacBJSNbDjKLRwzZwjcB', 'QMycyeGpthagILOdvKfGHAYSZjbVLZOIdIeKxBIGnXmJARhfltZSSoiOzlIpkHfThHWjokZxXCIFzyKGgUYRiUTx', 0x37310000000000000000, '14', 'B', 'Y');
INSERT INTO `ibd2sql_myisam`.`ddcw_alltype_table` VALUES (6, 8816683, -20, 8707, -8865, -588, 4467.0, 3013.0, '2020-10-18', '2020-3-18 1:56:5', '2022-2-3 6:19:12.', '17:49:14', 1996, 'UjKtNLgzncwAzHyRjmiqfvfZTOesAZRruGSpypMLlGXQTFmnmLeuHLOKaxphU', 'hswBYpimEAaKPymzZwOPZXMczfLyemBPinDjLVInIKMXURKOyiLcTyuNUmPgdjOxkRnvuTmaiAwlSwkxseil', 0x38340000000000000000, '40', 'A', 'X');
INSERT INTO `ibd2sql_myisam`.`ddcw_alltype_table` VALUES (7, 122873, -15, -9256, -9442, 1426, 1652.0, -665.0, '2005-11-1', '2000-3-25 16:40:14', '2013-1-30 14:31:57.', '7:28:42', 1993, 'EiCVuCSdNfPfhedmprzXxJWxwbJU', 'RWJnkCwuthxWrQgcyexiAMJMtLvalOQzhUn', 0x32390000000000000000, '23', 'A', 'X');
INSERT INTO `ibd2sql_myisam`.`ddcw_alltype_table` VALUES (8, 7959183, 87, 3501, -8353, 7698, -3902.0, 4404.0, '2019-9-14', '2004-5-10 17:15:7', '1999-7-26 18:19:43.', '21:17:2', 1993, 'NMJdGpvUgHVJQSNneTuMbPmnhHHYvXRcTVoRqOnoEURWRmtozgDyaFmMvHlpWICfMCYXdrIgC', 'VotvlNpisoCTmUpjjASfXhsUOznKaODVSTpRhMlcHshFPlbhVJC', 0x31340000000000000000, '35', 'B', 'Y');
INSERT INTO `ibd2sql_myisam`.`ddcw_alltype_table` VALUES (9, 4854976, -4, -2950, 5690, 189, 5695.0, 1029.0, '2000-6-2', '2022-1-17 18:32:21', '1995-8-15 1:34:55.', '23:25:30', 2025, 'mAmxIfxF', 'yJkOtszJcKBbu', 0x38360000000000000000, '84', 'B', 'Y');
INSERT INTO `ibd2sql_myisam`.`ddcw_alltype_table` VALUES (10, 6262780, 119, -5660, -2595, 7833, 1764.0, -3861.0, '2003-10-7', '2020-10-5 15:29:59', '1996-10-10 21:23:6.', '21:15:52', 1990, 'TqZzrwDgQDp', 'MPksKiHyaQgzRUdkSSjVzKhFFV', 0x38380000000000000000, '38', 'B', 'X');

解析MYD文件

代码语言:python3
复制
from myisam_myd_reader import MYD_READER
aa = MYD_READER('/data/mysql_3308/mysqldata/ibd2sql_myisam/ddcw_alltype_table.MYD')
data = aa.read_header(9)  # header
aa.read_int(4)            # int
aa.read_int(4)            # int
aa.read_int(1)            # tinyint
aa.read_int(2)            # smallint
aa.read_int(3)            # mediumint
aa.read_int(8)            # bigint
aa.read_float()           # float
aa.read_double()          # double
aa.read_date()            # date
aa.read_datetime()        # datetime
aa.read_timestamp()       # timestamp
aa.read_time()            # time
aa.read_year()            # year
aa.read_varchar()         # char 100
aa.read_varchar()         # varchar
aa.read_binary(10)        # binary
aa.read_varbinary()       # varbinary
aa.read_int(1)            # enum
aa.read_int(1)            # set

总结

myisam存储引擎使用的很少, 就不细究了. 各数据类型存储基本上和innodb也一样. 也没啥好说的.

附脚本

myisam_myd_reader.py

代码语言:python
代码运行次数:0
复制
#!/usr/bin/env python3
# write by ddcw @https://github.com/ddcw
# 读mysql myisam的myd文件的. 简单的读, 所以不考虑很多信息
import struct

class MYD_READER(object):
	def __init__(self,filename):
		self.f = open(filename,'rb')
	
	def read_int(self,n,signed=True):
		return int.from_bytes(self.f.read(n),'little',signed=signed)

	def read_header(self,n):
		return self.f.read(n)

	def read_varchar(self):
		return self.f.read(self.read_int(1)).decode()

	def read_blob(self,):
		return self.f.read(self.read_int(2)).decode()

	def read_float(self,):
		return struct.unpack('f',self.f.read(4))[0]

	def read_double(self,):
		return struct.unpack('d',self.f.read(8))[0]

	def read_date(self):
		data = self.read_int(3,False)
		year = int(data/(16*32))
		month = int((data-year*16*32)/32)
		day = data - (year*16*32) - (month*32)
		return f"{year}-{month}-{day}"

	def read_datetime(self,):
		"""同ibd里面的datetime, 详情见ibd2sql/innodb_page.py"""
		bdata = self.f.read(5)
		idata = int.from_bytes(bdata[:5],'big')
		year_month = ((idata & ((1 << 17) - 1) << 22) >> 22)
		year = int(year_month/13)
		month = int(year_month%13)
		day = ((idata & ((1 << 5) - 1) << 17) >> 17)
		hour = ((idata & ((1 << 5) - 1) << 12) >> 12)
		minute = ((idata & ((1 << 6) - 1) << 6) >> 6)
		second = (idata& ((1 << 6) - 1))
		return f'{year}-{month}-{day} {hour}:{minute}:{second}'

	def read_time(self,):
		bdata = self.f.read(3)
		idata = int.from_bytes(bdata[:3],'big')
		hour = ((idata & ((1 << 10) - 1) << 12) >> 12)
		minute = (idata & ((1 << 6) - 1) << 6) >> 6
		second = (idata& ((1 << 6) - 1))
		return f'{hour}:{minute}:{second}'

	def read_timestamp(self):
		return self.read_int(4)

	def read_year(self):
		return 1900 + self.read_int(1)

	def read_enum(self):
		pass

	def read_set(self):
		pass

	def read_decimal(self,t1,t2):
		pass # 只做简单的数据类型解析, 因为要精简.

	def read_binary(self,n):
		return "0x"+"".join([ hex(x).split('0x')[-1] for x in self.f.read(n) ])

	def read_varbinary(self):
		return "0x"+"".join([ hex(x).split('0x')[-1] for x in self.f.read(self.read_int(1,False)) ])

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导读
  • MYISAM 存储引擎
  • fixed
  • dynamic
  • 演示
  • 总结
  • 附脚本
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档