CHAR 和 VARCHAR 选择建议
基础区别
| 特性 | CHAR | VARCHAR |
|---|---|---|
| 存储方式 | 定长 | 变长 |
| 最大长度 | 255 字符 | 65535 字节(受行大小限制) |
| 存储空间 | 固定分配,不足补空格 | 实际长度 + 1~2 字节长度前缀 |
| 检索性能 | 快(固定偏移) | 稍慢(需要读取长度前缀) |
| 尾部空格 | 插入时截断 | 保留 |
| 比较时空格 | 忽略尾部空格 | 忽略尾部空格(SQL 标准) |
存储与空间的详细对比
CHAR 存储
CREATE TABLE test (name CHAR(10));
INSERT INTO test VALUES ('abc');
-- 实际占用:10 个字节('abc' + 7 个空格补全)
-- 但比较时会忽略尾部空格,所以 'abc' = 'abc '
注意:MySQL 从磁盘读取 CHAR(10) 时,只需要从固定偏移量读取固定长度的数据,计算简单。
VARCHAR 存储
CREATE TABLE test (name VARCHAR(10));
INSERT INTO test VALUES ('abc');
-- 实际占用:4 个字节(1 字节长度前缀 + 'abc' 3 个字符的字节数)
- 长度前缀占 1 字节(长度 ≤ 255 时)或 2 字节(长度 > 255 时)
- 实际存储的内容不包含尾部空格补全
如何选择
选 CHAR 的场景
1. 固定长度的标识符
-- 国家代码:固定 2 个字母
country_code CHAR(2)
-- MD5 哈希值:固定 32 个十六进制字符
password_hash CHAR(32)
-- UUID:固定 36 个字符
uuid CHAR(36)
-- 手机号(中国):固定 11 位数字
phone CHAR(11)
2. 存储长度变化很小的短字段
-- 性别:M/F 或 UNKNOWN
gender CHAR(1)
-- Y/N 标记
is_active CHAR(1)
-- 状态码(类似枚举)
status CHAR(4) -- PAID,PEND,DONE...
3. 需要频繁更新的字段
CHAR 是定长存储,更新时不会导致行迁移(row migration),而 VARCHAR 更新为更长的值时可能需要页分裂。
选 VARCHAR 的场景
1. 可变长度的描述性字段
-- 用户名:长度差异大
username VARCHAR(50)
-- 邮箱:长度差异大
email VARCHAR(255)
-- 地址:长度不确定
address VARCHAR(200)
2. 大部分数据长度远小于最大长度
-- 评论内容:最长 1000 字,但大部分只有几十个字
comment TEXT 或 VARCHAR(1000)
如果用 CHAR(1000),大部分行会浪费 900+ 字节的存储空间。
3. 字符串长度超过 255
CHAR 最大只支持 255 个字符。更长的字段只能用 VARCHAR 或 TEXT。
对照表
| 字段示例 | 推荐类型 | 原因 |
|---|---|---|
| 国家代码 (2) | CHAR(2) | 固定长度 |
| 手机号 (11) | CHAR(11) | 固定长度 |
| MD5 值 (32) | CHAR(32) | 固定长度 |
| ISBN 号 (13) | CHAR(13) | 固定长度 |
| IP 地址 | VARCHAR(15) | 变长(IPv4) |
| 邮箱 | VARCHAR(255) | 变长 |
| 用户名 | VARCHAR(50) | 变长 |
| 文章标题 | VARCHAR(200) | 变长 |
| 身份证号 (18) | CHAR(18) | 固定长度 |
| 银行卡号 | VARCHAR(30) | 变长(不同银行不同) |
InnoDB 的特殊行为
行溢出
InnoDB 的数据页大小默认 16KB,一行数据不能超过半个页(约 8KB)。当 VARCHAR 长度较大时:
– 短字符串:直接存储在行的数据页中(行内存储)
– 长字符串(通常超过 768 字节):存储在溢出页(off-page),数据页中只保留 768 字节前缀和指针
-- 定义 VARCHAR(1000),InnoDB 不会在每行都预留 1000 字节
-- 而是在必要时使用溢出页
description VARCHAR(1000)
这意味着 VARCHAR(1000) 并不一定比 VARCHAR(100) 占用更多行内空间。
性能考虑
行大小与页密度
-- 如果大部分行的 name 是 5~10 字符
-- CHAR(100) 每行浪费 90+ 字节
-- 一页(16KB)能存放的行数减少 → 更多 I/O
-- VARCHAR(100) 按实际长度存储
-- 一页能存放更多的行 → 更高效的缓存和查询
排序性能
CHAR 的排序比 VARCHAR 稍快,因为长度固定,计算偏移量简单。但在实际应用中,差异非常小,通常可以忽略。
MySQL 8.0 的变化
MySQL 8.0 对字符集的处理有了变化:
– 默认字符集从 latin1 改为 utf8mb4
– utf8mb4 中一个字符最多占 4 字节
– VARCHAR(255) 在 utf8mb4 下最大占用 1020 字节 + 2 字节长度前缀
这意味在 utf8mb4 下,VARCHAR(255) 已经接近 InnoDB 的 768 字节行内限制,需要注意溢出页问题。
总结
在选择 CHAR 和 VARCHAR 时的核心原则:
- 先看长度是否固定:固定长度用 CHAR,变化大用 VARCHAR
- 再看最大长度:超过 255 只能用 VARCHAR 或 TEXT
- 结合业务场景:短标识符用 CHAR,描述性文本用 VARCHAR
- 考虑存储效率:大部分短而少数长时,CHAR 会导致空间浪费
- 考虑更新频率:高频更新 CHAR 优于 VARCHAR(避免行迁移)
总之:不确定时默认用 VARCHAR。只有在明确字段长度固定、且长度较短(< 50)且频繁更新时,CHAR 才是更好的选择。


暂无评论内容