InnoDB中char与varchar查询性能基本无差别,因行定位依赖record header而非固定长度;选型应聚焦字段变更频率、平均行长及业务语义是否定长。

char 查询快是理论成立,但 InnoDB 里基本没差别
在 MyISAM 或 MEMORY 引擎中,char 因为定长,每行起始位置可直接通过“行号 × 行长度”算出,跳转和扫描确实更快;但在 InnoDB 中,所有行都带变长头(record header),实际存储时无论 char 还是 varchar,都需要先读 header 才能定位字段偏移。这意味着:InnoDB 不靠“固定长度”做快速寻址,而是靠页内 slot 数组 + record header 解析。
实测千万级表、buffer pool 充足时,SELECT id FROM t WHERE name = ?(name 分别为 char(10) 和 varchar(10))的 QPS 差异通常在 ±2% 内——这个差距远小于网络抖动或查询缓存开关的影响。
varchar 长度 ≤255 时只用 1 字节存长度,但别滥用 varchar(255)
varchar 在存储时会额外用 1 或 2 字节记录真实长度:≤255 字符用 1 字节,>255 用 2 字节。看起来很省,但要注意:
- 如果定义
varchar(255)却只存 3 个字符(如手机号),虽然磁盘空间省了,但 InnoDB 的二级索引(B+ 树)节点里,该字段参与排序/比较时仍按最大可能长度预留空间(尤其在ORDER BY或GROUP BY临时表场景) - 使用
MEMORY引擎建临时表时,varchar会被强制转成等长char处理,varchar(255)就真占满 255 字节,拖慢 hash 索引构建 - 字符集影响实际字节数:utf8mb4 下一个汉字占 4 字节,
varchar(10)最多存 10 个字符,但最多占 40 字节 —— 而不是“最多 10 字节”
char 末尾空格会被自动 trim,但比较行为不一致
char 类型在检索时隐式执行 RTRIM(),比如插入 'abc '(含两个空格),SELECT 出来就是 'abc';而 varchar 保留原样。这导致几个易踩坑点:
-
WHERE name = 'abc '对char列会命中'abc',对varchar则不会(除非你显式加RTRIM()) -
UNIQUE约束下,char(5)插入'a'和'a '被视为相同值(后者被 trim 成'a'),但varchar(5)会允许两者共存 - 应用层做字符串校验时,如果依赖数据库返回值判断“是否含空格”,
char会掩盖问题,上线后才发现前端输入的空格被无声吞掉
选型真正该看的不是“快不快”,而是“改不改”和“存多少”
现代 MySQL(8.0+)在 buffer pool 命中率高、SSD 随机读快的场景下,char 和 varchar 的单行查询性能差异已可忽略。真正影响线上表现的是:
- 字段变更频率:频繁
UPDATE变长字段(尤其是varchar从短变长)可能触发页分裂,而char因定长,更新只改内容不挪位置 - 平均行长:同样存 1000 万用户昵称,若 95% 是 4~8 字符,
varchar(32)比char(32)节省约 60% 的磁盘 I/O 量(页内能塞更多行) - 业务语义是否要求“绝对定长”:身份证号(
char(18))、UUID(char(36))、国标编码(char(2))这类天然定长字段,用varchar反而增加解析开销且无收益
最常被忽略的一点:alter table 修改 char 长度(如 char(10) → char(12))是 inplace 的,而 varchar 改长度可能触发 full table rebuild —— 尤其当字段已存在大量数据时,这个成本比“查询快 1 微秒”实在得多。


















