必须用结构差异分析工具(如mysqldiff)比对开发与生产库结构并生成补丁SQL,而非重跑迁移或手动导入schema;迁移文件需保证幂等性,DDL前加存在性判断;严格对齐sql_mode、charset(utf8mb4)、timezone等配置。

怎么让开发库的表结构自动同步到生产库
直接用 ThinkPHP 自带的 php think migrate:run 不行——它只跑迁移文件,不比对现有结构。真要同步,得靠结构差异分析工具,而不是靠“再执行一遍迁移”。
常见错误是把开发环境的 schema.sql 手动导出再导入生产库,结果外键、索引顺序、字符集(比如 utf8mb4_unicode_ci vs utf8mb4_general_ci)全乱套,第二天就报 SQLSTATE[HY000]: General error: 1005 Can't create table。
- 必须先用
mysqldump --no-data --skip-triggers导出两边库的纯结构(去掉数据和触发器,避免干扰) - 用开源工具
mysqldiff(MySQL Utilities)比对:mysqldiff --server1=user:pass@localhost:3306 --server2=user:pass@prod-host:3306 dev_db:prod_db - 生成的 SQL 补丁里,
DROP INDEX和ADD INDEX顺序不能反——ThinkPHP 的模型如果依赖某个联合索引,删早了会直接让查询变全表扫描
ThinkPHP 迁移文件怎么写才不怕上线翻车
迁移文件不是写完就能跑,关键在 up() 和 down() 的幂等性。线上环境不允许“重试失败”,所以每条 SQL 都得自带存在性判断。
比如加字段,别写 $table->addColumn('status', 'tinyint'),而要用原生 SQL 包一层 ALTER TABLE IF NOT EXISTS(注意:MySQL 8.0.23+ 才支持 IF NOT EXISTS 在 ADD COLUMN 中,低版本得先查 information_schema.COLUMNS)。
立即学习“PHP免费学习笔记(深入)”;
-
up()里所有 DDL 操作前,加if (!$this->hasColumn('users', 'is_vip')) { ... }判断(用$this->getConnection()->getDoctrineSchemaManager()->listTableColumns()) - 改字段类型时,避免
changeColumn直接转json类型——老数据可能含非法 JSON,会卡住整个迁移事务 - 生产环境禁用
php think migrate:refresh,这个命令会删表重建,不是“回滚”,而是“重来”,连主键自增 ID 都重置
开发和生产数据库配置差异引发的隐性问题
最常被忽略的是 timezone 和 sql_mode。开发用 localhost 默认走宽松模式,生产开了 STRICT_TRANS_TABLES,结果 insert into user (name) values ('') 在开发能过,上线直接报 Field 'name' doesn't have a default value。
还有 charset:ThinkPHP 的 database.php 里写 'charset' => 'utf8' 是错的,MySQL 早就废弃 utf8,必须写成 'charset' => 'utf8mb4',否则 emoji 存进去变成 ???,后期修复成本远高于初期对齐。
- 检查生产库实际
sql_mode:连上去执行SELECT @@sql_mode,确保开发环境config/database.php的'strict' => true开关与之匹配 -
timezone统一设为'+08:00',别依赖系统时区;否则created_at字段在日志里显示的时间和数据库存的差 8 小时 - 连接池配置(如 PDO 的
PDO::ATTR_PERSISTENT)开发可关,但生产若开启,要注意长连接下SET NAMES不生效导致乱码
为什么 php think migrate:status 显示“Ran”却没生效
因为 ThinkPHP 迁移状态只查 migration 表里的记录,不验证表结构是否真按迁移文件执行了。你手动改过表、或者用其他工具同步过结构,migration 表没更新,状态就永远“已执行”,但实际字段早被删了。
典型现象:开发加了个 deleted_at 字段,迁移标记为 R,上线后模型调 softDelete() 报错 Unknown column 'deleted_at',但 php think migrate:status 还显示绿色勾。
- 上线前必做:用
php think migrate:status --verbose看每条迁移对应的 SQL 是否真在目标库执行过(需开启log_sql) - 更稳妥的做法是,在 CI/CD 流水线里加一步:用
mysqldiff对比开发库结构快照和生产库当前结构,有差异就阻断发布 - 别信
migration表,信information_schema—— 写个简单脚本查SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='your_db' AND TABLE_NAME='users' AND COLUMN_NAME='deleted_at'
结构同步这事,没有银弹。自动化工具只能帮你发现差异,真正安全的上线,靠的是每次变更都带着可验证的断言,而不是“应该没问题”。


















