EF Core 悲观与乐观并发控制、并发令牌

EF Core 悲观与乐观并发控制、并发令牌

EF Core 悲观并发控制(不推荐使用,EF Core也没有对其进行封装)

并发控制的概念

1.并发控制:避免多个用户同时操作资源造成的并发冲突问题。例如,统计点击量,秒杀,抢票

2.最好的解决方案:非数据库解决方案。

3.数据库层面的两种策略,悲观,乐观。

1.悲观并发控制一般采用行锁、表锁等排他锁对资源进行锁定,确保同时只有一个使用者操作被锁定的资源

2.EF Core没有封装悲观并发控制的使用,需要开发人员编写原生sql语句来使用悲观并发控制,不同数据库的语法不一样。

House与HouseConfig
 class House
 {
  public long Id { get; set; }
  public string Name { get; set; }
  public string? Owner { get; set; }
 }
 
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore.Metadata.Builders;
 
 class HouseConfig : IEntityTypeConfiguration<House>
 {
  public void Configure(EntityTypeBuilder<House> builder)
  {
  builder.ToTable("T_Houses");
  builder.Property(h => h.Name).IsUnicode();
  }
 }
 
Program
 using Microsoft.EntityFrameworkCore;
 
 Console.WriteLine("请输入您的姓名");
 string name = Console.ReadLine();
 using MyDbContext ctx = new MyDbContext();
 //开启事务
 using var tx = await ctx.Database.BeginTransactionAsync();
 Console.WriteLine("准备Select " + DateTime.Now.TimeOfDay);
 //select xxx for update 是MySQL的 其他数据库不一样需要自己单独了解
 var h1 = await ctx.Houses.FromSqlInterpolated($"select * from T_Houses where Id=1 for update")
    .SingleAsync();
 Console.WriteLine("完成Select " + DateTime.Now.TimeOfDay);
 if (string.IsNullOrEmpty(h1.Owner))
 {//房子没有所属 则可以抢购
     //为了模拟并发 等待时间
     await Task.Delay(5000);
     h1.Owner = name;
     await ctx.SaveChangesAsync();
     Console.WriteLine("抢到手了");
 }
 else
 {
     if (h1.Owner == name)
    {
         Console.WriteLine("这个房子已经是你的了,不用抢");
    }
     else
    {
         Console.WriteLine($"这个房子已经被{h1.Owner}抢走了");
    }
 }
 //提交事务
 await tx.CommitAsync();
 Console.ReadKey();

测试:编译完成后去对应的目录下找到对应的exe文件,运行2个,分别输入2个名字,例如Tom,Jerry

总结:

1.悲观并发控制的使用比较简单;

2.锁是独占、排他的,如果系统并发量很大的话,会严重影响性能,如果使用不当的话,甚至会导致死锁

3.不同的数据库的语法不一样

EF Core 乐观并发控制、并发令牌

并发令牌就是对当前一组数据(简单理解一行)的一个版本标记器(类似于软件版本的意思)

例如我要对这个House的所有者Owner进行修改,当这个房子进行预售的时候,Tom,Jerry进行抢购的时候,如果Owner不为空的时候,是应该修改失败的

 --此时Owner是空的情况下 (Tom)
 select * from T_Houses where id=1
 Update T_Houses set Owner='Tom' where id=1 and Owner='空'
 --Tom已经抢到 Owner=Tom
 
 --程序同时进行 (Jerry也在抢购) Owner为空
 select * from T_Houses where id=1
 --此时修改的时候 Owner=Tom 更新失败 EF Core抛出异常DbUpdateConcurrencyException对它进行catch即可
 Update T_Houses set Owner='Jerry' where id=1 and Owner='空'
 --此时Owner就是 并发令牌
House与HouseConfig
 class House
 {
  public long Id { get; set; }
  public string Name { get; set; }
  public string? Owner { get; set; }
 }
 
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore.Metadata.Builders;
 
 class HouseConfig : IEntityTypeConfiguration<House>
 {
  public void Configure(EntityTypeBuilder<House> builder)
  {
  builder.ToTable("T_Houses");
  builder.Property(h => h.Name).IsUnicode();
  builder.Property(h => h.Owner).IsConcurrencyToken();
  }
 }
 
Program
 using Microsoft.EntityFrameworkCore;
 
 Console.WriteLine("请输入您的姓名");
 string name = Console.ReadLine();
 using MyDbContext ctx = new MyDbContext();
 var h1 = await ctx.Houses.SingleAsync(h => h.Id == 1);
 if (string.IsNullOrEmpty(h1.Owner))
 {
  await Task.Delay(5000);
  h1.Owner = name;
  try
  {
  await ctx.SaveChangesAsync();
  Console.WriteLine("抢到手了");
  }
  catch (DbUpdateConcurrencyException ex)
  {
  var entry = ex.Entries.First();
  var dbValues = await entry.GetDatabaseValuesAsync();
  string newOwner = dbValues.GetValue<string>(nameof(House.Owner));
  Console.WriteLine($"并发冲突,被{newOwner}提前抢走了");
  }
 }
 else
 {
  if (h1.Owner == name)
  {
  Console.WriteLine("这个房子已经是你的了,不用抢");
  }
  else
  {
  Console.WriteLine($"这个房子已经被{h1.Owner}抢走了");
  }
 }
 Console.ReadLine();

ABA 问题

空-->Tom-->空-->Jerry

Guid->Tom-->Guid-->Jerry(更新失败) 不会产生这种ABA问题,因为并发令牌的每次生成的是唯一的

当Tom抢购之后,Tom又卖给了开发商 此时Owner=空 如果此时 Jerry更新数据,这种情况下是可以更新成功的,如果自己的系统不介意这种,可以不处理。如果介意,SQL server可以用RowVersion

 //实体类属性加上
 public byte[] RowVersion { get; set; }
 //在对应的HouseConfig里面加上
 builder.Property(h => h.RowVersion).IsRowVersion();
总结:

1.乐观并发控制能够避免悲观锁带来的性能、死锁等问题,因此推荐使用乐观锁并发控制而不是悲观锁

2.如果有一个确定的字段要被进行并发控制,那么使用IsConcurrencyToken()把这个字段设置为并发令牌即可;

3.如果无法确定一个唯一的并发令牌列,那么就可以引入一个额外的属性设置为并发令牌,并且每次更新数据的时候,手动更新这一列如果用的是SQL server数据库,那么也可以采用IsRowVersion列,这样就不用开发者手动来每次更新数据的时候,手动更新并发令牌的值了

 
posted @ 2022-07-29 00:02  忽如一夜娇妹来  阅读(465)  评论(0编辑  收藏  举报