# Database Migration — 迁移模式与示例 > 主流程见 SKILL.md,本文档为幂等迁移、Expand-Contract、批量迁移、大表策略的完整实现。 ## 幂等迁移 ```php public function up(): void { if (!Schema::hasTable('production_orders')) { Schema::create('production_orders', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('order_no', 50)->unique(); $table->timestamps(); $table->softDeletes(); }); } if (!Schema::hasColumn('production_orders', 'priority')) { Schema::table('production_orders', function (Blueprint $table) { $table->tinyInteger('priority')->default(0)->after('status'); }); } if (!$this->hasIndex('production_orders', 'idx_orders_priority')) { Schema::table('production_orders', function (Blueprint $table) { $table->index('priority', 'idx_orders_priority'); }); } } ``` ## Expand-Contract 三阶段示例(字段重命名 username → display_name) Phase 1 EXPAND: 添加 display_name nullable。部署 v2 双写。 Phase 2 MIGRATE: 独立迁移文件批量 UPDATE 回填。部署 v3 读新写双。验证一致性。 Phase 3 CONTRACT: 删除 username。部署 v4 仅新字段。 时间线:Day 1 加字段+双写,Day 2 回填,Day 3 读新写双+验证,Day 7 删旧字段。 ## 批量数据迁移模板 ```php $batchSize = 2000; $lastId = 0; while (true) { $affected = Db::update("UPDATE users SET normalized_email = LOWER(email) WHERE id > ? AND normalized_email IS NULL ORDER BY id LIMIT ?", [$lastId, $batchSize]); if ($affected === 0) break; $lastId = Db::selectOne("SELECT MAX(id) AS max_id FROM users WHERE normalized_email IS NOT NULL AND id > ?", [$lastId])->max_id ?? $lastId + $batchSize; usleep(100_000); } ``` 规则:1000–5000 行/批、批次间 sleep、游标 WHERE id > ?、低峰期执行。 ## 大表变更策略(百万级+) | 操作 | 风险 | 方案 | |------|------|------| | ADD COLUMN | 低 | 直接执行,nullable 或有默认值 | | ADD INDEX | 中 | ALGORITHM=INPLACE, LOCK=NONE | | DROP COLUMN | 高 | 先移除代码→部署→下版本迁移删除 | | ALTER TYPE | 高 | Expand-Contract | | RENAME COLUMN | 高 | Expand-Contract | | DROP TABLE | 极高 | 先重命名→观察一周→删除 | ## 危险操作清单 | 操作 | 缓解 | |------|------| | DROP TABLE | 先备份 | | DROP COLUMN | 先移除代码引用 | | ALTER TYPE | Expand-Contract | | TRUNCATE | 禁止生产 | | NOT NULL 无默认值 | 先 nullable→回填→再加约束 |