mysql使用efcore实现乐观并发控制
1. 原理
乐观并发的原理: update GoodsInfo set goodOwner=新值 where id=01 and goodOwner=旧值
剖析:当Update的时候,如果数据库中的goodOwner值已经被其他操作者更新为其他值了,那么where语句的值就会为false,因此这个Update语句影响的行数就是0,EF Core就知道“发生并发冲突”了, 因此SaveChanges()方法就会抛出DbUpdateConcurrencyException异常。
为了避免多个用户同时操作同一个资源造成的并发冲突问题,通常需要进行并发控制。
并发控制分为:乐观和悲观两策略
悲观:悲观并发控制一般采用行锁、表锁等 排它销对资源进行锁定,确保一个时间点只有一个用户在操作被锁定的资源。
悲观并发控件的使用比较简单,仅对要进行并发控制的资源加上锁即可,但是这种锁是独占排它的,如果系统并发量很大,锁会严重影响性能,如果使用不当,甚至会导致死锁。因此,对于高并发系统,要尽量避免使用悲观策略,改为NoSQL(Redis)。如果必须使用数据库控制并发,尽量采用乐观并发控制。
乐观:乐观并发控制则允许多个用户同时操作同一个资源,但通过并发冲突检测避免并发操作。
和悲观并发控制相比,乐观并发控制不需要显示使用事务,而且也不需要使用锁。
乐观并发控制:
EF Core 使用并发令牌列实现乐观并发控制,并发令牌列就是需要防止并发冲突的列。
乐观并发有以下两种方式,一:并发令牌列,二:记录级并发控件
一、并发令牌列, 仅一个并发列,或者能确定并发列的情况下使用
public
class
House
{
public
const
string
TABLE =
"House"
;
public
long
Id {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
string
? Owner {
get
;
set
; }
}
protected
override
void
OnModelCreating(ModelBuilder modelBuilder)
{
base
.OnModelCreating(modelBuilder);
//指定并发令牌列Owner
modelBuilder.Entity<House>().Property(m => m.Owner).IsConcurrencyToken();
// 配置表名映射
modelBuilder.Entity<House>().ToTable(House.TABLE);
}
2. 实现并发控制
[HttpPost(
"{name}"
)]
public
async Task<ActionResult> UpdateOwner(
string
name)
{
House house = await db.Houses.SingleAsync(m => m.Id == 1);
if
(
string
.IsNullOrEmpty(house.Owner))
{
//房子还在,开始抢房
await Task.Delay(5000);
house.Owner = name;
try
{
await db.SaveChangesAsync();
return
Ok($
"{name} 已经抢到房子。"
);
}
catch
(DbUpdateConcurrencyException ex)
//捕捉更新并发异常
{
//抢房失败
var
entry = ex.Entries.First();
var
dbValues = await entry.GetDatabaseValuesAsync();
string
newOwner = dbValues.GetValue<
string
>(nameof(House.Owner));
return
BadRequest($
"并发冲突,房子已经被{newOwner}抢走"
);
}
}
else
{
//房子已经被别人抢走了
if
(house.Owner == name.Trim())
{
return
Ok($
"房子已经是你的了,不用抢了"
);
}
else
{
return
Ok($
"很遗憾,这套房子已经被{house.Owner}抢走了"
);
}
}
}
二、记录级并发控件,需更新多列,或者事先无法确定需要更新的列
1. 实体类
public
class
House
{
public
const
string
TABLE =
"House"
;
public
long
Id {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
string
? Owner {
get
;
set
; }
/// <summary>
/// 时间戳,对应数据库 Timestamp 类型
/// </summary>
public
byte
[] RowVersion {
get
;
set
; }
}
2. 配置DB
protected
override
void
OnModelCreating(ModelBuilder modelBuilder)
{
base
.OnModelCreating(modelBuilder);
//每次更新数据时,RowVersion列会自动生成新值
modelBuilder.Entity<House>().Property(m => m.RowVersion).IsRowVersion();
//设置组合主键
// 配置表名映射
modelBuilder.Entity<House>().ToTable(House.TABLE);
}
本文来自博客园,作者:.net&new,转载请注明原文链接:https://www.cnblogs.com/wugh8726254/p/17461599.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)