CHAR固定长度,写入即全额分配n字符空间并空格填充;VARCHAR动态存储,仅占实际内容字节数加1–2字节长度头;TEXT始终外存,通过指针访问,带来额外I/O开销。

CHAR固定长度导致空间刚性分配
MySQL对CHAR(n)字段在写入时就按n个字符全额分配空间,不管实际内容多短。比如CHAR(20)存"a",也会占满20字符位置——在utf8mb4下就是80字节(每个字符最多4字节),且用空格补足。这种“一刀切”分配让InnoDB页内布局高度可预测,但代价是大量空格填充带来的磁盘和内存浪费。
常见错误现象:表里CHAR(100)字段平均只存12个字符,结果单行体积膨胀8倍,索引缓存命中率骤降,SELECT COUNT(*)变慢明显。
- 启用
pad_char_to_full_length模式(MySQL 8.0+)后,SELECT返回值末尾空格不再被自动trim,应用层字符串比较可能意外失败 - CHAR字段无法利用前缀索引(如
INDEX(col(10))),因为长度固定,优化器不认为有必要截断 - 当字段频繁UPDATE且新旧值长度差异大时,CHAR不会释放或收缩空间,容易加剧页分裂
VARCHAR动态存储依赖长度头与行格式
VARCHAR(n)实际占用 = 实际字符字节数 + 长度头(1或2字节)。这个“头”不是额外元数据,而是紧贴数据前缀的二进制字节:长度≤255时用1字节,否则用2字节。例如VARCHAR(500)存"hello"(5字符),在utf8mb4下占5×4 + 1 = 21字节;但存一个emoji(如"?")就要算4字节×1 + 1 = 5字节。
关键影响在InnoDB行格式:COMPACT和REDUNDANT把长度头和数据放一起;DYNAMIC或COMPRESSED则对超长值(通常>256字节)触发溢出页,行内只留20字节指针——这直接改变I/O路径。
- 不要假设
VARCHAR(65535)真能存65535字符:受单行最大65535字节限制,还要扣除其它字段、事务ID、回滚指针等开销 -
VARCHAR字段加索引时,若定义长度过大(如VARCHAR(2000)),即使只查前10个字符,索引B+树节点仍按2000算,大幅增加内存和磁盘索引体积 - ALTER TABLE修改
VARCHAR长度可能触发全表重建(尤其从短变长),而CHAR改长度几乎总是in-place
空格处理差异直接影响业务逻辑
CHAR写入时右侧空格被填充,读取时默认被截断(SQL模式TRADITIONAL或STRICT_TRANS_TABLES下行为一致);VARCHAR则原样保留所有空格——包括开头、中间、结尾。这意味着WHERE name = 'abc '在CHAR字段上会匹配'abc',但在VARCHAR上必须精确匹配带空格的值。
典型踩坑场景:
- 用户注册时输入
"admin "(带空格),存入VARCHAR后登录验证失败,因为比对时没trim - 导出CSV再导入,空格被Excel自动清理,
VARCHAR字段值变更而CHAR字段因填充机制反而“稳定” -
GROUP BY或DISTINCT对CHAR字段自动去空格归并,但对VARCHAR则严格区分"x"和"x "
TEXT类型绕过行内存储带来I/O隐性成本
TEXT系列(TINYTEXT/TEXT/MEDIUMTEXT/LONGTEXT)和VARCHAR根本不是同一套存储逻辑:前者永远不存于主记录行内,哪怕只存1个字符,也通过20字节指针跳转到溢出页。这意味着每次SELECT *或哪怕只查SELECT content FROM t WHERE id=1,都至少触发两次I/O——一次读主行,一次读溢出页。
很多人误以为VARCHAR(10000)和TEXT性能差不多,其实不然:
-
VARCHAR小值(≤~7000字节)仍在行内,避免了指针跳转;超过才溢出 -
TEXT字段无法创建全文索引以外的任何索引(不能做INDEX(content)),而VARCHAR可以 - 复制延迟场景下,
TEXT字段变更会放大binlog体积(因为溢出页地址变化也可能触发重写)


















