行溢出临界点约为8126字节,与页大小无关;VARCHAR按需溢出,TEXT/BLOB默认溢出;Dynamic格式下仅存20字节指针,UTF8mb4下最多存约2031个汉字。

行溢出触发的临界点是8KB,不是16KB
很多人误以为“一页16KB,所以超过16KB才溢出”,这是错的。InnoDB对单个大字段(如 VARCHAR、TEXT、BLOB)是否溢出的判断依据是:该字段真实数据长度是否超过约 8KB(准确说是 8126 字节左右)。一旦超过,就会触发行溢出,哪怕整行加起来还远不到16KB。
这是因为 InnoDB 在页内为每条记录预留了最小空间开销(记录头、事务ID、回滚指针、NULL列表、变长字段长度列表等),实际能留给“行内数据”的空间远小于16KB。实测表明,当一个 VARCHAR 字段存入约 8126 字节的 UTF8 数据后,后续插入就会开始将超出部分写入溢出页。
- Compact/Redundant 行格式:页内保留前
768字节,其余全放溢出页,页内用20字节存地址指针 - Dynamic/Compressed 行格式:页内 不存任何真实数据,只留
20字节指针 —— 这才是默认行为(MySQL 5.7+) - 字符集直接影响临界值:UTF8mb4 下,一个汉字占 4 字节,所以能存的字符数 ≈ 8126 ÷ 4 = 2031 个;而 ASCII 下可存约 8126 个字符
为什么不能靠增大 innodb_page_size 来避免溢出
innodb_page_size 确实控制页大小(默认 16384),但它在 MySQL 初始化后就不可更改,且即使设为 32K 或 64K,也 不会提高单字段的行内存储上限。InnoDB 的溢出逻辑是硬编码在行格式处理中的,与页大小无直接线性关系。
更关键的是:增大页大小会显著影响 Buffer Pool 利用率和随机读性能。比如一条只占 200 字节的普通记录,在 64K 页里会造成大量内部碎片;同时,一次 I/O 加载的无效数据变多,cache 命中率反而下降。
- 不要为了“塞下大字段”去调
innodb_page_size—— 它解决不了溢出问题,还会引入新瓶颈 - 真正影响溢出行为的是
ROW_FORMAT(建表时指定)和字符集,不是页大小 -
SHOW TABLE STATUS中的Avg_row_length值常严重失真,因为它把溢出数据也算进去了,不可信
TEXT/BLOB 为什么几乎必然溢出,而 VARCHAR(255) 却不会
TEXT 和 BLOB 类型从设计上就被 InnoDB 视为“潜在溢出列”。无论你只存几个字,只要该列定义存在,InnoDB 就会在记录中预留溢出指针空间,并在需要时立即启用溢出页。这不是“懒加载”,而是类型语义决定的存储策略。
反观 VARCHAR(M),它是否溢出完全取决于 实际写入长度 和当前行格式。例如 VARCHAR(255) 存英文或数字,通常仅占几十字节,远低于 768 字节阈值,全程走行内存储;但若你用它存 Base64 图片(超 10KB),就会立刻触发溢出。
-
TEXT列哪怕只存'a',也会产生溢出页(可通过innodb_monitor_output查看页分配) -
VARCHAR是“按需溢出”,TEXT/BLOB是“默认溢出”,这是二者最本质的区别 - 建表时用
ROW_FORMAT=DYNAMIC可统一行为:所有大字段都走纯指针模式,避免 Compact 下的“768 字节截断”带来的隐式截断风险
查询大字段时,到底发生了几次磁盘 I/O
查一条含溢出数据的记录,I/O 次数不是固定的。它取决于 Buffer Pool 是否已缓存对应溢出页:
- 第一次访问:主记录页(1次) + 至少1个溢出页(1次起),可能更多(如果数据跨多个溢出页)
- 后续访问:若溢出页仍在 Buffer Pool 中,则只需 1 次 I/O(主记录页);否则仍要补读溢出页
-
SELECT *必然拉取全部溢出内容;但SELECT id, name FROM t若不包含大字段,则完全不触碰溢出页
这也是为什么带 TEXT 字段的表,即使只查主键,EXPLAIN 显示的 rows 很小,实际执行却慢——因为优化器不知道溢出页的存在,无法预估 I/O 成本。真正容易被忽略的点在于:溢出页没有索引,也无法被 Buffer Pool 的 LRU 链表高效保护;它们更容易被淘汰,导致反复物理读。


















