【发布时间】:2018-07-10 16:57:48
【问题描述】:
我对(任何)一个 API 端点(ASP.NET Web API 2)的并发请求存在问题。当第二个请求在第一个请求完成之前开始处理时,数据库实体可能会出现并发问题。
示例:
1:请求R1开始处理,将实体E1和E2读入内存
2:请求R2开始处理,将实体E2和E1读入内存
3:请求 R1 更新对象 E1
4:请求 R1 创建 E3,如果 E1 和 E2 已经更新
5:请求 R2 更新对象 E2
6:请求R2创建E3,如果E1和E2已经更新了
问题是两个请求都在另一个请求更新实体 E1 和 E2 之前读取了它。所以两者都看到过时的、未更新的版本,并且永远不会创建 E3。
所有请求都在单个数据库事务中处理(隔离级别“已提交”,SQL Server 2017 上的实体框架 6)。 以我目前的理解,我认为这个问题无法在数据访问/数据存储级别(例如更严格的数据库隔离级别或乐观锁定)上解决,但需要在代码层次结构(请求级别)中更高。
我的建议是悲观地锁定所有在进入 API 方法时可以更新的实体。当另一个 API 请求启动时,它需要对任何已锁定的实体进行写锁定,它需要等到所有先前的锁定都被释放(队列)。一个请求的处理时间通常少于 250 毫秒。
这可以通过装饰 API 方法的自定义属性来实现:[LockEntity(typeof(ExampleEntity), exampleEntityId)]。单个 API 方法可以用零到多个这些属性进行修饰。如果可能,锁定机制将与 async/await 一起使用,或者只是让线程进入睡眠状态。 锁同步需要跨多个服务器工作,所以我在这里看到的最简单的选项是应用程序数据存储中的表示(单个,全局的)。
这种方法很复杂并且有一些性能劣势(阻塞请求处理,在锁定处理期间执行数据库查询),所以我愿意接受任何建议或其他想法。
我的问题:
1. 有没有描述这个问题的术语?这似乎是一件很常见的事情,但到目前为止我还不知道。
2. 有没有解决这个问题的最佳实践?
3. 如果没有,我的提议有意义吗?
【问题讨论】:
标签: concurrency locking asp.net-web-api2 race-condition isolation-level