当前位置: 首页 > article >正文

EF Core 乐观并发控制(并发令牌)

文章目录

  • 前言
  • 一、乐观并发的核心思想
  • 二、实现方法
    • 1)使用并发令牌(Concurrency Token)
    • 2)处理并发冲突
  • 三、工作原理
  • 四、适用场景
  • 五、与悲观并发的对比
  • 六、最佳实践
  • 总结


前言

Entity Framework (EF) Core 默认支持 乐观并发控制(Optimistic Concurrency Control),它通过检测数据冲突(而不是显式加锁)来保证数据一致性。

一、乐观并发的核心思想

  1. 无锁机制:允许多个事务同时读取和修改数据,提交时检查数据是否被其他事务修改。
  2. 冲突检测:通过版本号(RowVersion)或字段值比较,如果数据已被修改,则抛出
    DbUpdateConcurrencyException

二、实现方法

1)使用并发令牌(Concurrency Token)

  1. 为实体添加一个并发标记字段(如 RowVersion),每次更新时检查该字段是否与数据库中的值一致。

  2. 示例:通过 [ConcurrencyCheck] 特性标记字段

    	public class House
    	{
    	    public long Id { get; set; }
    	    public string Name{ get; set; }
    	
    	    [ConcurrencyCheck]  // 标记为并发令牌
    	    public string Owner{ get; set; }
    	}
    
  3. 示例:或通过 Fluent API 配置

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<House>()
            .Property(p => p.Owner)
            //.IsRowVersion()          // 自动映射为 SQL Server 的 `rowversion` 类型
            .IsConcurrencyToken();    // 标记为并发令牌
    }
    
    public class House
    {
    	    public long Id { get; set; }
    	    public string Name{ get; set; }		
    	    public string Owner{ get; set; }
    	    public byte[] RowVersion { get; set; }
    }
    public void Configure(EntityTypeBuilder<House> builder)
    {
        builder.ToTable("T_Houses");
        builder.Property(h=>h.Name).IsRequired();
        //builder.Property(h=>h.Owner).IsConcurrencyToken();
        builder.Property(h=>h.RowVersion).IsConcurrencyToken().IsRowVersion();
    }
    

2)处理并发冲突

  1. 当检测到数据已被修改时,EF Core 会抛出 DbUpdateConcurrencyException,开发者需捕获并处理冲突。

    try
    {
        var house = await context.Houses.FindAsync(houseId);
        house.Owner = "Tom";
        await context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException ex)
    {
        // 处理冲突
        var entry = ex.Entries.Single();
        var databaseValues = await entry.GetDatabaseValuesAsync();
    
        if (databaseValues == null)
        {
            // 数据已被删除
            Console.WriteLine("数据已被删除!");
        }
        else
        {
            // 获取当前数据库中的值
            var currentValues = databaseValues.ToObject() as House;
    
            // 策略1:使用数据库最新值覆盖当前修改
            entry.OriginalValues.SetValues(databaseValues);
    
            // 策略2:合并值(手动处理冲突)
            // currentValues 是数据库中的最新值
            // entry.Entity 是当前尝试提交的值
            // 例如:保留用户修改的某些字段,合并其他字段
            entry.Entity.Owner= "Tom";
            entry.Entity.RowVersion = currentValues.RowVersion;
    
            // 重新提交
            await context.SaveChangesAsync();
        }
    }
    

三、工作原理

  1. 查询数据:读取数据时,EF Core 会记录并发令牌的原始值(如 RowVersion)。

  2. 更新数据:提交修改时,生成的 SQL 会包含 WHERE 条件,检查并发令牌是否未被修改。

    UPDATE [T_Houses]
    SET [Owner] = @p0
    WHERE [Id] = @p1 AND [Owner] = @p2;
    
  3. 冲突检测:如果受影响的行数为 0(即 RowVersion 不匹配),抛出异常。

四、适用场景

  1. 低冲突概率:适合大部分时间数据竞争较少的场景。
  2. 高吞吐需求:避免锁机制的开销,提升性能。
  3. 分布式系统:无锁机制更适合跨服务的并发操作。

五、与悲观并发的对比

乐观并发悲观并发
实现方式版本号或字段检查(RowVersion、并发令牌)显式加锁(事务+锁机制)
性能低冲突时更高效高竞争时可能更高效
复杂度EF Core 内置支持,自动检测冲突需要手动管理锁和事务
数据竞争可能需重试或合并数据强制串行化,避免冲突

六、最佳实践

  1. 选择并发令牌
    优先使用 RowVersion(自动递增的二进制字段),而非业务字段。
    若使用业务字段(如 LastUpdatedTime),需确保其值在每次更新时被修改。
  2. 冲突处理策略
    客户端优先:强制覆盖数据库的值(需谨慎)。
    数据库优先:放弃当前修改,使用最新值。
    合并值:手动合并冲突字段(如用户编辑的字段优先)。
  3. 重试机制
    在分布式系统中,可为关键操作添加重试逻辑(如 Polly 库)。

总结

EF Core 的乐观并发通过版本号或字段值检测冲突,无需显式加锁,适合低竞争场景。通过 ConcurrencyCheckIsRowVersion() 配置并发令牌,并在冲突时通过 DbUpdateConcurrencyException 实现灵活的数据合并或重试逻辑。

  1. 乐观并发控制能够避免悲观锁带来的性能、死锁等问题,因此推荐使用并发控制而不是悲观锁。
  2. 如果有一个确定的字段要被进行并发控制,那么使用IsConcurrencyToken()把这个字段设置为并发令牌即可;
  3. 如果无法确定一个唯一的并发令牌列,那么就可以引入一个额外的属性设置为并发令牌,并且在每次更新数据库的时候,手动更新这一列的值;如果用的是SQL Server数据库,那么也可以采用RowVersion列,设置为并发令牌列。

http://www.kler.cn/a/613812.html

相关文章:

  • Vue学习笔记集--postcss-px-to-viewport
  • 性能比拼: Rust vs C++
  • 从泛读到精读:合合信息文档解析如何让大模型更懂复杂文档
  • SQLModel笔记
  • 视图、MySQL、触发器、存储过程、流程控制语句
  • 免去繁琐的手动埋点,Gin 框架可观测性最佳实践
  • SpringBoot 面试八股文
  • 【Pytorch实战教程】with torch.no_grad():
  • 【ArcGIS】ArcGIS10.6彻底卸载和ArcGIS10.2安装全过程
  • git push的时候出现无法访问的解决
  • Flink TaskManager之间数据传输(NetworkManager)
  • 服务器硬盘出现故障都有哪些解决方法?
  • Redis中的数据类型与适用场景
  • MATLAB 控制系统设计与仿真 - 31
  • NFC 碰一碰发视频的短视频剪辑功能源码技术开发
  • 编程技术水平横向和垂直发展的抉择全方位分析
  • 【HTML】验证与调试工具
  • 前端性能优化思路_场景题
  • chrome-driver安装
  • Hyperlane:Rust Web开发的未来,释放极致性能与简洁之美