数据库迁移三件套:建表、升级、卸载时最容易翻车的几个细节
很多人以为数据库迁移就是跑几条 SQL 的事,真正动手才发现坑全藏在细节里。这篇文章不聊理论,直接说我实际踩过的几个问题,以及现在的处理习惯。
建表:别只盯着字段类型
刚开始设计表的时候,我习惯先把字段类型定好就完事。后来被坑了几次才明白,字符集和排序规则才是第一道坎。如果表用了 utf8mb4,而连接串里还是 utf8,插入 emoji 直接报错。更隐蔽的是排序规则,不同库之间 join 查询时,如果 collation 不一致,性能会断崖式下降。
另外一个容易忽略的是索引长度。老版本 MySQL 里,varchar(255) 加前缀索引没问题,但换成 utf8mb4 后,255 字符乘以 4 字节已经超过 767 字节限制。建表语句里看似正常的索引,执行的时候直接报错。现在我的习惯是:所有字符串字段先想清楚到底需要多长,能用 varchar(64) 绝不用 255,索引设计也尽量用前缀索引。
升级:增量脚本比全量重建靠谱
早期做升级,我图省事,直接 drop 表再重建。数据量小的时候没问题,等表里有了几十万行,一次升级要锁表十几分钟,线上直接报警。后来改成写增量脚本,每个版本一个 SQL 文件,里面只包含新增字段、修改索引、数据订正这些操作。
这里有个关键点:升级脚本必须能重复执行。用 ALTER TABLE ... ADD COLUMN IF NOT EXISTS 这种语法,或者先查 information_schema 判断字段是否存在再执行。不然同一个脚本跑两遍,第二次必然报错。另外,升级前一定要备份,但这个备份不是备份整个库,而是备份将要变更的那张表。用 CREATE TABLE xxx_bak AS SELECT * FROM xxx 就够了,恢复的时候也快。
卸载:外键和残留数据是重灾区
卸载插件或者模块的时候,很多人只记得删主表,忘了关联表。比如一个订单插件,订单表删了,但订单明细表、支付回调记录表还在,下次重新安装的时候数据全乱套。我的做法是:所有表名统一加前缀,卸载时用 SHOW TABLES LIKE 'prefix_%' 把所有关联表都找出来,逐个确认再删。
还有外键约束的问题。如果建表时用了外键,卸载主表前必须先删子表,或者先 drop 外键约束。不然删除顺序反了,数据库直接报错。更稳妥的办法是建表时干脆不用外键,关联关系靠应用层保证,这样卸载的时候只需要按表名顺序删就行。
最后提醒一点:无论建表、升级还是卸载,都建议在事务里执行。MySQL 的 DDL 语句虽然不支持事务回滚,但至少把数据订正类的 DML 操作包在事务里,出问题还能 rollback。别问我怎么知道的,都是血泪教训。