CHAR 与 VARCHAR 的选择建议

CHAR 与 VARCHAR 的选择建议

基本区别

-- CHAR:定长字符串
CREATE TABLE t1 (col CHAR(10));

-- VARCHAR:变长字符串
CREATE TABLE t2 (col VARCHAR(10));

存储方式

特性 CHAR VARCHAR
长度 固定 可变
存储空间 总是分配最大长度 实际长度 + 1-2 字节长度前缀
存储单位 字符 字符 + 额外字节
尾部空格 自动填充/截断 保留(MySQL 8.0 之前保留,之后取决于 sql_mode)
最大长度 255 字符 65535 字节

实际存储对比

-- 存储 'ab' 在 CHAR(10) 中
-- 实际存储:'ab        '(占 10 个字符)
-- 读取时自动去掉尾部空格

-- 存储 'ab' 在 VARCHAR(10) 中
-- 实际存储:2(长度前缀)+ 'ab'(占 3 个字节)
-- 读取时原样返回

性能对比

存储空间

CHAR(10) 存 'ab':占用 10 个字符的空间,剩余 8 个空格浪费了
VARCHAR(10) 存 'ab':占用 2 个字符 + 1 个字节长度前缀

VARCHAR 在大部分情况下节省空间
但 VARCHAR 的长度前缀本身也占用空间(1 字节或 2 字节)

查询性能

CHAR 的优势

-- 1. 行长度固定,计算偏移更方便
--    InnoDB 处理定长行时,可以通过偏移量直接定位

-- 2. 页内行数更可预测
--    如果所有列都是定长类型,一页能放的行数是固定的

-- 3. 更新不改变行大小
--    CHAR 列更新时不会导致页分裂(因为长度不变)

VARCHAR 的优势

-- 1. 大多数情况下节省空间
--    更小的数据占用 → 更多行在 buffer pool
--    → 更高的缓存命中率

-- 2. 表总大小更小
--    备份更快、索引更小

如何选择

优先选择 VARCHAR 的场景

-- 1. 字段长度差异很大(如地址、描述)
VARCHAR(500)  -- 大多数地址 20-100 字,少数才 500

-- 2. 字段最大长度远大于平均长度
VARCHAR(100)  -- 名字平均 3-5 个字符

-- 3. 字段经常被更新
--    InnoDB 的 VARCHAR 更新可能会造成行迁移
--    (行大小变化导致原页面放不下)

-- 4. 经常创建索引
--    VARCHAR 索引更小,占用的磁盘和内存更少

-- 5. 多语言支持(UTF-8 字符)
VARCHAR(255)  -- 在 UTF-8 下最多 765 字节

优先选择 CHAR 的场景

-- 1. 固定长度的标识符
CHAR(32)      -- UUID、MD5
CHAR(11)      -- 手机号(中国大陆 11 位)
CHAR(18)      -- 身份证号(统一用 18 位)

-- 2. 非常短的字符串(几乎不浪费空间)
CHAR(1)       -- 性别、状态:M/F, Y/N
CHAR(2)       -- 省份代码:BJ, SH, GZ

-- 3. 长度高度统一的字段
CHAR(6)       -- 邮政编码
CHAR(4)       -- 验证码

-- 4. 需要存储固定格式便于比较
CHAR(32)      -- MD5值固定32位,存CHAR(32)查询效率更高

核心经验法则

如果字段长度变化大 → VARCHAR
如果字段长度固定且很短(< 20) → CHAR
如果字段长度固定且很长(> 100) → VARCHAR(CHAR会浪费太多空间)

实际性能测试对比

假设一个用户表:

-- 方案 A:用 CHAR
CREATE TABLE user_char (
    id INT,
    name CHAR(20),       -- 平均 6 字 × 3字节 = 18 字节
    phone CHAR(11),      -- 11 字节
    email CHAR(100),     -- 平均 20 × 3 = 60 字节
    address CHAR(200)    -- 平均 30 × 3 = 90 字节
);
-- 单行固定:4 + 20 + 11 + 100 + 200 = 335 字节

-- 方案 B:用 VARCHAR
CREATE TABLE user_varchar (
    id INT,
    name VARCHAR(20),     -- 平均 18 + 1 = 19
    phone VARCHAR(11),    -- 11 + 1 = 12
    email VARCHAR(100),   -- 60 + 1 = 61
    address VARCHAR(200)  -- 90 + 1 = 91
);
-- 单行平均:4 + 19 + 12 + 61 + 91 = 187 字节
-- 节省约 44% 空间

在 buffer pool 大小为 1GB 时:
– CHAR:可缓存 1G / 335 = 约 320 万行
– VARCHAR:可缓存 1G / 187 = 约 570 万行

VARCHAR 在此时有 78% 的缓存优势

常见误区

误区 1:VARCHAR(10) 和 VARCHAR(255) 存储相同的值占用一样空间

不对。虽然 ‘ab’ 在 VARCHAR(10) 和 VARCHAR(255) 中都只占 3 字节(2 + 1),但:
– VARCHAR(255) 的长度前缀为 1 字节
– VARCHAR(65535) 的长度前缀为 2 字节
– 更大的定义长度可能会影响内存中的临时表分配
– InnoDB 的行格式中,可变列的偏移列表定义可能更复杂

建议:VARCHAR 的长度应根据实际需求设定,不要随意给 255。

误区 2:CHAR 一定会更快

在现代 MySQL + InnoDB 下,CHAR 的”定长优势”已经不明显了。由于:
1. InnoDB 的页面管理机制
2. 变长行格式(COMPRESSED/DYNAMIC)
3. buffer pool 缓存机制

大多数场景下 VARCHAR 整体性能更好。

误区 3:CHAR 一定比 VARCHAR 更省空间

完全错误。对于同样存 ‘ab’:
– CHAR(10):10 字符(填充空格后)
– VARCHAR(10):2 字符 + 1 字节前缀

面试要点

  • CHAR 和 VARCHAR 的根本区别是”定长” vs “变长”
  • 选择的核心依据:字段长度是否统一 + 字段长度大小
  • VARCHAR 是 90% 场景的默认选择
  • CHAR 只在长度固定且短的字段(如状态码、定长 ID)中有优势
  • VARCHAR 定义长度不要给太大,根据实际需求设置
  • 理解存储引擎如何处理两者的底层存储差异
© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容