
本文探讨在已有大量旧数据的数据库上是否应使用迁移(migrations)进行表结构清理、数据删除或字段裁剪,并明确区分迁移、种子器与自定义命令的职责边界,帮助开发者避免架构维护陷阱。
在现代 PHP 框架(如 Laravel 或 CodeIgniter 4)中,数据库迁移的核心职责是版本化管理数据库结构变更——即 CREATE TABLE、ALTER TABLE、DROP TABLE 等 DDL 操作。它不是为数据清洗或业务逻辑清理而设计的工具。当你面对一个从旧项目继承的、含 49 张表和数千行混合数据的数据库时,必须严格遵循“结构归迁移,数据归命令”的工程原则。
✅ 迁移(Migrations)适用于以下场景:
- 新增/重命名/删除整张表(如 php artisan make:migration drop_legacy_logs_table);
- 添加/修改/删除字段或索引(如移除已废弃的 old_api_key 列);
- 调整约束(如外键、唯一性);
- 所有操作必须幂等、可回滚、不依赖运行时上下文。
示例:安全删除冗余表的迁移文件
// database/migrations/2024_05_10_000000_drop_unused_legacy_tables.php
public function up(MigrationBuilder $migration): void
{
Schema::dropIfExists('old_newsletter_subscribers');
Schema::dropIfExists('legacy_activity_log');
Schema::dropIfExists('temp_import_cache');
}⚠️ 注意:若原始旧库从未被纳入迁移体系(即无对应 create_*_table 迁移),则不应逆向补写迁移来“还原”结构——这会污染迁移历史,违背其“演进式建模”初衷。此时应直接在数据库中执行 DROP TABLE,并在文档中记录清理清单。
❌ 迁移绝不应用于以下操作:
- 删除特定业务数据(如“清除 2022 年前的测试用户”);
- 基于条件批量更新/软删除记录;
- 清洗脏数据(如修复空邮箱、去重用户名);
- 任何需访问模型、服务容器或业务逻辑的场景。
这类操作属于数据治理范畴,应交由 Laravel 的 Seeder(仅用于初始数据) 或更推荐的 自定义 Artisan 命令 实现:
php artisan make:command CleanLegacyData
// app/Console/Commands/CleanLegacyData.php
public function handle(): int
{
// 安全删除旧用户(保留开发账户)
User::where('created_at', '<', '2022-01-01')
->whereNotIn('email', ['dev@local.test', 'admin@example.com'])
->delete();
// 清理关联数据(注意外键约束)
Order::whereDoesntHave('user')->delete();
Product::whereNull('category_id')->forceDelete();
$this->info('✅ Legacy data cleaned successfully.');
return self::SUCCESS;
}执行命令:
php artisan clean:legacy-data --force # 加 --force 避免误操作
? 关键原则总结
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 删除整张废弃表 | 迁移(down() 中 dropIfExists) | 结构变更,需版本追踪 |
| 清理数万行旧业务数据 | 自定义 Artisan 命令 | 可加日志、事务、确认提示、分批处理 |
| 初始化测试数据 | Seeder(配合 --class 指定) | 仅用于 db:seed 场景,非生产清理 |
| 修改表结构(删字段/改类型) | 迁移(带 change() 或重建表) | DDL 变更必须可复现 |
最后提醒:若未来需 Squash Migrations(合并历史迁移以提升部署效率),所有混入迁移中的数据操作将永久丢失,导致新环境无法复现清理逻辑——这是不可逆的风险。因此,请始终坚守职责分离:让迁移只管“数据库长什么样”,让命令只管“数据库里有什么”。










