必须用char:固定长度标识符(如国家代码char(2)、md5值char(32))、高频排序/分组的短字符串、千万级表中用于等值查询的紧凑索引字段;其余场景优先varchar。

CHAR 和 VARCHAR 不是“哪个更好”,而是“哪个更合适”——关键看字段内容长度是否稳定、是否对空格敏感、是否高频参与排序或索引查找。
什么时候必须用 CHAR?
CHAR 的优势只在极少数场景成立,用错反而拖慢性能:
- 存储固定长度标识符:如国家代码
CHAR(2)("CN"、"US")、MD5哈希值CHAR(32)、UUID短格式(去掉横线后32位) - 列值长度几乎恒定且 ≤ 255 字符,比如状态码
CHAR(3)("OK "、"ERR"、"PND"),注意尾部空格会被自动截断(除非开启pad_char_to_full_length) - 高频
ORDER BY或GROUP BY的短字符串字段,CHAR能避免每次解析长度前缀,减少 CPU 开销 - 表数据量极大(千万级以上)且该字段常用于等值查询(如
WHERE code = 'US'),CHAR索引更紧凑,B+树层级更浅
不要因为“听说 CHAR 快”就滥用。例如把用户名定义成 CHAR(50),实际平均长度才 8,等于每行浪费 42 字节 × 百万行 → 多占 42MB 内存+磁盘,还放大 buffer pool 压力。
什么时候优先选 VARCHAR?
绝大多数业务字段都该用 VARCHAR,尤其当:
- 字段长度变化明显:用户名、地址、标题、描述等
- 实际平均长度远小于定义上限(比如
VARCHAR(255)中 95% 的值 ≤ 30 字符) - 需要保留末尾空格:
VARCHAR存什么取什么;CHAR检索时默认 trim 掉尾部空格(SQL 标准行为) - 使用 utf8mb4 字符集:中文/emoji 单字符最多占 4 字节,
CHAR(10)固定分配 40 字节,而VARCHAR(10)最多只用 40 字节 + 1 字节长度标识,短内容节省显著
注意两个坑:
-
VARCHAR(255)和VARCHAR(1000)在存储相同短字符串时开销一样,但前者会让 MySQL 优化器误判内存需求,可能拒绝使用内存临时表做GROUP BY - InnoDB 行最大限制是 65535 字节(所有列总和),
VARCHAR的长度声明计入这个上限 —— 即使你只存 1 字符,VARCHAR(65535)本身就会让建表失败(还要预留 NULL 标志、长度标识等)
CHAR 的空格行为到底怎么算?
这是最容易踩的语义坑:
- 插入时:
CHAR(5)插入'a'→ 存为'a '(4 个空格填充) - 查询时:
SELECT返回结果默认 去掉尾部空格,所以SELECT code FROM t WHERE code = 'a'能命中,但SELECT code FROM t WHERE code = 'a '也能命中(因为比较前会 pad 对齐) - 比较逻辑:MySQL 按照“补齐到等长再比”的规则,所以
'a' = 'a '为 true,但'a ' = 'a '也为 true —— 这不是 bug,是 SQL 标准 - 如果业务真依赖末尾空格(极少见),必须用
VARCHAR,或全局设置sql_mode=PAD_CHAR_TO_FULL_LENGTH(MySQL 8.0+ 支持,但会影响所有CHAR列)
别靠 LENGTH() 或 TRIM() 来“修复”——根源是类型选错了。
长度设多少才算合理?
没有“安全上限”,只有“最小够用”:
- 查历史数据,统计该字段的
MAX(LENGTH()),再加 10%~20% 余量(防未来扩展) - 避免拍脑袋写
VARCHAR(255)或VARCHAR(1024):它不省空间,反而影响执行计划 - 如果字段可能超 255 字符,且多数值 > 255,考虑
TEXT(但要注意TEXT不能有默认值、索引只能前缀、排序必走磁盘临时表) -
CHAR(1)存性别('M'/'F')比TINYINT更直观,但不如ENUM('M','F')省空间(不过ENUM有维护成本)
真正伤性能的从来不是类型本身,而是“用 CHAR(20) 存 3 字符邮箱”这种空间浪费,以及“用 VARCHAR(5000) 存日志摘要”导致 optimizer 放弃内存排序。











