char是定长字符串类型,存储时必用空格填充至指定长度(如char(10)存"abc"实际写入"abc "共10字符),空格物理存在且与存储引擎无关;读取时自动去除尾部空格。

CHAR固定长度,存储时必然补空格
CHAR(n)在写入时,无论实际字符串多短,都会被填充到n个字符长度。比如CHAR(10)存"abc",磁盘上真实写入的是"abc "(7个空格),总共占用10个字符空间。这个填充动作由MySQL Server层完成,与存储引擎无关。
关键点在于:这些空格是物理存在的,不是逻辑上“隐式补齐”。这意味着即使你用SELECT查出来时MySQL会自动trim尾部空格(SQL标准行为),但InnoDB页里、MYISAM数据文件里,那7个字节就是实打实的0x20。
- 如果字段定义为
CHAR(1)且值为空字符串'',仍占1字节(一个空格) - 启用
STRICT_TRANS_TABLES模式不影响填充逻辑,只影响超长截断是否报错 - 使用
utf8mb4时,每个字符最多占4字节,但CHAR(10)仍表示“最多10个字符”,不是10字节
VARCHAR变长存储,头部带长度前缀
VARCHAR(n)不补空格,但会在数据前加1或2字节长度标识。例如VARCHAR(10)存"abc",实际写入是0x03 0x61 0x62 0x63(1字节长度+3字节内容)。当字段最大长度≤255时用1字节;超过255(如VARCHAR(300))则用2字节——这个判断依据是定义长度,不是实际存入长度。
注意:这个长度前缀记录的是“字符数”,不是字节数(MySQL 5.0.3+起按字符计数,与字符集无关)。但底层B+树索引和行格式处理时,仍需按字节计算偏移,所以utf8mb4下VARCHAR(100)最坏可能占402字节(100×4 + 2)。
-
VARCHAR(65535)理论上可行,但受单行总长限制(InnoDB最大约65535字节,还得扣除其他字段和元信息) - MYISAM在
ROW_FORMAT=FIXED下会把VARCHAR也转成定长,彻底失去变长优势 - 空字符串
''存入VARCHAR(10)只占1字节(长度前缀0x00),不存任何字符
空格处理差异直接影响查询结果
CHAR字段读取时自动去除尾部空格,VARCHAR则原样返回。这意味着SELECT LENGTH(col)对同一值在两种类型上可能返回不同结果:
INSERT INTO t VALUES ('a '); -- 假设col是CHAR(2)
SELECT col, LENGTH(col) FROM t; -- 返回 'a', 1(空格被trim了)
INSERT INTO t VALUES ('a '); -- 假设col是VARCHAR(2)
SELECT col, LENGTH(col) FROM t; -- 返回 'a ', 2(空格保留)
这个行为不是ORM或客户端做的,是MySQL Server层在Item_string::val_str()阶段强制执行的。如果你依赖尾部空格做业务逻辑(比如密码盐值、base64末尾=号),必须用VARCHAR,否则会被静默丢弃。
行内存储结构决定碎片与缓存效率
InnoDB中,CHAR让每行长度固定,页内记录偏移可直接计算,减少页分裂概率;而VARCHAR因长度浮动,更新时容易引发页内碎片——比如把VARCHAR(10)从"ab"改成"abcdefghij",原位置放不下,就得搬移到页尾或新页,留下空洞。
但反过来看,CHAR在大量短字符串场景下浪费更多缓冲池(Buffer Pool)空间。例如10万行CHAR(32)存MD5值,实际只需32字节/行,但若用VARCHAR(32),平均可能省下5–8字节/行,10万行就是近1MB内存节省。
- MyISAM的
STATIC表全用定长,DYNAMIC表才支持VARCHAR变长 - 即便用
VARCHAR,如果大部分值都接近上限(比如VARCHAR(200)里90%存190+字符),其空间优势几乎消失,此时不如换CHAR - TEXT类型完全脱离行存储,不在这里讨论,但要注意它不参与行长度计算,也不受65535字节单行限制
真正难处理的不是选哪个类型,而是混合使用时的隐式转换陷阱——比如WHERE char_col = varchar_col会触发全表扫描,因为MySQL无法有效利用索引做定长/变长对齐。这种细节往往在慢查询日志里才暴露出来。











