当前位置:Gxlcms > 数据库问题 > C# 数据库并发的解决方案(通用版、EF版)

C# 数据库并发的解决方案(通用版、EF版)

时间:2021-07-01 10:21:17 帮助过:272人阅读

创建的数据库很简单,三张表:商品表,库存表,日志表

有了数据库,我们就创建C#项目,本项目采用C# DataBaseFirst 模式,结构如下:

技术分享图片

项目很简单,采用EF DataBaseFirst 模式很好构建。

 项目构建好了,下面我们模拟并发的发生?

主要代码如下(减少库存、插入日志):

技术分享图片
  1. #region 未做并发处理
  2. /// <summary>
  3. /// 模仿一个减少库存操作 不加并发控制
  4. /// </summary>
  5. public void SubMitOrder_3()
  6. {
  7. int productId = 1;
  8. using (BingFaTestEntities context = new BingFaTestEntities())
  9. {
  10. var InventoryLogDbSet = context.InventoryLog;
  11. var InventoryDbSet = context.Inventory;//库存表
  12. using (var Transaction = context.Database.BeginTransaction())
  13. {
  14. //减少库存操作
  15. var Inventory_Mol = InventoryDbSet.Where(A => A.ProductId == productId).FirstOrDefault();//库存对象
  16. Inventory_Mol.ProductCount = Inventory_Mol.ProductCount - 1;
  17. int A4 = context.SaveChanges();
  18. //插入日志
  19. InventoryLog LogModel = new InventoryLog()
  20. {
  21. Title = "插入一条数据,用于计算是否发生并发",
  22. };
  23. InventoryLogDbSet.Add(LogModel);
  24. context.SaveChanges();
  25. //1.5 模拟耗时
  26. Thread.Sleep(500); //消耗半秒钟
  27. Transaction.Commit();
  28. }
  29. }
  30. }
  31. #endregion
技术分享图片

此时我们 int productId=1 处加上断点,并运行程序(打开四个浏览器同时执行),如下:

技术分享图片

由上图可知,四个访问者同时访问这个未采用并发控制的方法,得到的结果如下:

技术分享图片

结果显示:日志生成四条数据,而库存量缺只减少1个。这个结果显然是不正确的,原因是因为发生了并发,其本质原因是脏读,误读,不可重读造成的。

那么,问题既然发生了,我们就想办法法解决,办法有两种,分别为:悲观锁方法、乐观锁方法。

悲观者方法:

悲观者方法(加了uodlock锁,锁定了更新操作,也就是说,一旦被锁定,其他访问者不允许访问此操作)类似这种方法,可以通过存储过程实现,在此不作解释了

技术分享图片

乐观者方法(通用版/存储过程实现):

在上述数据库脚本中,有字段叫做:VersionNum,类型为:TimeStamp。

字段 VersionNum 大家可以理解为版本号,版本号的作用是一旦有访问者修改数据,版本号的值就会相应发生改变。当然,版本号的同步更改是和数据库相关的,在SQLserver中会随着数据的修改同步更新版本号,但是在MySQL里就不会随着数据的修改而更改。因此,如果你采用的是MYSQL数据库,就需要写一个触发器,如下:

技术分享图片

技术分享图片

OK,了解了类型为Timestamp的字段,下面我们结合上述的小型数据库创建一个处理并发的存储过程,如下

技术分享图片
  1. create proc LockProc --乐观锁控制并发
  2. (
  3. @ProductId int,
  4. @IsSuccess bit=0 output
  5. )
  6. as
  7. declare @count as int
  8. declare @flag as TimeStamp
  9. declare @rowcount As int
  10. begin tran
  11. select @count=ProductCount,@flag=VersionNum from Inventory where ProductId=@ProductId
  12. update Inventory set ProductCount=@count-1 where VersionNum=@flag and ProductId=@ProductId
  13. insert into InventoryLog values(‘插入一条数据,用于计算是否发生并发‘)
  14. set @rowcount=@@ROWCOUNT
  15. if @rowcount>0
  16. set @IsSuccess=1
  17. else
  18. set @IsSuccess=0
  19. commit tran
技术分享图片

 这个存储过程很简单,执行两个操作:减少库存和插入一条数据。有一个输入参数:productId ,一个输出参数,IsSuccess。如果发生并发,IsSuccess的值为False,如果执行成功,IsSuccess值为True。

在这里,向大家说明一点:程序采用悲观锁,是串行的,采用乐观锁,是并行的。

也就是说:采用悲观锁,一次仅执行一个访问者的请求,待前一个访问者访问完成并释放锁时,下一个访问者会依次进入锁定的程序并执行,直到所有访问者执行结束。因此,悲观锁严格按照次序执行的模式能保证所有访问者执行成功。

采用乐观锁时,访问者是并行执行的,大家同时访问一个方法,只不过同一时刻只会有一个访问者操作成功,其他访问者执行失败。那么,针对这些执行失败的访问者怎么处理呢?直接返回失败信息是不合理的,用户体验不好,因此,需要定制一个规则,让执行失败的访问者重新执行之前的请求即可。

 时间有限,就不多写了...因为并发的控制是在数据库端存储过程,所以,C#代码也很简单。如下:

技术分享图片 View Code

在此,需要说明如下:

当IsSuccess的值为False时,应该重复执行该方法,我定的规则是重复请求十次,这样就很好的解决了直接反馈给用户失败的消息。提高了用户体验。

下面着重说下EF框架如何避免数据库并发,在讲解之前,先允许我引用下别人博客中的几段话:

在软件开发过程中,并发控制是确保及时纠正由并发操作导致的错误的一种机制。从 ADO.NET 到 LINQ to SQL 再到如今的 ADO.NET Entity Framework,.NET 都为并发控制提供好良好的支持方案。

相对于数据库中的并发处理方式,Entity Framework 中的并发处理方式实现了不少的简化。

在System.Data.Metadata.Edm 命名空间中,存在ConcurencyMode 枚举,用于指定概念模型中的属性的并发选项。
ConcurencyMode 有两个成员:

成员名称  说明
        None   在写入时从不验证此属性。 这是默认的并发模式。
        Fixed 在写入时始终验证此属性。

当模型属性为默认值 None 时,系统不会对此模型属性进行检测,当同一个时间对此属性进行修改时,系统会以数据合并方式处理输入的属性值。
当模型属性为Fixed 时,系统会对此模型属性进行检测,当同一个时间对属性进行修改时,系统就会激发OptimisticConcurrencyException 异常。

开发人员可以为对象的每个属性定义不同的 ConcurencyMode 选项,选项可以在*.Edmx找看到:

技术分享图片

 

Edmx文件用记事本打开如下:

技术分享图片 View Code

其实,在EF DataBaseFirst中,我们只需设置下类型为 TimeStamp 版本号的属性即可,如下:

技术分享图片

设置好了版本号属性后,你就可以进行并发测试了,当系统发生并发时,程序会抛出异常,而我们要做的就是要捕获这个异常,而后就是按照自己的规则,重复执行请求的方法,直至返回成功为止。

那么如何捕获并发异常呢?

在C#代码中需要使用异常类:DbUpdateConcurrencyException 来捕获,EF中具体用法如下:

技术分享图片
  1. public class SaveChangesForBF : BingFaTestEntities
  2. {
  3. public override int SaveChanges()
  4. {
  5. try
  6. {
  7. return base.SaveChanges();
  8. }
  9. catch (DbUpdateConcurrencyException ex)//(OptimisticConcurrencyException)
  10. {
  11. //并发保存错误
  12. return -1;
  13. }
  14. }
  15. }
技术分享图片

设置好属性后,EF会帮我们自动检测并发并抛出异常,我们用上述方法捕获异常后,就可以执行我们重复执行的规则了,具体代码如下:

技术分享图片 View Code

 至此,C#并发处理就讲解完了,是不是很简单呢?

项目源码地址:http://download.csdn.net/download/wolongbb/9977216

@陈卧龙的博客

 

C# 数据库并发的解决方案(通用版、EF版)

标签:row   串行   用户   项目   并发控制   png   用户体验   monitor   简化   

人气教程排行