根据我多年的经验,VARCHAR的最大长度和字符串类型的选择,用MySQL的七八个人都不清楚。网上的文章都是误导性的,而且大部分都是谣言。本文不仅介绍了原理,还提供了一个案例教大家自己分析,彻底解决大家的疑惑。假设有一个VARCHAR(64)CHARSETutf8mb4列,它存储的是Chinacn的字符串。那你猜猜,MySQL用了多少字节来存储?A:4BytesB:5BytesC:8BytesD:9BytesE:10BytesF:10.125BytesG:11BytesH:12BytesI:12.125BytesK:13Bytes正确答案是F和G,如果没猜对,可以解花7-10分钟阅读本文,解开这个谜题。快乐成长很容易。文章目录VARCHAR定义VARCHAR最大长度最大行大小可为空的列标识符字符集单个字符最大字节数VARCHAR长度标识符样本是一个可变长度的字符串。考虑到变长原理中的元素比较多,具体分解前需要先回顾一下官方的定义。为了便于理解,我使用CHAR定长类型进行对比介绍。先看两个小例子:VARCHAR(4),最多存4个字符,存多少个字符。存储的字节数=数据值的字节数+1字节(长度标识,后面会讲到)CHAR(4),最多可以存储4个字符,小于则尾部补空格4、存储字节数=数据值字节数+填充空格数。一般来说,VARCHAR和CHAR都是MySQL字符串类型,存储多个字符,可以设置最大存储字符数。存储开销与数据长度有关,字符集有关。是MySQL最常用的字符串类型。CHAR和VARCHAR的具体比较:如果启用PAD_CHAR_TO_FULL_LENGTH模式,则搜索时不会去除尾随空格。超过255个字符的CHAR会报错,提示使用TEXT或BLOB:ERROR1074(42000):Columnlengthtoobigforcolumn''long_char''(max=255);最大长度使用BLOB或TEXT而不是VARCHAR在MySQL的官方定义中。在常用的COMPACT和DYNAMIC行模式下,最大长度受几个因素的影响:除了行中存储的最大字节数之外的存储开销,官方的定义包括:NULL标识符、长度标识符、字符集算法存储字符为:最大长度(字符数)=(一行中存储的最大字节数-一个NULL标识列占用的字节数-一个长度标识占用的字节数)/字符集单个的最大值以字节为单位的字符数。有余数时向下舍入。下面通过实例验证逐步演示如何计算最大长度。最大行大小MySQL行默认最大为65535字节,所有列共享,所以VARCHAR的最大值受此限制。接下来,我们需要创建一个65536字节的VARCHAR来验证这个边界值。如前所述,VARCHAR语句的长度是指字符数。要转换为65536字节,一个字符最好只占一个字节。所以这里使用latin1字符集(MySQL默认字符集,不指定默认)。mysql>创建表test_varchar_length(vvarchar(65536)不为空);错误1074(42000):对于列“v”而言,列长度太大(最大值=65535);useBLOBorTEXTinstead,wecanseetheerror报错,并提示我们做最大长度为65535字节。如果我们要插入一个非空的VARCHAR,它的最大长度不能超过65535(行最大值)-2(长度标志)=65533字节(长度标志需要2个字节来表示2^16=65536个数字):/**测试边界值为65534,确认还是偏大;注意这里使用默认字符集latin1和单字节字符集*/mysql>createtabletest_varchar_length(vvarchar(65534)notnull);错误1118(42000):行大小太大。使用的表类型的最大行大小(不包括BLOB)为65535。这包括存储开销,请查看手册。你得把一些列改成TEXT或者BLOBs/**testboundaryvalue65533,createSuccess,说明行的最大值是65535*/mysql>createtabletest_varchar_length(vvarchar(65533)notnull);QueryOK,0rowsaffected(0.02sec)/**查看默认字符集,确认是latin1,每个字符Onlytakes1byte*/mysql>showcreatetabletest_varchar_length;+------------------------+----------------------------------------------------------------------------------------------------------+|表|创建表||+--------------------+----------------------------------------------------------------------------------------------------------+|test_varchar_length|CREATETABLE`test_varchar_length`(`v`varchar(65533)NOTNULL)ENGINE=InnoDBDEFAULTCHARSET=latin1|+------------------------+----------------------------------------------------------------------------------------------------+1rowinset(0.00sec)Nullablecolumnflag在COMPACT和DYNAMIC行格式中,行大小不仅包括数据列的长度,还包括可空列标志,即空标志。如果一列允许为空,则需要1位来标识,每个8位标识将组成一个字段,存储在每一行的开头。注意这个标识位不是放在每一列中,而是每一行共享的。假设一个表中有N个可为空的字段,NULL标志需要?N/8?(向上舍入)字节。此时整行可用于存储数据的空间只有65535??N/8?字节。话不多说,一起来验证一下:在行大小的例子中,我们知道可以创建一个最大长度为65533字节的非空VARCHAR列。现在要创建一个可空列,每行需要1位NULL标志,MySQL会把它组装成一个1字节的字段存储,那么我们应该最多可以创建65533(最大非空VARCHAR列)-1(NULLflagcolumn)\=65532bytesofnullableVARCHARcolumn:/**Deletethetablecreatedearlier*/mysql>droptabletest_varchar_length;QueryOK,0rowsaffected(0.01sec)/**测试边界值为65533,并且确认仍然通过Large;注意这里使用默认字符集latin1和单字节字符集*/mysql>createtabletest_varchar_length(vvarchar(65533));ERROR1118(42000):Rowsizetoolarge。使用的表类型的最大行大小(不包括BLOB)为65535。这包括存储开销,请查看手册。你得把一些列改成TEXT或者BLOBs/**测试边界值为65532,创建成功,说明可空标识列确实占了1个字节;注意这里使用默认字符集latin1和单字节字符集*/mysql>createtabletest_varchar_length(vvarchar(65532));QueryOK,0rowsaffected(0.03sec)计算VARCHAR的最大长度,并且可空标志是最容易忽略的。字符集中单个字符的最大字节数字符集中单个字符的最大字节数不难理解。列出MySQL中三种常见的字符集:GBK:单个字符最多占用2个字节。UTF8:单个字符最多占用3个字节。UTF8MB4:单个字符最多占用4个字节。假设还有6个字节存储字符,根据单个字符最大占用字节数,可以存储3个GBK,2个UTF8,1个UTF8MB4。VARCHAR的长度标识符比较复杂,网上的介绍错误很多,很容易出错。它的作用是记录数据的字节数。如果小于255,则存储开销仅为1个字节,如果大于255,则存储开销为两个字节。是因为根据可能的数据大小,分为0-255(28)、256-65535(216),刚好对应1字节和2字节。但需要注意的是,计算是根据字段声明的字符长度,计算可能的字节数,然后确定长度标志的字节数。例如VARCHAR(100),字符集为UTF8,可能的字节数为300,长度标识为2字节。这是网上最错误的介绍。另外,长度标志是底层存储开销,不占用字段声明的字符长度。声明的字符长度是数据的字符数,数据的字节数与字符集有关。以VARCHAR(1)为例,可以存放1个字符,MySQL会多找一个字节存放长度标识。示例公式应该理解为:VARCHAR的最大长度=(最大行大小-NULL标识列占用的字节数-长度标识字节数)/字符集中单个字符的最大字节数.有余数时向下舍入。接下来通过实验进行验证。为了便于理解和计算,例子中做了一些调整:没有设置可为空的列,这样可以去掉NULL标志列。为了体现长度标志的差距,使用了多个列来扩大它的存在,以反映根据可能的字节数计算长度,这里使用多字节字符集GBK建表,其中包含2个非空VARCHAR(127),每列的存储开销为127*2(最大可能的字节数,GBK字符占用2个字节)+length标识位1\=255字节:剩余空间为65535-255*2=65025字节剩余空间可以存放一个VARCHAR(32511)NOTNULL列(32511*2(GBK字符占2字节)+2(lengthidentification2bytes)=65024)mysql>droptabletest_varchar_length;QueryOK,0rowsaffected(0.01sec)/**测试边界值为32512,确认还是偏大*/mysql>createtabletest_varchar_length(v1varchar(127)notnull,v2varchar(127)notnull,vmvarchar(32512)不为空)CHARSET=GBK;错误1118(42000):行大小太大。所用表类型的最大行大小(不包括BLOB)为65535。这包括存储开销,请查看手册。你得把一些列改成TEXT或者BLOBs/**testboundary值为32511,创建成功,说明两个长度标识位一共占用2个字节*/mysql>createtabletest_varchar_length(v1varchar(127)notnull,v2varchar(127)notnull,vmvarchar(32511)notnull)CHARSET=GBK;QueryOK,0rowsaffected(0.02sec)接下来,将两个字段增加到128个字符,每列存储为最大字节数256+长度标识位2=258字节剩余空间65535-258*2=65019字节剩余空间可以存储一个VARCHAR(32508)NOTNULL列(32508*2(GBK字符占2字节)+2(长度标识2bytes)=65018):mysql>droptabletest_varchar_length;QueryOK,0rowsaffected(0.01sec)/**测试边界值为32509,确认还是偏大*/mysql>createtabletest_varchar_length(v1varchar(128)notnull,v2varchar(128)notnull,vmvarchar(32509)不为空)CHARSET=GBK;错误1118(42000):行大小太大。使用的表类型的最大行大小(不包括BLOB)为65535。这包括存储开销,请查看手册。你得把一些列改成TEXT或者BLOBs/**testboundary值为32508,创建成功,说明两个长度标识位一共占用4个字节*/mysql>createtabletest_varchar_length(v1varchar(128)notnull,v2varchar(128)notnull,vmvarchar(32508)notnull)CHARSET=GBK;QueryOK,0rowsaffected(0.02sec)恭喜,能看到这个的人估计不多,坚持了就升职了那么我们一起来解决原题:UTF8MB4字符中,汉字需要3个字节(大部分中文只需要3个字节,4个字节主要是emoji等辅助平面字符),那么“Chinacn”需要3+3+1+1总共8个字节的VARCHAR(64)CHARSETutf8mb4字段,数据的最大可能字节数为64*4=256,所以需要2个字节作为长度标识位;该字段可以为空,则还需要NULL标识位,MySQL会生成一个1字节的NULL标识列来记录;所以要存储“Chinacn”,该列需要8+2个字节,需要1个字节作为NULL标识列;因为此列由多个列共享。如果表只有一个字段,存储开销应该是11字节,否则只能算10.125字节(1/8等于0.125)所以答案是10.125或11字节。
