编程 ALTER TABLE 报 Row size too large 未必是你真的超了:MySQL 9.7.4 把行大小校验退回旧口径

2026-11-06 21:04:04

ALTER TABLE 报 Row size too large 未必是你真的超了:MySQL 9.7.4 把行大小校验退回旧口径

报错长什么样

ERROR 1118 (42000): Row size too large ...

有时它并不是 error,只是一条 warning,CREATE TABLE 或 ALTER TABLE 仍然能继续执行完。实际表现是:同一条 DDL 在半年前(同一个实例的旧小版本)能跑通,现在却被拒绝;表结构没有改动过。

原因:一次死锁修复让估算更准,也让校验更严

9.7.0 的改动(Bug #39129182):InnoDB 在此之前会低估索引记录的大小,导致它以不安全的顺序获取 page latch,并可能与并发的 B-tree 搜索一起进入死锁。修复后,InnoDB 会更准确地计算最大记录大小,包括索引前缀字段和空间 GEOMETRY 索引,从而能更早取到所需的 latch,避开该死锁。

副作用是校验规则变了:一些超出文档化 InnoDB 限制的 CREATE TABLE / ALTER TABLE 现在会被正确拒绝。问题在于,过去“差不多能过”的定义现在过不了——在更早的 LTS 上正常建出来的表被拒或产生告警,影响存量应用。这就是 Bug #120323(Bug #39249507),由 Percona 的 Paweł Olchawa 团队与 Amazon 的 Meng-Hsiu Chiang 团队贡献。

9.7.4(2026-10-20)的行为:大多数 DDL 恢复旧的校验行为,只在防止 B-tree 死锁确实需要的地方保留精确估算;ALTER TABLE ... ALGORITHM=INSTANT 继续使用更严格的计算。8.4 线上对应的版本是 8.4.11,它同样保留了防死锁所需的那部分计算,其余 DDL 回到旧校验。

如何确认自己遇到的是校验规则变化

以下三个比对成本都很低:

  1. 跨版本对照:同一份 DDL 在 9.7.3 被拒、在 9.7.4 通过(或 8.4.10 被拒、8.4.11 通过)。表定义一个字没改,说明问题出在校验规则上,不是表真的超了。
  2. 换算法对照:ALGORITHM=INSTANT 失败,而 ALGORITHM=COPY 或 INPLACE 通过。这说明你遇到的是 INSTANT 路径仍在使用严格算法,并非行宽真的越界。
  3. 时间线对照:表定义没动过,只在升级实例后重建或加列失败。

反过来,如果 INSTANT 和 COPY 都失败,并且下面 SQL 估算出的宽度确实贴着页上限,那就是真的超了,该拆表。

INSTANT 仍然严格的原因

INSTANT 增加列或修改默认值时不会重建表,大默认值要即时写进数据字典并反映到行上。如果这一步对默认值长度的估算偏松,后续 B-tree 操作拿到的记录大小就是错的,等于把死锁修复前的隐患原样放回去。所以这条路径用严格换安全,代价是大默认值的 DDL 更容易被拦。

诊断 SQL

按字符集计算列宽的字节上界:

SELECT table_schema, table_name,
       SUM(CASE
             WHEN character_maximum_length IS NOT NULL
               THEN character_maximum_length *
                    (CASE character_set_name
                       WHEN 'utf8mb4' THEN 4
                       WHEN 'utf8mb3' THEN 3
                       ELSE 1 END)
             ELSE COALESCE(numeric_precision, 8)
           END) AS approx_row_bytes
FROM information_schema.COLUMNS
WHERE table_schema = 'app' AND table_name = 't_big'
GROUP BY table_schema, table_name;

再看两个影响上限的实例参数:

SELECT @@innodb_default_row_format, @@innodb_page_size;

DYNAMIC 行格式 + 16K 页时,单行上限约 8126 字节,这是区分“真超”和“校验规则变化”的基准线。一串 varchar(255) 的 utf8mb4 列每列就占 1020 字节,七八列叠起来就接近上限了。

处理顺序

  1. 先用上面三条对照判断问题的性质,不要急着拆表或缩短 varchar——可能根本不需要改动。
  2. 确认是校验规则问题:在 9.7.4(或 8.4.11)上直接重跑原 DDL。还卡在旧版本的,按目标版本升级,不要先去重构表结构。
  3. 必须走 INSTANT 的:缩掉过大的默认值(挪到应用层,或改成 NULL 再加回填),然后重试。
  4. 回退:降级不是原地回滚,用逻辑备份恢复到旧版本更稳妥;动手前先在目标版本上验一遍这份 DDL 能通过,避免来回折腾两趟。
  5. 确认真的超了:改 ROW_FORMAT=DYNAMIC,把宽的 varchar 下沉到子表或换成 TEXT,减少索引前缀列——后者正是这次估算变准的部分。
复制全文 生成海报 MySQL InnoDB DDL 升级迁移

推荐文章

程序员茄子在线接单