ThinkPHP 本身不提供内置数据库迁移功能,需通过第三方扩展 topthink/think-migration 实现,其核心是让表结构变更可版本化、可协作、可回滚;安装需按 TP 版本选择对应扩展版本,配置命令注册与数据库连接,执行 migrate:install 初始化元数据表,再通过 migrate:create 生成规范命名的迁移文件,严格遵循 up()/down() 编写规则,最后用 migrate:run 执行并借助 migrate:status 验证状态,跨环境须比对 think_migrations 表确保一致。

ThinkPHP 本身不提供内置的数据库迁移和版本控制能力,必须依赖第三方扩展 topthink/think-migration 实现。它不是“开箱即用”的功能,而是一套需手动集成、规范编写、谨慎执行的结构变更管理机制。核心目标是让表结构变更像代码一样可版本化、可协作、可回滚。
安装与初始化迁移环境
迁移命令能否运行,取决于扩展是否装对、命令是否注册、数据库是否连通。
- TP6/TP7 项目执行:composer require topthink/think-migration:^4.0;TP5.x 需用 ^2.x(注意 PHP 版本兼容性)
- TP6 必须检查
config/console.php中'commands' => [...]是否包含\think\migration\Command::class,漏掉则所有 migrate 命令报 “Command not defined” - 运行
php think migrate:install创建元数据表(默认think_migrations),这是后续识别迁移状态的基础,跳过将导致 “No migrations to run” - 确保
config/database.php中数据库配置正确——迁移读的是这里,.env的 DB 配置在 TP6 默认不生效
生成与编写迁移文件
迁移文件不是普通 PHP 类,它是带时间戳前缀、固定路径、严格命名规则的“结构操作单元”,框架靠它排序和识别。
- 务必使用命令生成:
php think migrate:create AddUserNicknameToUsersTable,自动生成文件如20260902130000_add_user_nickname_to_users_table.php,存于database/migrations/(注意是 migrations,非 migrate) - 类名必须与文件名下划线后部分一致(如
AddUserNicknameToUsersTable),且继承think\migration\Migrator -
up()中用链式方法定义正向变更:$this->table('users')->addColumn('nickname', 'string', ['limit' => 30]);禁止调用模型或 Db 门面 -
down()必须可逆:删字段要先判断是否存在,推荐写法:if ($this->hasColumn('users', 'nickname')) { $this->table('users')->removeColumn('nickname')->save(); }
执行、验证与跨环境同步
迁移不是“跑完就完”,关键在执行后的状态确认和多环境一致性保障。
立即学习“PHP免费学习笔记(深入)”;
- 应用变更:用
php think migrate:run(不是php think migrate,后者无响应) - 查看状态:运行
php think migrate:status,能列出已执行/未执行的迁移,但不显示 SQL 错误详情 - 回滚操作:慎用
php think migrate:rollback,建议先在测试环境验证down()是否真能成功执行,尤其涉及大表索引或外键时 - 环境比对:上线前对比各环境的
think_migrations表内容,确保迁移记录完全一致,避免“开发建了表,生产还缺字段”
字段级变更追踪与日志补充
ThinkPHP 迁移只管结构快照,不管字段值变化。若需审计谁改了哪个字段、何时改的,得额外设计。
- 迁移文件中可在类注释或文件名里体现意图,例如:
20260902131500_alter_users_status_enum_add_rejected.php - 对敏感字段(如
status、price、audit_result),在模型中用before_write事件捕获脏数据:$model->getChangedData()返回修改字段,配合$model->getOrigin($field)获取旧值 - 日志表建议含:
table_name、record_id、field_name、old_value、new_value、operator_id、created_at,并确保写入与主更新同事务



















