EF Code 如何应对高并发

打印 上一主题 下一主题

主题 637|帖子 637|积分 1911

1、高并发的情况,时常会发生数据不稳定的情况
  在看本节内容之前,请先看上一章SqlServer 高并发的情况下,如何利用锁保证数据的稳定性
  本节内容,也是具体讨论如何在EF中实现这些操作
2、场景模拟,同上一章,抢券
  EF 不考虑高并发的情况下,抢券代码为:
  1. string _currOwner = Console.ReadLine();//当前用户
  2. using var ctx = new MyDBContext();
  3. var cop = ctx.Coupons.Single(x => x.Id == 2);
  4. if (!string.IsNullOrEmpty(cop.Owner))
  5. {
  6.     Console.WriteLine($"券被抢了");
  7. }
  8. else
  9. {
  10.     cop.Owner = _currOwner;
  11.     Thread.Sleep(5000);
  12.     ctx.SaveChanges();
  13.     Console.WriteLine($"恭喜{_currOwner}抢到券{cop.Id}了");
  14. }
  15. Console.ReadLine();
复制代码
  打开两个进程,让tom和jerry同时先后进行抢券,模拟出一个券同时被两个用户抢到的情况
  
  
  上图可用直观看出,都提示抢券成功,但是owner是晚一点点执行update的jerry,在实际生产中,无法给tom一个交代
3、解决并发问题
  3.1 通过updlock,悲观并发控制
  1. string _currOwner = Console.ReadLine();//当前用户
  2. using var ctx = new MyDBContext();
  3. using var tx = ctx.Database.BeginTransaction();
  4. FormattableString sql = $@"select * from Coupons with(updlock) where id=2";
  5. var cop = ctx.Coupons.FromSqlInterpolated(sql).Single();
  6. if (!string.IsNullOrEmpty(cop.Owner))
  7. {
  8.     Console.WriteLine($"券被抢了");
  9. }
  10. else
  11. {
  12.     cop.Owner = _currOwner;
  13.     Thread.Sleep(5000);
  14.     ctx.SaveChanges();
  15.     Console.WriteLine($"恭喜{_currOwner}抢到券{cop.Id}了");
  16. }
  17. tx.Commit();
  18. Console.ReadLine();
复制代码
  解决:但这个是排他锁,有可能造成线程卡顿问题
    
  3.2 通过定义鉴权字段,乐观并发控制
    CouponConfig添加配置
  1.             builder.Property(x => x.Owner).IsConcurrencyToken();
复制代码
    抢券代码:
  1. string _currOwner = Console.ReadLine();//当前用户
  2. using var ctx = new MyDBContext();
  3. var cop = ctx.Coupons.Single(x => x.Id == 2);
  4. if (!string.IsNullOrEmpty(cop.Owner))
  5. {
  6.     Console.WriteLine($"券被抢了");
  7. }
  8. else
  9. {
  10.     Thread.Sleep(5000);
  11.     try
  12.     {
  13.         cop.Owner = _currOwner;
  14.         await ctx.SaveChangesAsync();
  15.         Console.WriteLine($"恭喜{_currOwner}抢到券{cop.Id}了");
  16.     }
  17.     catch (DbUpdateConcurrencyException ex)
  18.     {
  19.         var entry = ex.Entries.First();
  20.         var dbValues = entry.GetDatabaseValues();
  21.         var newOwner = dbValues.GetValue<string>(nameof(Coupon.Owner));
  22.         Console.WriteLine($"并发冲突,{newOwner}已经抢到该券了");
  23.     }
  24. }
复制代码
    结果:
      
    根据update语句,可用看出where加了owner=旧值,来判断是否发生过更改
  3.3 添加数据版本标识
    如果无法定义一个明确的鉴权字段,那么可用通过新增一个字段,来标识数据来进行鉴权
  1.     public class Coupon
  2.     {
  3.         public int Id { get; set; }
  4.         public string Name { get; set; }
  5.         public string? Description { get; set; }
  6.         public string? Owner { get; set; }
  7.         public byte[] RowVersion { get; set; } #迁移到数据库,类型为rowversion,当数据更新时,版本会自动递增
  8.     }
复制代码
    迁移后数据库表代码
  1. CREATE TABLE [dbo].[Coupons] (
  2.     [Id]          INT            IDENTITY (1, 1) NOT NULL,
  3.     [Name]        NVARCHAR (MAX) NOT NULL,
  4.     [Description] NVARCHAR (MAX) NULL,
  5.     [Owner]       NVARCHAR (MAX) NULL,
  6.     [RowVersion]  ROWVERSION     NOT NULL,
  7.     CONSTRAINT [PK_Coupons] PRIMARY KEY CLUSTERED ([Id] ASC)
  8. );
复制代码
    CouponConfig添加配置
  1.             builder.Property(x => x.RowVersion).IsRowVersion();
复制代码
    抢券代码同3.2
    结果:
  
  理论和3.2相同,where会做一个rowversion的旧值判断
 
  总结:这三种方法由浅入深,各有利弊,在并发量不大的情况下使用3.1,并发量较大的情况下使用3.2&3.3
 
  感谢关注!!
 
 
 
 
 
 
 
 
 
   
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

冬雨财经

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表