我在使用 SQL Server 和实体框架的 .NET 6 项目中遇到问题。我的应用程序部署在 Kubernetes 上,因此有 3 个 Pod。我有多个用户尝试从数据库中获取下一个可用的“订单”记录(用户名= null,状态=“可用”)。当用户获取下一个订单时,他们会将其用户名分配给订单记录,然后使用
await _dbContext.SaveChangesAsync()
更新记录以反映该分配。我希望每个用户都有一个唯一的订单来查看,但有些用户获得相同的订单记录,从而导致并发问题。
我已经在捕获
DbUpdateConcurrencyException
的 Try/Catch 块中进行了保存,这似乎可以防止大多数并发问题。然而,仍然有一些操作没有被双重更新操作捕获并成功。这些操作在同一时间(不到 0.2 秒)在不同的 Pod 上发生。
var nextAvailableOrder = await _dbContext.Orders
.Where(o => o.Status == OrderStatus.Available && o.Username == null)
.OrderByDescending(o => o.Type)
.ThenBy(o => o.DateOfService)
.FirstOrDefaultAsync(cancellationToken);
if (nextAvailableOrder == null)
{
return null;
}
try
{
// Assign new user and transition Order to new status
// Order.Username = request.Username
// Order.Status = 'In Progress'
_stateEngine.MoveNext(nextAvailableOrder, OrderAction.GetNext, request.Username);
await _dbContext.SaveChangesAsync(cancellationToken);
return nextAvailableOrder;
}
// Handle concurrency issues
catch (DbUpdateConcurrencyException ex)
{
// Retry logic
}
我使用订单记录上的用户名字段作为并发令牌,但并行更新的问题仍然存在。欢迎任何建议。
您遇到的问题是,您对 SQL 进行了 2 次单独的调用,一次调用是为了查找下一项,另一次调用是为了更新用户名/状态(如果有效)。您需要将其变成单个 SQL 事务,最好具有适当的锁定。
我的建议是使用存储过程,它将返回下一个可用订单并同时更新状态。
或者,使用
ExecuteUpdate
执行更新,然后选择记录,如下所示。
var key = Guid.NewGuid();
var rowsUpdated = _dbContext.Orders
.Where(o => o.Status == OrderStatus.Available && o.Username == null)
.OrderByDescending(o => o.Type)
.ThenBy(o => o.DateOfService)
.Take(1) // untested, but I don't see why it shouldn't work since it's an IQueryable
.ExecuteUpdate(setters => setters
.SetProperty(o => o.Username, request.Username)
.SetProperty(o => o.Status, OrderStatus.Available)
.SetProperty(o => o.SomeKey, key.toString()) // need to add SomeKey to model
);
if (rowsUpdated == 1){
var nextOrder = _dbContext.Orders
.FirstOrDefault(o => o.SomeKey == key.toString());
}
相关文档:
https://learn.microsoft.com/en-us/ef/core/ saving/execute-insert-update-delete#executeupdate