VARCHAR(n)按实际长度行内存储,TEXT则溢出至外部页并仅存前缀和指针;VARCHAR适合短且需索引的字段,TEXT仅用于超长内容或明确无需B+树索引的场景。

MySQL 中 VARCHAR(n) 和 TEXT 的实际存储方式差异
两者在磁盘上都按实际长度存储,但 VARCHAR 存在行内存储上限(InnoDB 默认 65535 字节总行宽),而 TEXT 类型的数据默认被移出主记录页,只在行内保留最多 768 字节前缀(压缩表为 20 字节)和指向外部页的指针。
这意味着:
-
VARCHAR(1000)存 10 个字符,只占约 11 字节(1 字节长度 + 内容);存满则占 1001 字节 -
TEXT存 10 个字符,仍需额外访问外部页(哪怕很小),带来一次潜在的随机 I/O - 当
VARCHAR定义过长(如VARCHAR(65535)),即使内容很短,也可能因行宽超限触发隐式转为TEXT或报错
什么时候该用 VARCHAR(n),什么时候必须用 TEXT
选 VARCHAR 的典型场景:字段长度可预期、常参与 WHERE/ORDER BY/GROUP BY、需要索引全文或前缀(如 INDEX(col(191)))。
选 TEXT 的刚性原因只有两个:
- 内容可能远超 65535 字节(
VARCHAR最大理论值即此,且受字符集影响,utf8mb4 下实际可用约 16383 个字符) - 明确不需要在该字段上建普通 B+ 树索引(
TEXT不支持无前缀索引,CREATE INDEX ON t(c)会报错,必须写CREATE INDEX ON t(c(255)))
注意:TINYTEXT/MEDIUMTEXT/LONGTEXT 仅影响最大长度和内部存储策略,不改变“溢出存储”这一核心行为。
性能影响最明显的三个操作
以下行为在 TEXT 上代价更高:
-
排序与临时表:若
ORDER BY text_col且无法走索引,MySQL 常将整列加载进 sort buffer 或磁盘临时表,而VARCHAR更可能被截断或优化掉 -
复制延迟:binlog 中
TEXT字段默认以完整值记录(STATEMENT 模式下尤其危险),大字段更新易拖慢从库;VARCHAR因长度可控,影响小得多 -
内存占用波动:InnoDB 缓冲池对
TEXT外部页的缓存粒度是 16KB 整页,哪怕只读取一个 100 字节的TEXT值,也可能带入整个页;VARCHAR则随行一起加载,更紧凑
容易被忽略的兼容性陷阱
看似只是类型名不同,但实际会影响很多周边行为:
- JSON 函数支持:
VARCHAR可直接传给JSON_EXTRACT(),但若字段定义为TEXT且含非 UTF8 字符,可能触发隐式转换失败 - 分区表限制:
TEXT/BLOB类型不能作为分区键,VARCHAR可以 - 在线 DDL:
ALTER TABLE ... MODIFY COLUMN c TEXT在 MySQL 5.7+ 通常是 instant,但若原字段是VARCHAR(2000)且已存大量数据,切换时仍可能触发表拷贝(因外部存储结构变更)
真正决定性能的往往不是单次查询,而是这些“不起眼”的交互点——比如一个本该走索引的 WHERE text_col = ?,因为没加前缀索引,最终变成全表扫描加逐行读外部页。











