mysql使用efcore实现乐观并发控制

发布时间 2023-06-06 20:09:50作者: .net&new

为了避免多个用户同时操作同一个资源造成的并发冲突问题,通常需要进行并发控制。

并发控制分为:乐观和悲观两策略

悲观:悲观并发控制一般采用行锁、表锁等 排它销对资源进行锁定,确保一个时间点只有一个用户在操作被锁定的资源。

 悲观并发控件的使用比较简单,仅对要进行并发控制的资源加上锁即可,但是这种锁是独占排它的,如果系统并发量很大,锁会严重影响性能,如果使用不当,甚至会导致死锁。因此,对于高并发系统,要尽量避免使用悲观策略,改为NoSQL(Redis)。如果必须使用数据库控制并发,尽量采用乐观并发控制。

乐观:乐观并发控制则允许多个用户同时操作同一个资源,但通过并发冲突检测避免并发操作。

和悲观并发控制相比,乐观并发控制不需要显示使用事务,而且也不需要使用锁。

乐观并发控制:

EF Core 使用并发令牌列实现乐观并发控制,并发令牌列就是需要防止并发冲突的列。

乐观并发有以下两种方式,一:并发令牌列,二:记录级并发控件

一、并发令牌列, 仅一个并发列,或者能确定并发列的情况下使用

public class House

   {
       public const string TABLE = "House";
       public long Id { getset; }
       public string Name { getset; }
       public string? Owner { getset; }
   }
protected override void OnModelCreating(ModelBuilder modelBuilder)
       {
           base.OnModelCreating(modelBuilder);
           //指定并发令牌列Owner
           modelBuilder.Entity<House>().Property(m => m.Owner).IsConcurrencyToken();
           // 配置表名映射
           modelBuilder.Entity<House>().ToTable(House.TABLE);
       }
 

2. 实现并发控制

[HttpPost("{name}")]
       public async Task<ActionResult> UpdateOwner(string name)
       {
           House house = await db.Houses.SingleAsync(m => m.Id == 1);
           if (string.IsNullOrEmpty(house.Owner))
           {//房子还在,开始抢房
               await Task.Delay(5000);
               house.Owner = name;
               try
               {
                   await db.SaveChangesAsync();
                   return Ok($"{name} 已经抢到房子。");
               }
               catch (DbUpdateConcurrencyException ex)//捕捉更新并发异常
               {//抢房失败
                   var entry = ex.Entries.First();
                   var dbValues = await entry.GetDatabaseValuesAsync();
                   string newOwner = dbValues.GetValue<string>(nameof(House.Owner));
                   return BadRequest($"并发冲突,房子已经被{newOwner}抢走");
               }
           }
           else
           {//房子已经被别人抢走了
               if (house.Owner == name.Trim())
               {
                   return Ok($"房子已经是你的了,不用抢了");
               }
               else
               {
                   return Ok($"很遗憾,这套房子已经被{house.Owner}抢走了");
               }
           }
       }

二、记录级并发控件,需更新多列,或者事先无法确定需要更新的列

 

 1. 实体类

 
 public class House
   {
       public const string TABLE = "House";
       public long Id { getset; }
       public string Name { getset; }
       public string? Owner { getset; }
       /// <summary>
       /// 时间戳,对应数据库 Timestamp 类型
       /// </summary>
       public byte[] RowVersion { getset; }
   }
 

2. 配置DB

 
protected override void OnModelCreating(ModelBuilder modelBuilder)
       {
           base.OnModelCreating(modelBuilder);
           //每次更新数据时,RowVersion列会自动生成新值
           modelBuilder.Entity<House>().Property(m => m.RowVersion).IsRowVersion();
           //设置组合主键
 
           // 配置表名映射
           modelBuilder.Entity<House>().ToTable(House.TABLE);
       }
 
转自:https://www.cnblogs.com/friend/p/16729756.html