关于MySQL VARCHAR的错误经验,你中了几条?
在项目的技术设计评审中,大家关于MySQL VARCHAR
字段的最佳使用,发生了一些分歧和和讨论。事后,特意针对MySQL中VARCHAR
类型的使用进行了整理,在此过程中才发现,某些曾经认为的最佳实践和经验,随着MySQL的版本迭代已经不复存在,甚至存在一些错误的“幻觉常识”。
这篇文章将MySQL的VARCHAR
使用中常见的“幻觉常识”,进行分析和整理,看看在你的经验中,有多少已经是过时的,甚至是错误的?
为什么经常设置为VARCHAR(255)?
在MySQL 4.0和之前版本中(2004年之前),VARCHAR
的最大长度是255字节。这是因为当时MySQL用一个字节来存储字符串长度信息,最多支持255字节的长度(一个字节的最大值是 2^8 – 1 = 255)。
MySQL 4.1版本(2004年)引入了一些重要的改进,包括支持多字节字符集(如 UTF-8)。因此,占用存储的字节数不仅取决于字符个数,还取决于字符的实际编码长度。
同时,将VARCHAR
的最大长度限制扩展到了65,535字节(理论最大值),但这还取决于具体的行存储方式(如行格式、字符集、其他字段等的影响)。
在实际中,VARCHAR
的长度受限于MySQL的最大行大小(通常是 65,535 字节)以及每个字段需要使用的额外字节来记录数据长度(通常1~2个字节)。
也就是说,关于VARCHAR(255)
这样的设置,起源于20多年前的MySQL 4.0及以前版本。现在多少人还是在使用MySQL 4.0及以前版本?但这样的设置习惯却被延续下来了。
VARCHAR
的最大长度是多少?
我们已经看到,MySQL 4.1及以后版本中,VARCHAR
的最大长度限制扩展到了65,535字节,这一限制来自MySQL的行格式(因为最大行大小通常是65535字节,含额外的元信息)。
但实际的最大长度还受字符编码的影响,因为字符编码会决定每个字符的字节数(例如UTF-8一个字符可能占1-4字节)。VARCHAR
字段还需要额外的1-2个字节用于存储字符串的长度信息,因此其实际存储的字符数据不能完全填满65535字节。
对于不同字符编码,VARCHAR
支持的最长字符数:
- 字符类型为GBK:每个字符最多占2字节,65535 ÷ 2 = 32766字符。
- 字符类型为UTF8:每个字符最多占3字节,65535 ÷ 3 = 21845字符。
- 字符类型为UTF8MB4:每个字符最多占4字节,65535 ÷ 4 = 16383字符。
但需要注意的是,VARCHAR
的实际最大字符数还要减掉用于存储字符串长度的额外字节(1或2字节)。
因此,当VARCHAR
最大长度定义超出255(即:超过1字节长度元数据的范围),MySQL会使用2字节来存储字符串长度。
如果严格考虑内存分配,GBK实际能存储32765个字符,UTF8实际存储最多21844个字符,UTF8MB4实际存储最多16382个字符。
如果存储的内容超出VARCHAR
的最大长度,会报如下错误:
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs。
阿里开发规范中也有相关的要求:
【强制】varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,如果存储长度大于此值,定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
阿里开发规范中的建议属于工程实施层面的建议,并不是MySQL的实际限制。这一限制的主要目的是减少行长度过大导致潜在的性能问题,同时明确建议将大数据存储的字段定义为TEXT
或BLOB
类型,且存储在表外(以引用的形式),适合更大数据量的存储需求。
VARCHAR(50)和VARCHAR(255)性能上有差别?
在InnoDB存储引擎下,VARCHAR
字段的存储方式是基于实际存储的字符串长度而非定义的最大长度,因此VARCHAR(50)
和VARCHAR(255)
的实际存储性能是基本相同的。
当内容短(小于255字节)时,其存放方式完全一致,长度信息占用的字节数和实际存储的字符集编码规则(UTF-8,UTF-8MB4,GBK等)决定最终存储空间的占用大小。
- 字符长度信息使用1字节来编码
VARCHAR
的长度,仅当最大长度(定义时的长度,如255或256)超过255时,长度信息才会使用2字节。 - 当字段从
VARCHAR(50)
扩展到VARCHAR(100)
,长度定义的变化不会触发表的重建,因为字段存储方式没有改变。但如果从VARCHAR(50)
改为VARCHAR(256)
时,就需要给字段长度信息分配2字节,这可能会导致表的重建。
既然255长度以下对InnoDB都一样,而且平时基本上也不太会使用到MYISAM存储引擎,那么是不是为了省心,就可以把255长度以下的字段的类型都设置成VARCHAR(255)
呢?
实时并不是这样的,因为还要考虑内存的因素。
尽管InnoDB
对255以下的VARCHAR
存储性能没有任何差别,但需要考虑一些其他场景,例如:
- 索引存储:如果要对
VARCHAR(255)
字段创建索引,则索引长度可能会超过MySQL的最大编码限制。 - 内存表存储问题:MySQL使用临时内存表(MEMORY引擎)来存储查询过程中的中间结果,而MEMORY表对
VARCHAR
字段的实现不支持动态长度存储,必须按照定义的最大长度分配固定内存空间。当长度相同时(都小于50个字符),VARCHAR(255)
的内存开销比VARCHAR(50)
高得多。
因此,即便255以下字段在存储的物理实现没有直接差别,但出于性能优化和内存节约的考虑,应尽量避免泛化定义所有字段为VARCHAR(255)
。
在实际设计中,字段的最大长度应基于实际需要而不是漫无边际地设置高于需要的长度。
一些基本原则:
- 如果字段长度有明确限制,例如用户名最长不超过50字符,建议定义为
VARCHAR(50)
而非VARCHAR(255)
。 - 如果字段可能存储大数据量,但长度上限是明确的(例如5000字符),建议直接定义为
TEXT
而非VARCHAR
。 - 更短的字段在定义索引、内存使用、磁盘存储、查询性能优化上有明显优势。
关于此部分,基本的结论就是:在长度够用的情况下,越短越好。
VARCHAR(50)能保存16、25、还是50个汉字?
在MySQL的VARCHAR
字段中,n
表示最多可以存储的字符数,而不是字节数。这和其他数据库系统(如SQL Server的nvarchar
和Oracle的varchar2
)有不同的表现。
由于不同数据的实现差异很容易让人在跨数据库迁移时容易产生误解,以为汉字在MySQL中的VARCHAR
长度需要打对折或除以3。但实际上这种限制不存在。
对于汉字,在MySQL的VARCHAR
定义中,字符数不会因为字符编码(如UTF-8或UTF8MB4)而减少。无论定义VARCHAR(50)
还是VARCHAR(n)
,都意味着最多可以存n
个字符(例如50个汉字),而不是字节。
当然,对于不同的字符编码(如UTF-8、UTF8MB4、GBK等),每个字符的实际存储耗用的字节数是不同的:
- UTF-8:汉字通常占3字节(有时可能是4字节)。
- UTF8MB4:汉字通常占4字节。
- GBK:汉字通常占2字节。
每个字符的字节大小影响的是存储的实际空间,而字段中定义的VARCHAR(n)
表示字符的最大数量,与字节大小无关。
另外,当统计字符数时,推荐使用CHARACTER_LENGTH
而不是LENGTH
。
CHARACTER_LENGTH
返回的是字符串的字符数,无论一个字符占几个字节。LENGTH
返回的是字符串所占的字节数,会因字符编码而不同。
假如字符串"师兄奇谈"
被存储在UTF-8编码中,字符数为4,但每个汉字占3字节。
执行以下查询:
-- 返回12(总共有4个汉字,每个汉字3字节,占用4 * 3 = 12字节)
SELECT LENGTH("师兄奇谈");
-- 返回4(总共有4个汉字,不论每个汉字多少字节,字符数就是4)
SELECT CHARACTER_LENGTH("师兄奇谈");
基于上面的分析,我们可以得知,在MySQL中使用VARCHAR
时,业务实际需要多长的字段字符数,就定义为多长,并不需要再根据字符编码进行转换。
使用2^n长度的VARCHAR
可以优化性能?
在网络上有一种说法“在数据库设计时,为了节约磁盘存储空间和内存空间,以及应用程序处理效率,建议根据字段的实际长度,选择一个近似的2^n
长度值进行对齐。比如,采用VARCHAR(8)
、VARCHAR(16)
、VARCHAR(32)
等”。这种说法是否有理论依据?
在现代数据库设计中,VARCHAR
的长度选择与“2^n
对齐”基本无关。数据库的字段存储逻辑通常以行宽为基础,而不是单个字段的长度。具体来说,数据库引擎将根据存储引擎(如InnoDB或MyISAM)以及实际数据的长度动态分配存储空间,而不是按照字段定义的最大长度进行硬性对齐。
在VARCHAR
的存储中,只有实际的字符串长度 + 元信息(1-2字节用于记录字符串长度)决定了最终的存储开销,字段长度定义为2^n
并不会显著优化性能或存储。
现代磁盘和内存管理机制的块大小(如磁盘IO操作的sector或页大小)通常远远大于单字段的存储长度(例如MySQL的默认页大小是16KB),这种情况下,单个字段的定义长度(即VARCHAR(n)
的n)是否是2^n
对存储性能和内存效率几乎没有影响。
在数据库设计时,根据实际数据长度选择字段长度,而不是强制选择2^n
或2^n-1
的值。例如,手机号的长度是11 位,字段定义建议为VARCHAR(11)
,而不是VARCHAR(16)
。
小结
最后,再来总结一下文中的几条关键点:
第一,在大多数情况下,使用VARCHAR(255)
意义并不大,从MySQL 4.1版本及以后,字段的最大长度已经不受255字节的限制了。但在需要注意的是,当字段的长度超过255时,需要给字段长度信息分配的字节数由1个字节变为2字节,这可能会导致表的重建。
第二,MySQL 4.1及以后版本中,VARCHAR
的最大长度限制扩展到了65,535字节,这一限制来自MySQL的行格式。但实际的最大长度还受字符编码的影响,因为字符编码会决定每个字符的字节数(例如UTF-8一个字符可能占1-4字节)。
第三,在InnoDB存储引擎下,VARCHAR
字段的存储方式是基于实际存储的字符串长度而非定义的最大长度,因此VARCHAR(50)
和VARCHAR(255)
的实际存储性能是基本相同的。但出于性能优化和内存节约(索引存储和内存表存储问题)的考虑,应尽量避免泛化定义所有字段为VARCHAR(255)
。
第四,在MySQL的VARCHAR
字段中,n
表示最多可以存储的字符数,而不是字节数,即VARCHAR(50)
表示可以存储50个汉字,与字符编码无关。
第五,在数据库设计时,根据实际数据长度选择字段长度,而不是强制选择2^n
或2^n-1
的值。单个字段的定义长度(即VARCHAR(n)
的n)是否是2^n
对存储性能和内存效率几乎没有影响。
现在可以对照一下,你命中了几条“无意义”甚至是错误的经验?
关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台
除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接