前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >[MYSQL] 关于varchar使用1字节还是2字节来存储大小的深度分析!(bug id 117736)

[MYSQL] 关于varchar使用1字节还是2字节来存储大小的深度分析!(bug id 117736)

原创
作者头像
大大刺猬
发布2025-03-19 11:03:24
发布2025-03-19 11:03:24
3900
代码可运行
举报
文章被收录于专栏:大大刺猬大大刺猬
运行总次数:0
代码可运行

导读

我们知道varchar是变长字段, 即长度是不固定的, 所以需要额外的存储其长度. 那么需要使用多少字节来存储呢?

首先, 我们知道mysql中varchar的限制是65535字节, 即在utf8mb3字符集下, 限制为varchar(21845); 在utf8mb4字符集下限制为varchar(16383). 超过这个大小, 你将会收到如下报错信息:

代码语言:txt
复制
ERROR 1074 (42000): Column length too big for column 'aa' (max = 21845); use BLOB or TEXT instead
ERROR 1074 (42000): Column length too big for column 'aa' (max = 16383); use BLOB or TEXT instead

既然最大才65535字节, 那我们就只需要2字节就可以表示其长度了. 遇到varchar通通使用2字节表示其长度不就行了吗?

的确是可行的,而且还非常简单. 这只是站在第一层的考虑. 实际存储时,也并非如此.

数据行结构

在我们分析varchar使用1字节还是2字节来存储大小?时, 我们先来看看数据行在磁盘上是怎么存储的(已经了解的同学可以跳过)

我们就只看 主键索引的叶子节点(就这存数据的), 如下图:

记录变量长度的部分位于variable length部分, 当后面的pk或者数据部分有变量时,就在variable length读取其长度.

分析

首先, 我们先去官网查询一下相关信息, 毕竟官网信息非常的全面.

果不其然,我们在数据类型章节找到了相关信息.

其描述如下:

In contrast to CHAR, VARCHAR values are stored as a 1-byte or 2-byte length prefix plus data. The length prefix indicates the number of bytes in the value. A column uses one length byte if values require no more than 255 bytes, two length bytes if values may require more than 255 bytes.

这段话大意是: 和char不同, varchar存储使用1-2字节来记录其长度, 如果varchar不超过255字节, 则使用1字节来记录; 如果超过255字节, 则使用2字节来存储其长度.

看起来虽然麻烦一丢丢了, 但却更合理了.(能使用1字节存储的就不需要使用2字节存储)

既然官网都这么写了, 那就应该是这样了哦. 我只能说这只是站在第二层!

深度分析

啊, 官网都直接写了啊, 还分析个屁! 本着严谨的学习态度, 我们再来考深度分析分析.

比如存在如下的表和数据

代码语言:sql
复制
create table t20250319(c1 varchar(300) primary key) default character set utf8mb4;
insert into t20250319 values('1234567890');

虽然元数据(varchar(300))里面记录的是300字符(即1200字节), 但实际数据只有10字节啊, 这有必要使用2字节来存储其大小吗?

看起来是没必要的. 但是我们不知道实际的数据大小, 解析的时候怎么确定是1字节还是2字节呢?

诶, 假设: 我们可以看第1字节的第1bit是否为1, 若为1, 则表示使用2字节来记录大小; 若为0,则表示使用1字节来记录大小. 这样的话, 当数据小于128时, 我们就只需要1字节就可以表示其大小了; 当数据大于128时, 我们再使用2字节就是了(计算的时候得排除掉第1字节的第1bit).

那对于元数据不超过255字节的字段也这么存储? 当然不需要了, 我们先判断元数据信息, 是否大于255, 若不大于255,则直接读取1字节即可; 若大于255,我们再读取第1字节的第1bit, 判断是否为1.

既然我们牺牲了1bit来记录是否使用2字节, 那我们2字节能表示的范围就只有 2**15=32768了. INNODB PAGE_SIZE最大只有64KB,而每页至少要2行数据, 所以每行数据(页内)不超过32K, 也是在能表示的范围内的呢. 所以没得问题!

上述的假设理论上是没得问题的, 那对于第1字节的第1bit超过255之后, 使用2字节表示大小, 那这2字节到底怎么表示的大小呢?

解析方法如下:

我们读取右边的1字节, 若>128, 则再读取1字节, 由于右边已经牺牲了1字节, 所以只能作为高位(低位必须得能表示1-255), 所以 (右边数据-128)*255 即表示高位大小, 然后加上左边的1字节大小即可.

看起来有丢丢复杂, 没事,我们使用python代码来完整的表示:

代码语言:python
代码运行次数:0
运行
复制
size = readint_reverse(1) # 读取1字节
if maxsize <= 255:
	return size
elif size > 128:
	size = readint_reverse(1) + (size-128)*256
return size

maxsize表示元数据的大小(字节)

readint_reverse表示读取1字节的variable length

使用python代码看起来是不是简单多了. 到这了, 就是站在第三层!

是否真的如此呢? 我们还得验证下 -_-

验证

当元数据信息小于255字节的时候, 是没得争议的使用1字节来记录大小, 所以我们只验证当元数据信息大于255时的情况.

先准备如下数据

代码语言:sql
复制
create table t20250319_varchar(c1 varchar(300) primary key) default character set utf8mb4;
insert into t20250319_varchar values('1234567890');
insert into t20250319_varchar values(repeat('x',129));

这里只有1个字段且为主键, 是为了方便解析. 不然还得考虑trxid,nullbitmask之类的,太复杂了.

然后我们使用如下python脚本来解析数据文件:

代码语言:python
代码运行次数:0
运行
复制
#!/usr/bin/env python
# write by ddcw
# 验证varchar使用1字节还是2字节的脚本

import struct
import sys
filename = sys.argv[1]
f = open(filename,'rb')
f.seek(4*16384,0) # 如果是5.7, 则改为f.seek(3*16384,0), 因为没得sdi
data = f.read(16384)
f.close()

# 开始解析数据了
offset = 99

offset += struct.unpack('>h',data[offset-2:offset])[0]
size = struct.unpack('>B',data[offset-5-1:offset-5])[0]
if size > 128: # 不需要考虑元数据信息小于255的情况, 因为我们只验证大于255的情况...
	size = struct.unpack('>B',data[offset-5-1-1:offset-5-1])[0] + (size-128)*256
print('第1行数据:',data[offset:offset+size].decode())


offset += struct.unpack('>h',data[offset-2:offset])[0]
size = struct.unpack('>B',data[offset-5-1:offset-5])[0]
if size > 128:
	size = struct.unpack('>B',data[offset-5-1-1:offset-5-1])[0] + (size-128)*256
print('第2行数据:',data[offset:offset+size].decode())

然后我们使用该脚本解析数据即可:

按照我们刚才猜想的情况, 的确正确的解析出来了数据. 证明我们的猜想是正确的, 看来官方文档还是有待完善的.

总结

所以结论就是: 当元数据信息<=255字节时, 使用1字节记录大小; 当元数据信息大于255时,使用1-2字节记录大小.

思考: 那对于定长的char又是怎么样的呢? (站在第4层!)

这种细节问题, 虽然不影响我们对innodb存储的理解, 但是在编写相关工具时,就会被小坑一手. 这个问题我已经提相关BUG给官方了, 后续反馈我会同步更新的

相关BUG地址: https://bugs.mysql.com/117736

参考:

https://dev.mysql.com/doc/refman/8.0/en/char.html

https://github.com/ddcw/ibd2sql

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导读
  • 数据行结构
  • 分析
  • 深度分析
  • 验证
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档