You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Entity Framework布尔类型ConcurrencyToken并发控制失效排查

问题根因
  • 核心逻辑冲突:你明确要求「每个IdChain仅可被使用一次」,但事务末尾又将InUse改回false提交,等于用完的IdChain又变回可用状态,直接违背了业务规则,也给并发请求的修改留了可乘之机。
  • 布尔类型并发令牌的局限性:EF的并发令牌校验逻辑是生成UPDATE语句时,在WHERE条件中携带属性查询时的原始值。当你在同一个事务内先把InUse设为true、最后又改回false提交后,数据库中InUse的最终值和其他并发请求查询到的原始值都是false,后续请求的UPDATE语句的WHERE条件会匹配成功,自然不会触发DbUpdateConcurrencyException。
  • 快照读的时间差问题:默认隔离级别下,多个并发请求会同时查询到InUse = false的快照,后续修改会排队等待行锁,前序事务提交后如果InUse回到false,排队的修改就会全部执行成功。
解决方案

1. 修正业务逻辑(优先级最高)

既然要求每个IdChain仅可使用一次,事务末尾不要将用过的InUse改回false,永久标记为已使用即可,后续业务直接使用你新生成的MovementsChain实例:

// 直接删掉这行还原InUse状态的代码
// movementChain.InUse = false;

修改后只有第一个请求能成功将InUse设为true提交,后续请求要么查询时就发现InUse = true直接返回错误,要么UPDATE时WHERE条件匹配不到行,触发并发异常。

2. 替换并发令牌为版本号字段

布尔类型的并发令牌状态只有两种,很容易因为业务逻辑的来回修改导致并发校验失效,建议新增版本号字段做全局并发控制:

修改实体:

public partial class MovementsChain
{
    public Guid IdChain { get; set; }
    public int IdRegistry { get; set; }
    public DateTime Creation { get; set; }
    public bool InUse { get; set; }
    // 新增版本号字段,EF会自动在更新时校验+自增
    [Timestamp]
    public byte[] RowVersion { get; set; }
}

[Timestamp]标记的字段会自动作为全局并发令牌,只要行被修改过版本号就会变更,后续请求的更新必然会触发并发异常,不需要再单独把InUse设为并发令牌。

3. 查询时加悲观锁避免快照并发

如果不想改现有字段结构,可以在查询MovementsChain时就加行级更新锁,让第一个拿到锁的请求直接阻塞其他所有请求的查询,前序事务提交后,后续请求查询到的InUse已经是true,直接返回错误即可:

var movementChain = await Context.MovementsChain
    .OrderByDescending(e => e.Creation)
    // EF Core 7+ 支持直接用WithLock加锁提示,低版本可以用FromSqlRaw加WITH (UPDLOCK, HOLDLOCK)
    .WithLock(LockMode.Update, LockBehavior.Hold)
    .FirstOrDefaultAsync(e => e.IdRegister == register.IdRegister);

4. 调整校验顺序

把客户端传入IdChain的校验提前到查询逻辑里,避免查到不匹配的IdChain,减少无效锁占用:

// 先校验客户端传入的IdChain合法性
if (request.IdChain == Guid.Empty)
    return new TransactionResult(Error.IdChainNotSpecified);

var movementChain = await Context.MovementsChain
    .WithLock(LockMode.Update, LockBehavior.Hold)
    .OrderByDescending(e => e.Creation)
    .FirstOrDefaultAsync(e => 
        e.IdRegister == register.IdRegister 
        && e.IdChain == request.IdChain);

内容的提问来源于stack exchange,提问作者Cheshire Cat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.12 02:03:03