EF Core 如何处理多个预订请求并发的理想方式
EF Core ideal way on how to handle concurrency for multiple reservation requests
概览
嗨,我正在开发一个使用 ASP.NET Core 作为后端和 Vue.js 的小应用程序作为前端。作为数据库访问,我为 SQL Server 选择了 Entity Framework Core。我的疑问是处理这种情况的理想方式:给定来自多个客户的同时2个或更多请求如何使办公室座位预订仅针对一个请求成功。
数据库结构
上下文如下:
- 我有一个预订 table 结构如下
(每一行代表一个预订,谁,哪里,什么时候)
- 在插入新行之前,我需要检查是否有位置。原始 SQL 查询可能如下所示
SELECT COUNT(*) FROM Reservations WHERE OfficeId = x and Date = 'y';
(x 和 y 是两个请求参数)
- 我会将之前查询返回的值与我要预订的办公室的最大容量进行比较
val_query < office_capacity
。这样我就知道至少有一个房间可以预订了。
我的疑惑
给定一个可用座位,如果同时处理两个在同一日期预订办公室座位的请求,并且都读取有预订空间,他们将在 Reservations table 即使理论上只有一个空间
以下是我一直在考虑的几个解决方案:
- 定制的基于锁的机制,可确保单次预订成功。
- 1.) 读取上述查询可用的位置数 2.) 如果与办公容量的比较成功,则执行插入 3.) 进行进一步的读取和比较以验证一切是否正确按顺序
对于第二个选项,当一个请求成功插入预订时,后续请求的读数将考虑它,如果没有更多的位置,则不会执行插入。另一方面,如果更多的请求同时读取相同的数据,它们将执行插入,但在第二次读取时它们将能够检查是否违反约束并删除 Reservations 中的行。在最后一个场景中,可用座位尚未分配给任何请求,但至少遵守办公室最大座位数的限制。
那么我的问题是什么?
我的问题是是否有设计模式或最佳实践来解决上述问题。
数据库事务是您的朋友。但是,ReadCommitted
的默认交易 isolation level 将无法满足您的需要。不保证您查询的数据——是否有预订房间——在您的工作完成之前不会改变;它只保证您收到的数据来自已提交的交易。通过使用 Serializable
隔离级别,您可以保证在处理事务的进程完成其工作(检查可用性然后添加预订)之前不会添加竞争预订。
This documentation 包含如何在 Entity Framework 上下文中使用事务的示例。这段代码应该给你大致的想法:
// start a transaction with the higher isolation level
using (var dbContextTransaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
try
{
// run your query to ensure no competing reservation exists
// use AsNoTracking to ensure the query runs against your database in the transaction context
context.Reservations.AsNoTracking()...;
// if it is valid, insert your reservations
context.Reservations.Add(...);
// run the insert statement
context.SaveChanges();
// commit the transaction
dbContextTransaction.Commit();
}
catch
{
// roll back the transaction if anything went wrong so that your database isn't locked
dbContextTransaction.Rollback();
throw;
}
}
概览
嗨,我正在开发一个使用 ASP.NET Core 作为后端和 Vue.js 的小应用程序作为前端。作为数据库访问,我为 SQL Server 选择了 Entity Framework Core。我的疑问是处理这种情况的理想方式:给定来自多个客户的同时2个或更多请求如何使办公室座位预订仅针对一个请求成功。
数据库结构
上下文如下:
- 我有一个预订 table 结构如下
(每一行代表一个预订,谁,哪里,什么时候) - 在插入新行之前,我需要检查是否有位置。原始 SQL 查询可能如下所示
SELECT COUNT(*) FROM Reservations WHERE OfficeId = x and Date = 'y';
(x 和 y 是两个请求参数) - 我会将之前查询返回的值与我要预订的办公室的最大容量进行比较
val_query < office_capacity
。这样我就知道至少有一个房间可以预订了。
我的疑惑
给定一个可用座位,如果同时处理两个在同一日期预订办公室座位的请求,并且都读取有预订空间,他们将在 Reservations table 即使理论上只有一个空间
以下是我一直在考虑的几个解决方案:
- 定制的基于锁的机制,可确保单次预订成功。
- 1.) 读取上述查询可用的位置数 2.) 如果与办公容量的比较成功,则执行插入 3.) 进行进一步的读取和比较以验证一切是否正确按顺序
对于第二个选项,当一个请求成功插入预订时,后续请求的读数将考虑它,如果没有更多的位置,则不会执行插入。另一方面,如果更多的请求同时读取相同的数据,它们将执行插入,但在第二次读取时它们将能够检查是否违反约束并删除 Reservations 中的行。在最后一个场景中,可用座位尚未分配给任何请求,但至少遵守办公室最大座位数的限制。
那么我的问题是什么?
我的问题是是否有设计模式或最佳实践来解决上述问题。
数据库事务是您的朋友。但是,ReadCommitted
的默认交易 isolation level 将无法满足您的需要。不保证您查询的数据——是否有预订房间——在您的工作完成之前不会改变;它只保证您收到的数据来自已提交的交易。通过使用 Serializable
隔离级别,您可以保证在处理事务的进程完成其工作(检查可用性然后添加预订)之前不会添加竞争预订。
This documentation 包含如何在 Entity Framework 上下文中使用事务的示例。这段代码应该给你大致的想法:
// start a transaction with the higher isolation level
using (var dbContextTransaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
try
{
// run your query to ensure no competing reservation exists
// use AsNoTracking to ensure the query runs against your database in the transaction context
context.Reservations.AsNoTracking()...;
// if it is valid, insert your reservations
context.Reservations.Add(...);
// run the insert statement
context.SaveChanges();
// commit the transaction
dbContextTransaction.Commit();
}
catch
{
// roll back the transaction if anything went wrong so that your database isn't locked
dbContextTransaction.Rollback();
throw;
}
}