# 数据迁移实战:建表、升级、卸载时绕开字段与索引的五个暗坑
做BBS或任何带数据库的插件开发,绕不开数据迁移。很多开发者把注意力放在业务逻辑上,建表语句随手一写,升级脚本能跑就行,卸载时删掉表就算完事。结果线上跑一段时间,字段类型不匹配、索引重复、旧数据残留的问题才慢慢浮出来。这篇文章不谈架构,只聚焦迁移过程中最容易翻车的五个具体位置。
坑一:建表时字段类型和长度拍脑袋定
最常见的是用INT存用户ID、用VARCHAR(255)存所有字符串。在MySQL里,INT有符号范围约21亿,看起来够用,但如果你参考的是Xiuno BBS这类系统,它的主键和关联字段往往用的是INT UNSIGNED,并且有特定的长度约定。插件表如果和外键字段类型不一致,JOIN查询时索引直接失效,数据量小的时候感觉不到,上万条以后查询耗时翻倍。
另一个高频问题是VARCHAR长度。比如存IP地址,有人写VARCHAR(15),只考虑了IPv4,但系统如果同时存IPv4和IPv6,15个字符根本不够。建表前先翻一遍核心表的字段定义,照着写,别自己发明。
坑二:升级脚本里重复加索引
插件从1.0升到1.1,你可能在升级代码里写了ALTER TABLE ADD INDEX。但如果用户先装了1.1再回退到1.0再升到1.1,或者升级脚本被重复执行,就会报“Duplicate key name”。更隐蔽的是,有些数据库版本对重复索引不报错,只是静默跳过,你以为加上了,实际没有。
稳妥做法是升级前先查information_schema,确认索引不存在再执行。或者用CREATE INDEX IF NOT EXISTS——但注意MySQL直到8.0才支持这个语法,老版本得手动判断。Xiuno BBS的插件机制里通常有版本号比对,利用好这个版本号,别让升级逻辑重复跑。
坑三:卸载时只删表,不删配置项和附件
卸载插件时DROP TABLE是最容易想到的,但插件往往还写了配置到setting表、上传了附件到upload目录、注册了钩子到hook表。只删主表,残留的配置项会在后台配置页留下孤儿数据,钩子残留则可能导致卸载后页面报错。
建议卸载脚本按这个顺序清理:先删钩子注册记录,再删配置项,再删附件文件和记录,最后删主表。如果插件有多个表,用数组遍历删除,别漏掉任何一张。
坑四:字符集和排序规则不统一
建表时如果不显式指定CHARACTER SET和COLLATE,会继承数据库默认值。但如果核心表用的是utf8mb4_general_ci,你的插件表用了utf8mb4_unicode_ci,JOIN时会出现“Illegal mix of collations”错误。这个问题在本地开发环境可能因为默认配置一致而不出现,一到线上就暴露。
建表语句里养成显式声明的习惯,并且和核心表保持一致。如果核心表用的是utf8mb4,别用utf8,否则emoji存不进去。
坑五:升级时直接改字段类型,不考虑存量数据
从VARCHAR(50)改成VARCHAR(100)通常没问题,但从VARCHAR改成INT、或者从TEXT改成VARCHAR,如果存量数据里有不符合新类型的内容,ALTER TABLE会失败或者截断数据。更麻烦的是,有些数据库在严格模式下直接报错,非严格模式下静默截断,你根本不知道数据丢了。
涉及类型变更的升级,先加一个新字段,把旧数据清洗后写入新字段,确认无误再删旧字段。多几步操作,但安全。
数据迁移的坑大多不是技术难题,而是细心程度的问题。建表前对照核心表结构,升级时考虑重复执行,卸载时清理干净,字符集显式声明,改类型前先备份数据。这五条做到,迁移环节基本不会出大问题。
你在插件开发中遇到过哪种迁移翻车现场?是字段类型对不上,还是卸载后残留数据导致页面白屏?欢迎在评论区说说你的排查过程。