主页>技术社区>IT 技术>编程开发>.NET

EF Code 如何应对高并发

eIT.com.cn 2023/3/18 6:31:41 阅读 3 次

打印


1、高并发的情况,时常会发生数据不稳定的情况

  在看本节内容之前,请先看上一章SqlServer 高并发的情况下,如何利用锁保证数据的稳定性

  本节内容,也是具体讨论如何在EF中实现这些操作

2、场景模拟,同上一章,抢券

  EF 不考虑高并发的情况下,抢券代码为:

string _currOwner = Console.ReadLine();//当前用户
using var ctx = new MyDBContext();
var cop = ctx.Coupons.Single(x => x.Id == 2);
if (!string.IsNullOrEmpty(cop.Owner))
{   Console.WriteLine($"券被抢了");
}
else
{   cop.Owner = _currOwner;   Thread.Sleep(5000);   ctx.SaveChanges();   Console.WriteLine($"恭喜{_currOwner}抢到券{cop.Id}了");
}
Console.ReadLine();

  打开两个进程,让tom和jerry同时先后进行抢券,模拟出一个券同时被两个用户抢到的情况

  

  

  上图可用直观看出,都提示抢券成功,但是owner是晚一点点执行update的jerry,在实际生产中,无法给tom一个交代

3、解决并发问题

  3.1 通过updlock,悲观并发控制

string _currOwner = Console.ReadLine();//当前用户
using var ctx = new MyDBContext();
using var tx = ctx.Database.BeginTransaction();
FormattableString sql = $@"select * from Coupons with(updlock) where id=2";
var cop = ctx.Coupons.FromSqlInterpolated(sql).Single();
if (!string.IsNullOrEmpty(cop.Owner))
{   Console.WriteLine($"券被抢了");
}
else
{   cop.Owner = _currOwner;   Thread.Sleep(5000);   ctx.SaveChanges();   Console.WriteLine($"恭喜{_currOwner}抢到券{cop.Id}了");
}
tx.Commit();
Console.ReadLine();

  解决:但这个是排他锁,有可能造成线程卡顿问题

    

  3.2 通过定义鉴权字段,乐观并发控制

    CouponConfig添加配置

            builder.Property(x => x.Owner).IsConcurrencyToken();

    抢券代码:

string _currOwner = Console.ReadLine();//当前用户
using var ctx = new MyDBContext();
var cop = ctx.Coupons.Single(x => x.Id == 2);
if (!string.IsNullOrEmpty(cop.Owner))
{   Console.WriteLine($"券被抢了");
}
else
{   Thread.Sleep(5000);   try   {       cop.Owner = _currOwner;       await ctx.SaveChangesAsync();       Console.WriteLine($"恭喜{_currOwner}抢到券{cop.Id}了");   }   catch (DbUpdateConcurrencyException ex)   {       var entry = ex.Entries.First();       var dbValues = entry.GetDatabaseValues();       var newOwner = dbValues.GetValue<string>(nameof(Coupon.Owner));       Console.WriteLine($"并发冲突,{newOwner}已经抢到该券了");   }
}

    结果:

      

    根据update语句,可用看出where加了owner=旧值,来判断是否发生过更改

  3.3 添加数据版本标识

    如果无法定义一个明确的鉴权字段,那么可用通过新增一个字段,来标识数据来进行鉴权

    public class Coupon   {       public int Id { get; set; }       public string Name { get; set; }       public string? Description { get; set; }       public string? Owner { get; set; }       public byte[] RowVersion { get; set; } #迁移到数据库,类型为rowversion,当数据更新时,版本会自动递增   }

    迁移后数据库表代码

CREATE TABLE [dbo].[Coupons] (   [Id]          INT            IDENTITY (1, 1) NOT NULL,   [Name]        NVARCHAR (MAX) NOT NULL,   [Description] NVARCHAR (MAX) NULL,   [Owner]       NVARCHAR (MAX) NULL,   [RowVersion]  ROWVERSION     NOT NULL,   CONSTRAINT [PK_Coupons] PRIMARY KEY CLUSTERED ([Id] ASC)
);

    CouponConfig添加配置

            builder.Property(x => x.RowVersion).IsRowVersion();

    抢券代码同3.2

    结果:

  

  理论和3.2相同,where会做一个rowversion的旧值判断

 

  总结:这三种方法由浅入深,各有利弊,在并发量不大的情况下使用3.1,并发量较大的情况下使用3.2&3.3

 

  感谢关注!!

 

 

 

 

 

 

 

 

 

   

 






相关内容


热门栏目


特别声明


最新资讯
热讯排行



合作媒体友情链接
生活常识小贴士 软件开发教程 智慧城市生活网 息县通生活服务[移动版] 息县商圈[移动版] 美食菜谱
健康养生 法律知识 科技频道 电影影讯 留学考研学习 星座生肖|解梦说梦




关于我们 | 联系我们 | 合作媒体 | 使用条款 | 隐私权声明 | 版权声明

      Copyright © 2023 eIT.com.cn. All Rights Reserved. 豫ICP备2022012332号