EF Core 如何应对高并发

EF Core 如何应对高并发

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

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

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

代码语言:javascript代码运行次数:0运行复制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.1、通过updlock,悲观并发控制

代码语言:javascript代码运行次数:0运行复制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添加配置

代码语言:javascript代码运行次数:0运行复制builder.Property(x => x.Owner).IsConcurrencyToken();抢券代码:

代码语言:javascript代码运行次数:0运行复制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、添加数据版本标识

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

代码语言:javascript代码运行次数:0运行复制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,当数据更新时,版本会自动递增

}迁移后数据库表代码

代码语言:javascript代码运行次数:0运行复制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添加配置

代码语言:javascript代码运行次数:0运行复制builder.Property(x => x.RowVersion).IsRowVersion();抢券代码同3.2

结果:

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

总结

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

相关阅读

格兰仕空调可以使用多少年(格兰仕空调寿命及保养方法)
神石降落 《卧龙吟》神装风暴再次掀起
365bet日博

神石降落 《卧龙吟》神装风暴再次掀起

⌚ 07-09 👁️ 5807
既可防身也可传情,欧洲人曾这样使用伞
365bet足球赌博

既可防身也可传情,欧洲人曾这样使用伞

⌚ 09-02 👁️ 8189