必须用CHAR的场景是:字段长度严格固定、字符集下字节数可控、高频等值查询或作索引前导列,且不依赖尾部空格;否则VARCHAR更安全高效,因其按需存储、保留空格、避免填充浪费,并在InnoDB中支持溢出优化。

短且长度恒定的字段用 CHAR,变化大或普遍偏短的字段用 VARCHAR——选错类型在千万级表里可能多占 30% 存储空间,且拖慢索引定位速度。
什么时候必须用 CHAR?
不是“能用就用”,而是满足以下全部条件时才值得考虑:CHAR 的固定长度优势才能真正兑现:
- 字段长度严格固定(如
CHAR(2)存国家代码'CN'、'US',绝不会是'USA') - 字符集下最大字节数可控(
utf8mb4下CHAR(2)最多占 8 字节,不会溢出行限制) - 该列高频参与等值查询或作为联合索引前导列(
WHERE status = 'A'),此时CHAR的地址计算更直接 - 不依赖尾部空格语义(默认模式下检索会自动 trim,
PAD_CHAR_TO_FULL_LENGTH开启后才保留)
反例:CHAR(32) 存 MD5 值看似合理,但如果业务中存在部分旧数据只存了 16 位或带中间空格,后续比对逻辑极易出错。
为什么 VARCHAR 在大多数场景更安全?
VARCHAR 的可变性不是妥协,而是对现实数据分布的适配。关键点在于它的存储开销和行为可预测:
- 长度前缀只占 1 字节(≤255 字符)或 2 字节(>255),远小于
CHAR的填充浪费 - 插入
'abc'到VARCHAR(100),实际只写 4 字节(1 字节长度 + 3 字节内容),不像CHAR(100)强制写满 100 字符空间 - 支持尾部空格原样保留(
INSERT INTO t(v) VALUES('a '),查出来仍是'a ') - 在
innodb的DYNAMIC行格式下,超长值自动溢出到独立页,避免单行膨胀影响缓冲池效率
注意:VARCHAR(65535) 不等于“随便设”,它受整行最大 65535 字节限制,且实际可用长度还要减去其他列、隐式字段(如 trx_id、roll_ptr)占用的空间。
TEXT 类型不能替代 VARCHAR 来“省事”
看到字段可能很长,就直接上 TEXT?这是典型误用。它带来三重代价:
- 无法设置默认值(MySQL 8.0.19+ 对
TINYTEXT/TEXT放宽,但仍有约束) - 索引只能建前缀(
INDEX(content(100))),且前缀长度超过 1000 字符时,排序/分组性能断崖下跌 - 查询需二次 I/O:先读主记录拿到 20 字节指针,再跳转到溢出页取真实内容,
SELECT content FROM t WHERE id = 123比同长度VARCHAR多一次磁盘寻道
正确做法:若字段平均长度 VARCHAR(1000);仅当明确需要存 >1MB 文本(如日志原始内容、HTML 片段)且极少全量读取时,才考虑 MEDIUMTEXT。
容易被忽略的字符集放大效应
很多人只看 CHAR(10) 或 VARCHAR(10) 的“10”,却忘了 utf8mb4 下一个汉字占 4 字节。这意味着:
-
CHAR(10)在utf8mb4下最多占 40 字节,而VARCHAR(10)存'你好'只占 1(前缀)+ 8 = 9 字节 - 同一张表混用
latin1和utf8mb4字段时,CHAR的“固定”反而成了陷阱——因为字符集不同,相同CHAR(N)占用字节数差异巨大,导致行大小估算失准 - 建表时显式声明字符集,比如
name VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs,避免继承库级默认值带来的隐式转换开销
真正影响存储效率的从来不是“用不用可变长”,而是你是否清楚每个字符在当前字符集下实际占多少字节、以及这些字节如何被引擎组织进页结构里。


















