跳到主要内容

5.8 EF Core 迁移发布策略

迁移不是“本地运行一下 database update”。生产数据库承载真实业务数据,任何表结构变化都需要可审查、可备份、可回滚,并尽量避免长时间锁表和应用停机。迁移发布策略的目标是让代码版本和数据库结构稳定协同演进。

学习目标

  • 能区分开发环境迁移和生产环境迁移的执行方式。
  • 能生成、审查和归档 EF Core 迁移 SQL 脚本。
  • 能设计向前兼容的表结构变更,降低停机风险。
  • 能为失败迁移准备备份、回滚和补救方案。

应用场景

  • Todos 表增加 DueDate 字段并发布到生产。
  • 把一个可空字段改成必填字段。
  • 给大表增加索引或拆分字段。
  • 多个应用实例滚动部署时保持新旧代码都能运行。

核心概念

概念说明
Migration 文件EF Core 记录模型变化的 C# 文件,必须提交到版本库
迁移 SQL 脚本从迁移生成的数据库变更脚本,生产发布前应审查
向前兼容数据库先支持新旧代码共同运行,再逐步切换代码
Expand / Contract先扩展结构保证兼容,确认稳定后再收缩旧结构
回滚发布失败后的恢复路径,可能是回滚代码、执行反向脚本或数据修复
备份生产迁移前保留可恢复的数据快照

案例:新增任务截止日期

新增可空字段通常是低风险迁移:旧代码不会使用它,新代码可以逐步写入。

dotnet ef migrations add AddTodoDueDate \
--project src/TodoApp.Infrastructure \
--startup-project src/TodoApp.Api

生成生产可审查 SQL:

dotnet ef migrations script \
--project src/TodoApp.Infrastructure \
--startup-project src/TodoApp.Api \
--output artifacts/migrations/2026-08-12-add-todo-due-date.sql

如果发布环境可能已经执行过部分迁移,可以生成幂等脚本:

dotnet ef migrations script --idempotent \
--project src/TodoApp.Infrastructure \
--startup-project src/TodoApp.Api \
--output artifacts/migrations/2026-08-12-idempotent.sql

脚本进入发布流程后,应由开发和数据库负责人审查,再在测试或预生产环境演练。

推荐发布流程

阶段动作产出
开发修改实体和配置,生成 MigrationMigration 文件
本地验证新库迁移、旧库升级、核心测试可重复执行的验证记录
脚本生成生成 SQL 或幂等 SQL可审查脚本
预生产演练使用接近生产数据量验证耗时和锁风险清单和执行窗口
生产发布备份、执行脚本、部署应用、验证健康发布记录
发布后监控错误率、慢查询、连接和业务指标回归确认

不要让生产应用启动时自动执行迁移,除非项目规模很小且团队明确接受风险。更稳妥的做法是把数据库变更作为发布步骤单独执行和审计。

示例:Expand / Contract

Title 拆成 TitleNormalizedTitle 时,不要一步删除旧字段或要求所有代码立即切换。更安全的节奏是:

  1. Expand:新增 NormalizedTitle 可空字段,旧代码继续可用。
  2. Deploy:新代码开始写入两个字段,并读取新字段兜底旧字段。
  3. Backfill:后台脚本补齐历史数据。
  4. Enforce:确认无空值后增加约束或索引。
  5. Contract:所有实例都升级后,删除旧兼容逻辑或旧字段。

示例回填脚本:

UPDATE Todos
SET NormalizedTitle = UPPER(LTRIM(RTRIM(Title)))
WHERE NormalizedTitle IS NULL;

大表回填不要一次性更新全表,应按主键范围分批执行,并观察锁、日志增长和业务延迟。

示例:从可空改为必填

直接把可空列改成 NOT NULL 可能失败,因为历史数据里已经有空值。更稳妥的流程是先补数据,再加约束。

UPDATE Todos
SET DueDate = CreatedAt
WHERE DueDate IS NULL;

ALTER TABLE Todos
ALTER COLUMN DueDate DATETIMEOFFSET NOT NULL;

如果补值规则有业务含义,应由产品或业务负责人确认。不要为了让迁移通过就随意填默认值。

示例:索引发布注意点

索引能提升查询速度,但创建索引会消耗 CPU、IO 和日志空间。大表索引发布前应在预生产环境测量耗时。

CREATE INDEX IX_Todos_TenantId_IsCompleted_CreatedAt
ON Todos (TenantId, IsCompleted, CreatedAt DESC);

索引不是越多越好。每个索引都会增加写入成本和存储成本,应服务真实查询路径,并在发布后通过慢查询和执行计划验证效果。

回滚策略

失败类型常见处理
应用代码失败,数据库兼容旧代码回滚应用版本,保留数据库变更
数据库脚本执行失败且未提交修复脚本或回滚事务
数据库脚本部分成功根据发布记录执行补救脚本,必要时恢复备份
数据写入错误停止写入、保留现场、执行数据修复脚本

生产回滚不一定等于执行 Down 迁移。删列、改类型、数据回填等操作可能不可逆。发布前要明确:哪些变更可以回滚,哪些只能向前修复。

重点难点

  • Migration 文件要提交到版本库,不能只保留本地数据库状态。
  • 生产迁移前要生成 SQL 脚本并审查,不要盲目执行自动迁移。
  • 有数据的表做非空约束、类型变更和大索引时风险更高。
  • 多实例滚动发布要求数据库结构同时兼容新旧代码。
  • 回滚方案必须在发布前写清楚,发布失败时才不会临时猜测。

常见误区

误区推荐做法
本地 database update 成功就直接上生产生成 SQL、审查、演练、备份后发布
应用启动自动迁移生产库把数据库迁移作为独立发布步骤
一次迁移同时改结构、搬数据、删旧列拆成 Expand / Backfill / Contract 多步
只准备代码回滚同时准备数据库补救和数据恢复方案
忽略迁移耗时在接近生产数据量的环境测量锁和耗时

练习

  • Todos 增加可空 DueDate,生成 Migration 和 SQL 脚本。
  • 设计一个把可空字段改成必填字段的两阶段发布流程。
  • 为任务列表查询新增组合索引,并说明如何验证索引效果。
  • 写一份发布检查清单:备份、脚本、执行人、验证项和回滚方案。

延伸阅读