[ibd2sql] myisam MYD文件存储格式

2024年 5月 31日 42.4k 0

导读

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

MYISAM 存储引擎

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

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

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> 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)
直接演示吧

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

[ibd2sql] myisam MYD文件存储格式-1
第一行数据
我们只有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

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

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

[ibd2sql] myisam MYD文件存储格式-2
我们发现第一字节变为了00 即第1bit也为0, 表示这行数据被删除了. 由于是定长的, 我们只需要后移 1*3 + 1*3 + 1*3 字节就能到第二行数据.

dynamic

这是一种动态格式, 就是有varchar(有时候会被视为char), blob之类的类型时,使用的的格式.
也是header+data, 只不过header部分比较麻烦

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

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

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> 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
[ibd2sql] myisam MYD文件存储格式-3
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是差不多的, 这里就不讲了.
构造数据:

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文件

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

[ibd2sql] myisam MYD文件存储格式-4

总结

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

附脚本

myisam_myd_reader.py

#!/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 22)
year = int(year_month/13)
month = int(year_month%13)
day = ((idata & ((1 17)
hour = ((idata & ((1 12)
minute = ((idata & ((1 6)
second = (idata& ((1

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论