17370845950

VARCHAR(n) 与 TEXT 在 MySQL 中的性能与存储区别
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 = ?,因为没加前缀索引,最终变*表扫描加逐行读外部页。