EF Core 迁移在高并发下不可多实例并行执行,因缺乏分布式锁与并发校验,易致元数据竞争、脚本错乱、重复或跳过迁移;生产环境须禁用自动迁移,改用独立迁移服务+数据库级应用锁+幂等SQL脚本。

EF Core Migration 在高并发环境为什么不能直接运行
多个实例同时执行 dotnet ef migrations add 或 dotnet ef database update 会导致迁移元数据(__EFMigrationsHistory 表)竞争、脚本顺序错乱,甚至出现重复执行、跳过迁移、锁表失败等现象。EF Core 的 database update 默认不加分布式锁,也不校验其他节点是否正在迁移。
生产环境必须禁用自动迁移(context.Database.Migrate())
在 Web API 启动时调用 context.Database.Migrate() 是常见错误——它会在每个实例启动时尝试执行所有待应用迁移,极易引发并发冲突。该方法仅适合单机开发或测试场景。
- 它不检查当前是否有其他进程正在迁移
- 它不阻塞等待,也不重试,失败即抛
SqlException或死锁异常 - 在 Kubernetes 多副本或负载均衡部署下,等于让 N 个服务同时抢着改库
推荐方案:独立迁移服务 + 数据库级互斥
将迁移操作从应用生命周期中剥离,由专职服务(如带重试的 Job 或 CI/CD 脚本)执行,并借助数据库原生机制确保「同一时刻最多一个迁移进程」。
- 使用
dotnet ef migrations script --idempotent --output migration.sql提前生成幂等 SQL 脚本,而非运行时动态生成 - 在部署阶段,用带超时与重试的脚本执行该 SQL,例如通过
sqlcmd或psql,并捕获锁等待/死锁错误后退避重试 - 关键一步:在执行迁移前,先尝试获取数据库级应用锁(SQL Server 用
sp_getapplock,PostgreSQL 用pg_advisory_lock) - 迁移成功后显式释放锁;若进程崩溃,锁会在会话断开时自动释放(依赖 DB 连接池配置)
DECLARE @result INT;
EXEC @result = sp_getapplock
@Resource = 'EFCoreMigrationLock',
@LockMode = 'Exclusive',
@LockOwner = 'Session',
@LockTimeout = 30000;
IF @result < 0
BEGIN
RAISERROR('Migration lock not acquired', 16, 1);
RETURN;
END
-- 执行迁移语句(如 CREATE TABLE / ALTER COLUMN)
-- ...
EXEC sp_releaseapplock @Resource = 'EFCoreMigrationLock';迁移脚本必须幂等且可重入
EF Core 生成的迁移 SQL 默认不是完全幂等的(比如 CREATE INDEX 不带 IF NOT EXISTS)。手动调整或使用 --idempotent 参数可缓解,但仍需验证。
-
--idempotent会让脚本在执行前检查__EFMigrationsHistory表,跳过已记录的迁移 —— 这是基础安全线 - 但对 DDL(如添加非空列、修改主键)仍可能失败,需人工补全
IF NOT EXISTS或拆分迁移步骤 - 避免在迁移中写业务逻辑(如
Sql("UPDATE ...")),这类语句无法自动回滚,且并发下易读到脏数据
真正难处理的是跨迁移的数据修正逻辑 —— 它需要外部协调状态、分批处理、并设计补偿事务,这已经超出 EF Core 迁移能力边界。










