Laravel 数据迁移回滚策略
基础回滚命令
# 回滚最后一次迁移 php artisan migrate:rollback # 回滚指定步数(回滚最后5次迁移) php artisan migrate:rollback --step=5 # 回滚所有迁移 php artisan migrate:rollback --step=0 # 回滚到指定批次 php artisan migrate:rollback --batch=3
高级回滚策略
1 使用 migrate:reset 完全回滚
# 回滚所有迁移并删除所有表(移除所有数据) php artisan migrate:reset # 使用环境限制 php artisan migrate:reset --env=production
2 使用 migrate:fresh 重建数据库
# 删除所有表并重新运行所有迁移 php artisan migrate:fresh # 重建并运行种子数据 php artisan migrate:fresh --seed # 只对特定连接进行操作 php artisan migrate:fresh --database=mysql
安全回滚策略
1 环境检查机制
// 在迁移文件中添加安全机制
class CreateUsersTable extends Migration
{
public function up()
{
// 防止在生产环境误操作
if (app()->environment('production') || app()->environment('staging')) {
$this->abortIfNotSafe();
return;
}
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
public function down()
{
// 添加确认机制
if (app()->environment('production')) {
$this->abortIfNotConfirmed();
return;
}
Schema::dropIfExists('users');
}
private function abortIfNotSafe()
{
throw new \RuntimeException('禁止在生产环境执行此迁移!');
}
private function abortIfNotConfirmed()
{
throw new \RuntimeException('生产环境回滚需要手动锁定!');
}
}
2 数据备份策略
// 在迁移前自动备份
class OrderTableMigration extends Migration
{
public function up()
{
// 执行前备份工具
$backup = new DatabaseBackup();
$backup->backupTable('orders', 'orders_backup_' . date('Y_m_d_H_i_s'));
Schema::table('orders', function (Blueprint $table) {
$table->string('status')->default('pending');
});
}
public function down()
{
Schema::table('orders', function (Blueprint $table) {
$table->dropColumn('status');
});
}
}
自定义回滚命令
创建自定义命令 app/Console/Commands/RollbackMigration.php:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class RollbackMigration extends Command
{
protected $signature = 'migrate:rollback-custom {--step=1 : 回滚步数} {--backup : 是否备份}';
protected $description = '自定义迁移回滚工具,支持数据备份';
public function handle()
{
// 检查环境
if (app()->isProduction()) {
$this->error('生产环境禁止直接回滚!');
return 1;
}
$step = $this->option('step');
// 备份选项
if ($this->option('backup')) {
$this->info('开始备份数据...');
$this->backupCurrentData();
}
// 日志记录
$this->logMigrationHistory('回滚开始');
try {
// 执行回滚
$this->call('migrate:rollback', [
'--step' => $step,
'--force' => true,
]);
$this->logMigrationHistory('回滚完成');
$this->info('迁移回滚成功!');
} catch (\Exception $e) {
$this->logMigrationHistory('回滚失败: ' . $e->getMessage());
$this->error('回滚失败: ' . $e->getMessage());
return 1;
}
return 0;
}
private function backupCurrentData()
{
$tables = DB::select('SHOW TABLES');
foreach ($tables as $table) {
$tableName = array_values((array)$table)[0];
// 复制表结构
$backupTable = $tableName . '_backup_' . date('Y_m_d_H_i_s');
DB::statement("CREATE TABLE {$backupTable} LIKE {$tableName}");
// 复制数据
DB::statement("INSERT INTO {$backupTable} SELECT * FROM {$tableName}");
}
$this->info('数据备份完成');
}
private function logMigrationHistory($message)
{
\Log::info("[迁移回滚] {$message}", [
'user' => auth()->user()?->username ?? 'system',
'time' => now(),
'env' => app()->environment()
]);
}
}
安全回滚检查清单
1 回滚前检查
// 在迁移的 down() 方法中添加安全检查
public function down()
{
// 检查是否有关键数据
$count = DB::table('users')->count();
if ($count > 10000) {
throw new \Exception("表中有 {$count} 条数据,禁止回滚!");
}
// 检查是否有外键引用
$hasForeignKeys = $this->hasForeignKeys();
if ($hasForeignKeys) {
throw new \Exception("存在外键引用,请先解除外键关系!");
}
Schema::dropIfExists('users');
}
private function hasForeignKeys()
{
// 检查外键的逻辑
return false;
}
2 回滚策略配置文件
// config/rollback.php
return [
'backup' => [
'enabled' => true,
'tables' => [
'users',
'orders',
'products'
],
'path' => storage_path('backups')
],
'environment' => [
'production' => [
'allow' => false,
'requires_confirmation' => true
],
'staging' => [
'allow' => true,
'requires_confirmation' => true
],
'local' => [
'allow' => true,
'requires_confirmation' => false
]
],
'safety' => [
'max_rows' => 10000,
'check_foreign_keys' => true,
'log_actions' => true
]
];
完整安全回滚流程
# 1. 查看当前状态 php artisan migrate:status # 2. 查看迁移历史 php artisan migrate:history # 3. 创建备份点 php artisan db:backup # 4. 检查环境 php artisan migrate:safety-check # 5. 执行回滚(自定义命令) php artisan migrate:rollback-custom --step=1 --backup # 6. 如果回滚失败,恢复到备份 php artisan db:restore --latest
生产环境安全回滚流程
// 生产环境专用回滚脚本
<?php
// app/Console/Commands/SafeRollbackCommand.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Carbon\Carbon;
class SafeRollbackCommand extends Command
{
protected $signature = 'migrate:safe-rollback
{--step=1 : 回滚步数}
{--confirm : 确认执行}
{--maintenance : 维护模式}';
protected $description = '生产环境安全回滚';
public function handle()
{
// 1. 检查验证确认
if (!$this->option('confirm')) {
$this->error('请使用 --confirm 参数确认执行');
return 1;
}
// 2. 记录开始时间
$startTime = Carbon::now();
$this->info('回滚开始: ' . $startTime);
// 3. 启用维护模式
if ($this->option('maintenance')) {
$this->call('down', [
'--message' => "系统维护中,迁移回滚进行中"
]);
}
// 4. 创建安全备份
$backupFile = $this->createBackup();
try {
// 5. 检查数据库完整性
$this->checkDatabaseIntegrity();
// 6. 执行回滚
DB::transaction(function () {
$this->call('migrate:rollback', [
'--step' => $this->option('step'),
'--force' => true
]);
});
// 7. 验证回滚结果
$this->verifyRollback();
$this->info('回滚成功,耗时: ' . $startTime->diffInSeconds(Carbon::now()) . '秒');
} catch (\Exception $e) {
// 8. 恢复备份
$this->restoreBackup($backupFile);
$this->error('回滚失败: ' . $e->getMessage());
$this->error('已恢复到备份点');
return 1;
} finally {
// 9. 关闭维护模式
if ($this->option('maintenance')) {
$this->call('up');
}
}
return 0;
}
private function createBackup()
{
$backupPath = storage_path('backups/' . date('Y_m_d_H_i_s') . '_backup.sql');
// 数据库备份逻辑
exec('mysqldump -u ' . config('database.connections.mysql.username') .
' -p' . config('database.connections.mysql.password') .
' ' . config('database.connections.mysql.database') .
' > ' . $backupPath);
$this->info('备份已创建: ' . $backupPath);
return $backupPath;
}
private function checkDatabaseIntegrity()
{
// 检查所有迁移状态
$pendingMigrations = DB::table('migrations')
->whereNull('batch')
->orWhere('batch', '<', 1)
->exists();
if ($pendingMigrations) {
throw new \Exception('数据库存在未完成的迁移');
}
}
private function verifyRollback()
{
// 验证关键表是否存在
$tables = [
'users',
'orders',
'products'
];
foreach ($tables as $table) {
if (!Schema::hasTable($table)) {
throw new \Exception("关键表 {$table} 不存在");
}
}
}
private function restoreBackup($backupFile)
{
$this->info('恢复备份: ' . $backupFile);
exec('mysql -u ' . config('database.connections.mysql.username') .
' -p' . config('database.connections.mysql.password') .
' ' . config('database.connections.mysql.database') .
' < ' . $backupFile);
}
}
最佳实践总结
- 永远在测试环境验证 - 在本地或预发布环境测试迁移
- 备份是必须的 - 回滚前必须备份
- 记录回滚日志 - 记录在审计日志中
- 维护窗口 - 在低峰期执行
- 回滚路径设计 - 确保 down() 方法正确实现
- 版本控制 - 记录在 Git 历史中
这些策略确保在 Laravel 项目中运行迁移系统时,回滚操作既安全又可预测。
