MySQL 字段长度从 `255` 扩到 `500` 那晚,我差点把整站评论表锁到第二天中午
上周给社区做评论功能升级,原表设计的时候 `content` 字段设了 `varchar(255)`,想着够用了。结果用户开始发长文吐槽,255 直接砍半,报错信息还特隐蔽——前端只显示"发布失败",日志里才藏着 `Data too long for column 'content'`。
行,改吧。`ALTER TABLE comment MODIFY content VARCHAR(500)`,本地测完没问题,丢到生产执行。表数据八十万条,这语句一跑,整个评论模块卡死,连接数飙到顶,Nginx 开始吐 502。我这才反应过来,MySQL 5.7 改 `varchar` 长度虽然不算重建表,但超过 255 字节要改行格式,锁表时间跟数据量成正比。
最后靠 `pt-online-schema-change` 苟过去的,影子表建完、触发器挂上、数据追平、切表 rename,全程业务无感知。但影子表那二十分钟,我盯着进度条手心全是汗——生怕触发器漏了哪条并发评论。
这事给我敲了几个钉子:
一、扩字段前先算字符集
`utf8mb4` 下一个字符 4 字节,`varchar(255)` 实际占 1020 字节,刚好卡在行内存储的临界。扩到 500 直接触发页分裂,跟 `text` 类型的存储路径都变了。现在建表但凡可能变长的字段,我直接 `text` 起步,省得后期折腾。
二、大表结构变更必须走工具
`pt-osc` 或 `gh-ost` 二选一,别信 `ALGORITHM=INPLACE` 的安慰。尤其有外键的表,`pt-osc` 的外键处理策略选不对,切表后子表关系全乱。我现在的 checklist 里专门有一条:改结构前 `SHOW CREATE TABLE` 确认引擎、字符集、外键、触发器一个不落。
三、卸载旧版本别顺手 `DROP TABLE`
这次升级还踩了个历史包袱。评论表之前用过一个第三方插件,卸载的时候作者文档写"清理数据请手动删除相关表"。我照做了,结果那插件偷偷在 `user` 表里加了 `comment_count` 字段做冗余计数,`DROP` 的时候没联动清,留下个孤儿字段。后来新系统读这字段做排行榜,数据全是 stale 的,排查半天才发现是"遗迹数据"。
现在我的卸载流程改成:先 `mysqldump` 单表备份,再 `RENAME TABLE` 到归档库放两周,最后写清理脚本遍历 `INFORMATION_SCHEMA.COLUMNS` 扫关联字段。宁可磁盘多占点,也别给后面埋雷。
四、版本升级记得校验 `sql_mode`
从 5.7 迁 8.0 测试环境的时候,`ONLY_FULL_GROUP_BY` 和 `NO_ZERO_DATE` 直接把我一堆历史 SQL 炸出来了。最阴的是 `STRICT_TRANS_TABLES`,以前插超长的字符串会静默截断,8.0 直接报错。迁移前用 `pt-query-digest` 抓慢日志里的隐患语句,比上线后救火强一百倍。
数据这东西,建的时候嫌麻烦,迁的时候想抽自己,删的时候手一抖就是事故。各位站长有类似被锁表支配的恐惧吗?

