3.3 varchar(M) 能存多少个字符,为什么提示最大16383?
首先要理解varchar(M)
的M
是说字符个数,而不是字节。
为什么不能varchar(20000)
之类的,是20000
个字符放不下吗?
为什么提示只能最大16383
个字符呢?这个数字是怎么算出来的?
这个我就得和你好好唠嗑了!
varchar
是变长的,「varchar(64)
」 能存放0~64
个字符不等,并不一定是存了最大64
个字符,谁知道这个类型到底存了几个字符呢?innodb
设计的时候,就已经考虑到了,不过是用字节作为单位,后续我们可以根据对应字符集转变为字符来理解,innodb
必须记录变长字段varchar
真实占用的字节数L
。前面说过了,innodb
最多分配2
个字节(16个bit位)的空间去记录这个L
。
❝
InnoDB
有它的一套规则,我们引入W
、M
和L
这几个符号:
- 假设某个字符集中「最多」需要
W
字节来表示一个字符
utf8mb4
字符集中的W
就是4
utf8
字符集中W
就是3
gbk
字符集中的W
就是2
ascii
字符集中的W
就是1
。
- 对于变长类型
VARCHAR(M)
来说,这种类型表示能存储最多M
个字符(注意是字符不是字节) 所以这个类型能表示的字符串最多占用的字节数就是M × W
。 - 假设它实际存储的字符串占用的字节数是
L
。
❞
来看极限边界情况,innodb
为了记录一下varchar
真实存储多少个「字节」,最多分配2
个字节的空间去记录,2
个字节16
个比特位,全部为1
,最大能记录的数字是2^16-1
是65535
个,innodb
最大能记录varchar
占用的字节数就是65535
个,utf8mb4
字符集一个字符是最大是4
个字节,65535 / 4 = 16383.75
,只要varchar
字符数不超过16383
个,innodb
就可以记录真实占用的长度L
,再多就记录不了了!所以就能解释刚刚的图了,varchar(20000)
不行,最大也就16383
个字符
「但是!这里强调是有但是的!」
「行最大长度是65535
字节」,行里面有很多东西,包括变长字段列表、NULL
值列表、记录头信息。你得考虑该字段如果允许为NULL
,NULL
值列表会占用一个字节(只要没超过8
个字段),每一列字段的变长字段实际长度会花费1~2
个字节,如果该字段的数据太大,会变成溢出列,该字段的数据会分成很多行存储(后面会讲,你可以看完NULL
值列表和溢出列后再回来看这个例子)。所以即便提示16383
个字符,你也绝对不可能存到16383
。
我做了个测试
create table t2 ( name varchar(16383))charset=utf8mb4;
不断往这个字段添加字符保存测试,最后发现,这些字符总长度到极限也就是48545
字节。
这里48545
个字节,再多一个字符就会报错,远不到65535
字节,差了1W
多字节。主要是因为溢出列的原因,数据分散在不同的行中,所以,很长的数据,建议往text
类型考虑。这个现象可以看出,varchar(M)
的M
很大,实际是达不到M
这个边界值的。
下面说明一下规则(讲解中字符集用utf8mb4
,W=4
)
规则一:如果允许存储的最大字节数M × W <= 255
,「varchar
占用的真实字节数L
只分配1
个字节来表示。」
❝
有人说,允许存储的最大字节数M × W <= 255
,即允许存储的最大字符数 <= ⌊255 / 4⌋ = 「63」个时,varchar
占用的真实字节数L
仅分配1
个字节就能表示。这个结论正确吗? 显然错误,因为这里255 / 「4」,你怎么知道每个存储的一个字符是4
个字节呢?难道全部存的emoji
表情?不存字母汉字啥的?InnoDB
在读记录的变长字段长度列表时先查看表结构,如果某个变长字段允许存储的最大字节数不大于255
时,只用1
个字节来表示真实数据占用的字节。
❞
规则二:如果允许存储的最大字节数M × W > 255
,则分为两种情况:
如果实际存储字节L <= 127
,varchar
占用的真实字节数L
仅分配1
个字节就能表示。(⌊ … ⌋表示向下取整)
❝
有人说,实际存储字节L <= 127,即「实际存储字符」 <= ⌊127 / 4⌋ = 「31」个时,varchar占用的真实字节数L仅分配1个字节就能表示。这个结论正确吗? 显然错误,因为这里127 / 「4」,你怎么知道实际存储的一个字符是4个字节呢?难道全部存的emoji表情?不存字母汉字啥的?
❞
如果实际存储字节L > 127
,varchar
占用的真实字节数L
需要分配2
个字节才能表示。
另外需要注意的是,变长字段列表只存储非NULL
的列的长度。
表记录是这样的
对于第二条记录,c4
列值为NULL
,所以只存储c1
和c2
列即可。
第一条记录的变长字段长度列表部分占用3
字节空间,因为有c1
、c2
、c4
列,且内容都很少,每列真实占用字节数用1个字节可以表示,加起来就是3
个字节,第二条记录变长字段长度列表部分占用2
字节。
当然,并不是所有记录都有这个「变长字段长度列表」部分,比方说表中「所有的列都不是变长的数据类型」或者 「所有列的值都是NULL
」 的话,这一部分就不需要有。实际业务开发中,几乎没有不使用varchar
的,所以实际开发中的记录都会有「变长字段长度列表」部分