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

EF Core行/表锁与隔离级别:展位预订并发控制方案咨询

问题解决方案

你不需要使用Serializable隔离级别,这个方案并发性能差,且无法完全解决重复预订问题。最优实现是搭配UPDLOCK+ROWLOCK行级提示,配合默认的Read Committed隔离级别即可完全满足需求。


为什么原方案不可行

Serializable作为最高隔离级别,会对查询覆盖的键范围加范围锁,不只会锁定你要操作的单条展位记录,还会阻塞同表其他无关展位的写入操作,并发性能极差。
更关键的是,你现有代码中默认的EF Core查询在Serializable级别下加的是共享读锁,共享锁在读取完成后就会释放,不会持续到事务结束,两个并发请求完全可能同时读到同一条未预订的展位记录,后续都执行Purchase()逻辑更新状态,依旧会出现重复预订问题。


UPDLOCK+ROWLOCK的适配性说明

这两个查询提示的特性完全匹配你的核心诉求:

  • UPDLOCK为更新锁:查询时直接对目标行加更新锁,同一时间仅允许一个事务持有同一行的更新锁,其他同样执行购买逻辑、加更新锁查询同一条展位的请求会被直接阻塞,直到当前事务提交/回滚,从根源上避免两个请求同时读到未预订状态的问题。
  • 更新锁和普通读操作的共享锁完全兼容:同表其他展位的操作、不加更新锁的普通查询(比如展位列表展示、展位信息公开查询)不会被阻塞,不会影响其他业务场景。
  • ROWLOCK明确指定锁粒度为行级,避免数据库查询优化器自动将锁升级为页锁、表锁,进一步降低对无关操作的影响。
  • 所有锁会持续持有到事务结束才释放,完整覆盖你读取记录、校验预订状态、更新预订标记、保存数据的全流程,不存在锁提前释放的漏洞。

具体代码实现

事务不需要指定Serializable级别,使用数据库默认的Read Committed即可。

SQL Server 版本

SQL Server需要显式指定锁提示,通过原生SQL拼接查询即可:

using var tx = await context.Database.BeginTransactionAsync();

// 查询时加行级更新锁
var product = await context.SponsorshipProducts
    .FromSqlRaw("SELECT * FROM SponsorshipProducts WITH (UPDLOCK, ROWLOCK) WHERE Id = {0}", productId)
    .FirstOrDefaultAsync();

if (product == null)
{
    await tx.RollbackAsync();
    // 处理展位不存在的业务逻辑
    return;
}

product.Purchase(); // 内部校验预订状态,未预订则设置ReservedById等标记
await context.SaveChangesAsync();
await tx.CommitAsync();

PostgreSQL/MySQL 版本

这类数据库使用SELECT ... FOR UPDATE语法实现行级排他读,EF Core 5.0+内置了对应支持,不需要写原生SQL:

using var tx = await context.Database.BeginTransactionAsync();

var product = await context.SponsorshipProducts
    .Where(x => x.Id == productId)
    .ForUpdate() // 等价于加行级更新锁
    .FirstOrDefaultAsync();

if (product == null)
{
    await tx.RollbackAsync();
    // 处理展位不存在的业务逻辑
    return;
}

product.Purchase();
await context.SaveChangesAsync();
await tx.CommitAsync();

额外兜底方案

为了彻底避免极端场景(比如漏加锁的业务代码、直接操作数据库的修改请求)导致的重复预订,可以在数据库层面加过滤唯一索引,从存储层做最后一层校验。以SQL Server为例:

CREATE UNIQUE INDEX IX_SponsorshipProducts_Reserved_Status
ON SponsorshipProducts(Id)
WHERE ReservedById IS NOT NULL;

该索引仅对已预订的展位记录生效,未预订的记录不受影响,性能开销极小,可以保证同一条展位永远只会存在一个有效的预订记录。


内容的提问来源于stack exchange,提问作者Chris Kooken

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 01:51:25