问题
CHAR和VARCHAR的区别?
答案
1. 核心区别
| 特性 | CHAR | VARCHAR |
|---|---|---|
| 存储方式 | 定长,不足部分用空格填充 | 变长,按实际长度存储 |
| 长度范围 | 0-255字符 | 0-65535字节 |
| 存储开销 | 固定空间 | 实际内容 + 1-2字节长度前缀 |
| 尾部空格处理 | 检索时自动删除尾部空格 | 保留尾部空格 |
| 性能 | 读取快(固定位置) | 灵活节省空间 |
| 适用场景 | 长度固定的数据 | 长度变化的数据 |
2. 存储方式详解
2.1 CHAR存储机制
定义:CHAR(10)
存储 'abc':
实际存储:'abc ' (补7个空格,总共10字符)
存储空间:10字节(单字节字符集)
存储 'hello world'(超过10字符):
实际存储:'hello worl' (截断到10字符,MySQL会警告)
存储结构:
[固定长度数据(N字节)]
2.2 VARCHAR存储机制
定义:VARCHAR(10)
存储 'abc':
实际存储:[3]['abc']
存储空间:1字节(长度) + 3字节(内容) = 4字节
存储 'hello world'(超过10字符):
实际存储:截断或报错(取决于SQL模式)
存储结构:
长度 ≤ 255字节:[1字节长度前缀][实际数据]
长度 > 255字节:[2字节长度前缀][实际数据]
3. 存储空间对比
3.1 实际占用空间计算
假设字符集为 utf8mb4(每字符最多4字节):
-- CHAR(10)
'abc' → 10字符 = 40字节(固定)
'hello' → 10字符 = 40字节(固定)
-- VARCHAR(10)
'abc' → 1字节(长度) + 3字符 = 1 + 12 = 13字节
'hello' → 1字节(长度) + 5字符 = 1 + 20 = 21字节
3.2 空间对比表(utf8mb4字符集)
| 实际内容 | CHAR(10) | VARCHAR(10) | 空间节省 |
|---|---|---|---|
| ‘a’ | 40字节 | 5字节 | 87.5% |
| ‘hello’ | 40字节 | 21字节 | 47.5% |
| ‘helloworld’ | 40字节 | 41字节 | -2.5% |
结论:内容越短,VARCHAR节省空间越多。
4. 尾部空格处理
4.1 CHAR的空格处理
CREATE TABLE test_char (col CHAR(10));
INSERT INTO test_char VALUES ('abc '); -- 插入时有尾部空格
SELECT col, LENGTH(col), CHAR_LENGTH(col) FROM test_char;
-- 结果:'abc', 3, 3
-- 尾部空格被自动删除!
SELECT * FROM test_char WHERE col = 'abc';
-- 匹配成功(忽略尾部空格)
SELECT * FROM test_char WHERE col = 'abc ';
-- 也匹配成功(忽略尾部空格)
4.2 VARCHAR的空格处理
CREATE TABLE test_varchar (col VARCHAR(10));
INSERT INTO test_varchar VALUES ('abc '); -- 插入时有尾部空格
SELECT col, LENGTH(col), CHAR_LENGTH(col) FROM test_varchar;
-- 结果:'abc ', 8, 3
-- 尾部空格被保留!
SELECT * FROM test_varchar WHERE col = 'abc';
-- 不匹配(尾部空格不同)
SELECT * FROM test_varchar WHERE col = 'abc ';
-- 匹配成功
注意:比较时 = 会忽略尾部空格,但 LIKE 不会。
5. 性能对比
5.1 读取性能
CHAR优势:
行记录结构:
[字段1(CHAR 10)][字段2(INT)][字段3(CHAR 20)]
↓ ↓ ↓
偏移0 偏移40 偏移44
读取字段3:直接跳到偏移44,读取20字节
VARCHAR劣势:
行记录结构:
[字段1长度+数据][字段2(INT)][字段3长度+数据]
↓ ↓ ↓
动态长度 需计算偏移 需计算偏移
读取字段3:需先读取字段1的长度,计算偏移后才能定位
结论:CHAR因为固定长度,寻址更快。
5.2 更新性能
CHAR:
- 原地更新(In-Place Update),性能较好
VARCHAR:
- 长度变化时可能导致行记录迁移(页分裂)
- 频繁更新可能产生碎片
5.3 基准测试示例
-- 测试表
CREATE TABLE bench_char (
id INT AUTO_INCREMENT PRIMARY KEY,
code CHAR(10),
name VARCHAR(100)
);
CREATE TABLE bench_varchar (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(10),
name VARCHAR(100)
);
-- 插入100万行数据
-- CHAR表:扫描速度略快 5-10%
-- VARCHAR表:存储空间节省 30-50%(取决于数据长度分布)
6. 适用场景
6.1 CHAR适用场景
1. 长度固定的数据
-- 身份证号(18位)
id_card CHAR(18)
-- 手机号(11位)
mobile CHAR(11)
-- 国家代码(2位)
country_code CHAR(2)
-- MD5哈希值(32位)
hash CHAR(32)
-- 状态码(固定长度)
status CHAR(1) -- 'A', 'D', 'P'
2. 频繁更新且长度不变
-- 用户状态(频繁修改)
user_status CHAR(1)
6.2 VARCHAR适用场景
1. 长度变化较大的数据
-- 用户名(1-50字符)
username VARCHAR(50)
-- 邮箱(长度不定)
email VARCHAR(255)
-- 地址(长度差异大)
address VARCHAR(500)
-- 文章摘要
summary VARCHAR(1000)
2. 平均长度远小于最大长度
-- 昵称(最大100,平均10)
nickname VARCHAR(100)
7. 选择建议
7.1 决策流程
是否长度固定?
├─ 是 → CHAR
└─ 否 → 是否平均长度接近最大长度?
├─ 是 → CHAR(性能优先)
└─ 否 → VARCHAR(空间优先)
7.2 实际推荐
-- 好的设计
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50), -- 长度变化大
mobile CHAR(11), -- 固定11位
status CHAR(1), -- 固定1位
email VARCHAR(255), -- 长度不定
address VARCHAR(500) -- 长度不定
);
-- 不好的设计
CREATE TABLE users_bad (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username CHAR(50), -- 浪费空间(大部分用户名很短)
mobile VARCHAR(11), -- 长度固定,用VARCHAR多余
email CHAR(255) -- 浪费空间
);
8. 字符集的影响
8.1 不同字符集的存储
-- latin1字符集(1字节/字符)
CHAR(10) → 10字节固定
VARCHAR(10) → 1字节长度 + 最多10字节
-- utf8字符集(最多3字节/字符)
CHAR(10) → 30字节固定(10字符 * 3字节)
VARCHAR(10) → 1字节长度 + 最多30字节
-- utf8mb4字符集(最多4字节/字符,支持emoji)
CHAR(10) → 40字节固定(10字符 * 4字节)
VARCHAR(10) → 1字节长度 + 最多40字节
注意:CHAR(N) 中的 N 是字符数,不是字节数。
9. 常见误区
误区1:CHAR一定比VARCHAR快
错误:CHAR总是更快
正确:只有在固定长度且频繁访问时,CHAR才有性能优势
现代MySQL对VARCHAR优化很好,差距很小
误区2:VARCHAR节省空间一定更好
错误:总是使用VARCHAR
正确:对于固定长度字段,CHAR避免了长度前缀开销
当平均长度接近最大长度时,CHAR更合适
误区3:CHAR不会产生碎片
错误:CHAR不会有碎片问题
正确:混合使用CHAR和VARCHAR,或者有NULL值时,仍可能产生碎片
10. 总结
CHAR特点:
- 定长存储,尾部空格填充,检索时删除
- 适合固定长度数据(手机号、状态码)
- 寻址快,适合频繁访问的固定长度字段
VARCHAR特点:
- 变长存储,节省空间
- 适合长度变化大的数据(姓名、邮箱、地址)
- 现代MySQL优化良好,性能接近CHAR
选择原则:
- 长度固定 → CHAR
- 长度变化大 → VARCHAR
- 空间敏感 → VARCHAR
- 性能敏感且固定长度 → CHAR
面试要点:能说明CHAR的定长填充机制、VARCHAR的长度前缀、尾部空格处理差异、以及各自的适用场景。
扩展阅读:对于超长文本(>65535字节),使用 TEXT 类型;对于二进制数据,使用 BINARY/VARBINARY。