c# 如何在高并发下保证数据库迁移(EF Core Migration)的安全性

9次阅读

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

c# 如何在高并发下保证数据库迁移(EF Core Migration)的安全性

EF Core Migration 在高并发环境为什么不能直接运行

多个实例同时执行 dotnet ef migrations adddotnet 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,例如通过 sqlcmdpsql,并捕获锁等待/死锁错误后退避重试
  • 关键一步:在执行迁移前,先尝试获取数据库级应用锁(SQL Server 用 sp_getapplockpostgresqlpg_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 迁移能力边界。

text=ZqhQzanResources