|
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(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
感谢关注!!
来源:https://www.cnblogs.com/wskxy/archive/2023/03/17/17228975.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
|