Laravel数据库迁移高级技巧:复杂架构变更的安全处理
在Laravel开发中,数据库迁移是管理数据库结构变更的核心工具。但随着项目复杂度提升,简单的up()和down()方法已无法应对生产环境的复杂场景。本文将通过三个实战案例,展示如何安全处理分表、大表修改和数据迁移等高级场景,确保零停机时间完成架构升级。
一、分表策略:从单表到历史数据归档
当用户表数据量超过百万级时,查询性能会显著下降。通过迁移实现动态分表,既能保持新数据写入效率,又能安全归档历史数据。
1.1 分表迁移文件实现
创建分表迁移文件:
php artisan make:migration create_user_archives_table
编辑迁移文件database/migrations/2025_10_01_000000_create_user_archives_table.php:
public function up()
{
// 创建归档表结构(与users表一致)
Schema::create('user_archives', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
// 添加归档专用字段
$table->timestamp('archived_at')->default(DB::raw('CURRENT_TIMESTAMP'));
});
// 数据归档(使用chunk避免内存溢出)
DB::table('users')
->where('created_at', '<', now()->subYear())
->chunkById(1000, function ($users) {
DB::table('user_archives')->insert(
$users->toArray()
);
});
}
public function down()
{
// 回滚时合并数据
DB::table('user_archives')->orderBy('id')->chunkById(1000, function ($archives) {
foreach ($archives as $archive) {
DB::table('users')->updateOrInsert(
['id' => $archive->id],
(array)$archive
);
}
});
Schema::dropIfExists('user_archives');
}
1.2 分表后的模型处理
修改User模型app/Models/User.php,添加归档查询作用域:
public function scopeNotArchived($query)
{
return $query->whereDoesntHave('archive');
}
public function archive()
{
return $this->hasOne(UserArchive::class);
}
二、大表字段修改:零停机变更策略
直接修改百万级数据量的表字段会导致长时间锁表。采用"双写过渡"方案可实现零停机变更。
2.1 安全添加索引示例
创建迁移文件:
php artisan make:migration add_index_to_sessions_table
编辑迁移文件database/migrations/2025_10_01_000001_add_index_to_sessions_table.php:
public function up()
{
// 为大表添加索引时使用算法优化
DB::statement('ALTER TABLE sessions ADD INDEX idx_last_activity (last_activity) USING BTREE');
// 监控迁移进度(适用于生产环境)
Log::info('Session表索引创建完成');
}
public function down()
{
DB::statement('ALTER TABLE sessions DROP INDEX idx_last_activity');
}
2.2 字段类型变更流程
- 创建新字段
- 双写数据到新旧字段
- 数据同步完成后切换读取新字段
- 删除旧字段(单独迁移)
// 第一步:添加新字段
Schema::table('users', function (Blueprint $table) {
$table->text('bio_new')->nullable()->after('password');
});
// 第二步:数据同步(生产环境建议使用队列)
DB::table('users')->whereNotNull('bio')->chunkById(1000, function ($users) {
foreach ($users as $user) {
DB::table('users')
->where('id', $user->id)
->update(['bio_new' => $user->bio]);
}
});
三、数据迁移事务与错误处理
复杂迁移必须考虑原子性,Laravel提供了事务支持和断点续跑能力。
3.1 事务封装多表操作
public function up()
{
DB::transaction(function () {
// 创建产品表
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->decimal('price', 8, 2);
$table->timestamps();
});
// 创建分类表
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
// 创建关联表
Schema::create('category_product', function (Blueprint $table) {
$table->foreignId('product_id')->constrained();
$table->foreignId('category_id')->constrained();
$table->primary(['product_id', 'category_id']);
});
});
}
3.2 断点续跑实现
使用迁移状态文件记录进度:
public function up()
{
$batch = DB::table('migrations')->max('batch') + 1;
$progressFile = storage_path("app/migration_progress_{$batch}.json");
$progress = json_decode(file_get_contents($progressFile), true) ?? ['last_id' => 0];
DB::table('old_orders')
->where('id', '>', $progress['last_id'])
->orderBy('id')
->chunkById(1000, function ($orders) use (&$progress, $progressFile) {
foreach ($orders as $order) {
// 转换数据并插入新表
DB::table('new_orders')->insert([
'id' => $order->id,
'amount' => $order->total,
'created_at' => $order->order_date
]);
$progress['last_id'] = $order->id;
}
// 保存进度
file_put_contents($progressFile, json_encode($progress));
});
}
四、迁移最佳实践清单
4.1 迁移文件命名规范
- 使用清晰的动作动词:
add_xxx_to_yyy_table、create_xxx_table - 包含业务领域:
create_payment_transactions_table而非create_transactions_table - 避免模糊词汇:
update_users_table应具体为add_phone_to_users_table
4.2 性能优化 checklist
- 大表操作使用
chunkById而非get - 添加索引指定算法:
USING BTREE - 避免在迁移中使用模型事件(会拖慢速度)
- 生产环境迁移前运行
EXPLAIN分析SQL执行计划
4.3 版本控制与回滚测试
每次迁移都应在测试环境完成以下验证:
php artisan migrate正常执行php artisan migrate:rollback可回滚- 回滚后再次
migrate无数据丢失
通过以上技巧,即使面对百万级数据量和复杂业务场景,也能确保数据库架构变更的安全性和可靠性。实际操作中,建议结合database/seeds/DatabaseSeeder.php创建测试数据,在预发布环境充分验证迁移方案。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



