在 EF Core 5 中插入之前锁定 table
Lock table before inserting in EF Core 5
我有以下场景:
- 用户可以创建具有给定金额(例如:500 美元)的订单
- 一天可以添加的总订单金额有限制(例如:最多 2000 美元/天)
目前,在创建新订单时,该需求实现如下:
var newOrder = /* logic for creating the new order */;
var orders = _ordersRepository.GetAllBy(userId, date); // get the orders from the db
var totalAmount = orders.Sum(o => o.Amount);
if(totalAmount < MaximumAmount) {
newOrder.IsApproved = true;
}
else {
newOrder.IsApproved = false;
}
_ordersRepository.Add(newOrder);
_ordersRepository.SaveChanges(); // insert into the db
这种方法的问题在于它无法在以下场景中正确处理并发插入:
- 最大订单限制:2000$
- 在一秒钟内,用户发送了 10 个请求,要求创建 10 个新订单,每个订单价值 500 美元
请求是并发处理的,由于时间短,当前实施的检查是在新订单保存到数据库之前执行的,因此允许创建所有这些请求。最终用户超过最大限制结束。
我怎样才能解决这个问题,最好是不必多次调用 SaveChanges
?我正在使用 Entity Framework Core 5 和 SQL 服务器。
感谢评论中的建议!
我试过使用 IsolationLevel
设置为 Serializable
的事务,但后来我意识到这会锁定太多 table(问题中的示例是一个虚拟的,实际实现更复杂)。
我同意有时在数据库中使用这种逻辑可能更容易,但是为此添加存储过程会破坏当前的一致性,并且很可能为其他存储过程敞开大门。我并不是说存储过程不好,只是在我目前的情况下,即使没有它们实现这个有点 harder/more 复杂,但出于一致性原因,我相信这是值得的。
我以
结束的解决方案
我已将流程分为 2 个步骤,如下所示:
// step 1
var newOrder = /* logic for creating the new order */;
_ordersRepository.Add(newOrder);
_ordersRepository.SaveChanges(); // insert into the db
// step 2
var orders = _ordersRepository.GetAllBy(userId, date); // get the orders from the db
var totalAmount = orders.Sum(o => o.Amount);
if(totalAmount < MaximumAmount) {
newOrder.IsApproved = true;
}
_ordersRepository.Update(newOrder);
_ordersRepository.SaveChanges(); // update the new order
- 第 1 步只是创建新订单并将其插入数据库,
IsApproved
标志保留为默认值,即 false
。
- 第 2 步执行每日限额验证,如果检查通过,
IsApproved
标记为 true
。
我知道这不是真正的解决方案,而是一种解决方法。锁定 table 可能会对性能产生太大影响,尤其是当给定的 table 被多个应用程序功能使用时。使用此解决方案,即使在第 2 步中出现问题,订单将保留 IsApproved=false
,因此不会产生任何影响,用户可以稍后重试,或者支持人员可以处理.
我有以下场景:
- 用户可以创建具有给定金额(例如:500 美元)的订单
- 一天可以添加的总订单金额有限制(例如:最多 2000 美元/天)
目前,在创建新订单时,该需求实现如下:
var newOrder = /* logic for creating the new order */;
var orders = _ordersRepository.GetAllBy(userId, date); // get the orders from the db
var totalAmount = orders.Sum(o => o.Amount);
if(totalAmount < MaximumAmount) {
newOrder.IsApproved = true;
}
else {
newOrder.IsApproved = false;
}
_ordersRepository.Add(newOrder);
_ordersRepository.SaveChanges(); // insert into the db
这种方法的问题在于它无法在以下场景中正确处理并发插入:
- 最大订单限制:2000$
- 在一秒钟内,用户发送了 10 个请求,要求创建 10 个新订单,每个订单价值 500 美元
请求是并发处理的,由于时间短,当前实施的检查是在新订单保存到数据库之前执行的,因此允许创建所有这些请求。最终用户超过最大限制结束。
我怎样才能解决这个问题,最好是不必多次调用 SaveChanges
?我正在使用 Entity Framework Core 5 和 SQL 服务器。
感谢评论中的建议!
我试过使用 IsolationLevel
设置为 Serializable
的事务,但后来我意识到这会锁定太多 table(问题中的示例是一个虚拟的,实际实现更复杂)。
我同意有时在数据库中使用这种逻辑可能更容易,但是为此添加存储过程会破坏当前的一致性,并且很可能为其他存储过程敞开大门。我并不是说存储过程不好,只是在我目前的情况下,即使没有它们实现这个有点 harder/more 复杂,但出于一致性原因,我相信这是值得的。
我以
结束的解决方案我已将流程分为 2 个步骤,如下所示:
// step 1
var newOrder = /* logic for creating the new order */;
_ordersRepository.Add(newOrder);
_ordersRepository.SaveChanges(); // insert into the db
// step 2
var orders = _ordersRepository.GetAllBy(userId, date); // get the orders from the db
var totalAmount = orders.Sum(o => o.Amount);
if(totalAmount < MaximumAmount) {
newOrder.IsApproved = true;
}
_ordersRepository.Update(newOrder);
_ordersRepository.SaveChanges(); // update the new order
- 第 1 步只是创建新订单并将其插入数据库,
IsApproved
标志保留为默认值,即false
。 - 第 2 步执行每日限额验证,如果检查通过,
IsApproved
标记为true
。
我知道这不是真正的解决方案,而是一种解决方法。锁定 table 可能会对性能产生太大影响,尤其是当给定的 table 被多个应用程序功能使用时。使用此解决方案,即使在第 2 步中出现问题,订单将保留 IsApproved=false
,因此不会产生任何影响,用户可以稍后重试,或者支持人员可以处理.