所有表结构变更必须通过带版本号的sql文件执行,禁止直接在生产库运行alter命令;每个文件仅含一个操作、严格编号、开头注明影响范围。

MySQL表结构变更必须走SQL文件,不能直接在生产库上ALTER
线上表结构改了但没留痕,等于没改——下次部署、回滚、新环境初始化全要抓瞎。核心原则:所有CREATE、ALTER、DROP操作必须写成可执行的SQL文件,按顺序编号存放,像V001_add_user_email.sql、V002_alter_user_status.sql。
常见错误现象:mysql -e "ALTER TABLE user ADD COLUMN phone VARCHAR(20)"这种命令跑完就丢,CI/CD里没法复现,DBA查问题时连谁、什么时候、为什么加的字段都搞不清。
- 每个SQL文件只做一件事(比如只加一个字段,或只改一个索引),避免合并多个变更导致失败后难以定位
- 文件名用严格递增版本号(推荐
V{yyyymmddhhmm}_{desc}.sql或V001_...),别用Git commit hash——它不反映执行顺序 - SQL开头加注释说明影响范围,例如
-- 影响:user表,预计执行时间
Liquibase vs Flyway:选哪个取决于你有没有Java生态依赖
两者都靠维护databasechangelog表来记录已执行脚本,但集成方式和默认行为差异明显。没用Spring Boot?优先看Flyway;项目里已经用了spring-boot-starter-jdbc又想少配配置?Liquibase对YAML/JSON变更支持更原生。
使用场景对比:
- Flyway默认只认
V前缀的SQL文件,命名规则硬,但启动快、冲突少,适合纯SQL团队 - Liquibase支持
XML/YAML/JSON格式的逻辑变更(如<addcolumn></addcolumn>),能跨数据库抽象,但运行时多一层解析,出错信息更难读 - 都支持
undo(Flyway CE版不支持,Liquibase需手动写rollback块),但生产环境慎用——ROLLBACK不是万能的,删列、改类型大概率不可逆
ALTER TABLE卡住或超时?先查information_schema.INNODB_TRX再动手
执行ALTER TABLE时连接挂起、应用报Lock wait timeout exceeded,八成不是SQL写错了,而是有长事务占着表没释放。MySQL 5.7+ 的INNODB_TRX表能立刻告诉你谁在挡路。
实操建议:
- 先跑
SELECT TRX_ID, TRX_MYSQL_THREAD_ID, TRX_QUERY FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'RUNNING';,找TRX_QUERY为空但TRX_STARTED很老的线程 - 确认无业务影响后,用
KILL {TRX_MYSQL_THREAD_ID}干掉它(别KILL错主库复制线程) - 大表加字段务必加
ALGORITHM=INSTANT(MySQL 8.0.12+),否则默认COPY会锁全表;8.0以下只能用pt-online-schema-change
SQL迁移脚本里禁止出现USE database_name和绝对路径
带USE的脚本在多环境(dev/staging/prod)切换时极易执行到错库,而硬编码路径如/var/lib/mysql/myapp/会让脚本彻底失去移植性。所有上下文必须由工具或部署流程注入。
容易踩的坑:
- Flyway默认以当前连接库为执行目标,脚本里写
USE反而会导致后续语句在错误库执行(比如建表建到mysql系统库) - Liquibase的
changeLog文件若含schema属性,应统一设为变量(如${db.schema}),由application.yml或启动参数传入 - 涉及数据填充的SQL(如
INSERT INTO config VALUES (...)),必须加WHERE NOT EXISTS或用INSERT IGNORE,避免重复执行报Duplicate entry
最麻烦的从来不是写第一个ALTER,而是三个月后你忘了那个V027脚本里悄悄加了个ON UPDATE CURRENT_TIMESTAMP,结果测试库时间戳全乱了——版本管理的本质,是让下次读代码的人不用猜你当时怎么想的。











