Concurrency 并发API请求-数据库冲突

Concurrency 并发API请求-数据库冲突,concurrency,locking,asp.net-web-api2,race-condition,isolation-level,Concurrency,Locking,Asp.net Web Api2,Race Condition,Isolation Level,我对(任何)一个API端点(ASP.NET Web API 2)的并发请求有问题。当第二个请求在第一个请求完成之前开始处理时,数据库实体可能存在并发问题 示例: 1:请求R1开始处理,它将实体E1和E2读入内存 2:请求R2开始处理,它将实体E2和E1读入内存 3:请求R1更新对象E1 4:如果E1和E2已更新,请求R1将创建E3 5:请求R2更新对象E2 6:如果E1和E2已更新,请求R2将创建E3 问题在于,两个请求都在另一个请求更新实体E1和E2之前读取实体E1和E2。因此,两者都看到了过

我对(任何)一个API端点(ASP.NET Web API 2)的并发请求有问题。当第二个请求在第一个请求完成之前开始处理时,数据库实体可能存在并发问题

示例:
1:请求R1开始处理,它将实体E1和E2读入内存
2:请求R2开始处理,它将实体E2和E1读入内存
3:请求R1更新对象E1
4:如果E1和E2已更新,请求R1将创建E3
5:请求R2更新对象E2
6:如果E1和E2已更新,请求R2将创建E3

问题在于,两个请求都在另一个请求更新实体E1和E2之前读取实体E1和E2。因此,两者都看到了过时的、未更新的版本,而且E3从未创建过

所有请求都在单个数据库事务中处理(隔离级别“读取已提交”,SQL Server 2017上的Entity Framework 6)。 根据我目前的理解,我认为这个问题无法在数据访问/数据存储级别(例如,更严格的数据库隔离级别或乐观锁定)上解决,但需要在代码层次结构(请求级别)的更高级别上解决

我的建议是悲观地锁定在输入API方法时可以更新的所有实体。当启动另一个API请求时,该请求需要在任何已锁定的实体上设置写锁,它需要等待,直到释放所有以前的锁(队列)。请求通常在250毫秒内处理。
这可以通过修饰API方法的自定义属性来实现:
[LockEntity(typeof(ExampleEntity),exampleEntityId)]
。单个API方法可以用这些属性中的零到多个进行修饰。如果可能的话,锁定机制将与async/await一起工作,或者只是将线程置于睡眠状态。锁同步需要跨多个服务器工作,因此我在这里看到的最简单的选项是应用程序数据存储中的表示(单个、全局)。
这种方法很复杂,并且有一些性能缺点(阻塞请求处理,在锁处理期间执行DB查询),所以我愿意接受任何建议或其他想法

我的问题:
1.有没有描述这个问题的术语?这似乎是一件很平常的事,但到目前为止我还不知道。
2.关于如何解决这个问题,有什么最佳实践吗?

3.如果没有,我的建议有意义吗?

我不会绕过无状态的web和REST,也不会阻止请求。250ms是四分之一秒,在我看来相当慢。例如,100个并发请求将相互阻止,至少一个流将等待25秒!这会大大降低应用程序的速度

我看到一些应用程序(包括Atlassians Confluence)通知用户当前数据集已被其他用户更改。(有时也会提到另一个用户。)我会使用WebSocket,例如Signal,以同样的方式进行。其他应用程序,如service now,正在将远程更改合并到当前打开的数据集中

如果不想使用WebSocket,还可以尝试在服务器端进行合并或检查,并使用特定的HTTP状态代码和消息通知用户。同时告诉用户更改了什么,尝试合并并询问合并是否正常