
我们在项目当中,经常因为业务原因需要对数据一致性进行控制,在高并发场景下,更需要使用悲观或乐观锁进行进一步设计。
悲观锁:
所谓悲观锁,就是在进行操作时针对记录加上排他锁,这样其他事务如果想操作该记录,需要等待锁的释放。
悲观锁在处理并发量和频繁访问时,等待时间比较长,冲突概率高,并发性能不好。
乐观锁
在提交对记录的更改时才将对象锁住,提交前需要检查数据的完整性
据此,我们正好了解一下EFCore现行并发控制策略。
默认情况下,EFCore实现了乐观锁控制,在保存数据时会进行一致性校验,分为两种模式:
[ConcurrencyCheck]
通过标识 [ConcurrencyCheck] 来检查数据一致性。
假如,我们有以下表:
public class Goods {
public Guid Id { get; set; }
[ConcurrencyCheck]
public DateTime ExpirationDate {set; get;}
[ConcurrencyCheck]
public int Status {set; get;}
}
我们标识了过期日期和状态为一组乐观锁。
2021年1月1日,张三查询到过期日期为2021年1月2日的A商品已入库0,想要修改商品状态为销售入库1。
此时,李四修改了A商品过期日期为2021年1月1日,实际已经过期。
那么张三在SaveChange的时候,会去检查商品的过期日期和状态是不是一开始查出来的样子,就会执行下面的语句:
update Goods set Status=1 where ExpirationDate ='2021-01-02' and Status = 0
这个时候,因为过期日期已经被改过,所以保存失败,这样我们就可以保证在商品过期日期修改后,正在出库的商品能够得到检查,而不是以读取到的数据为依据去判断更新。
同理,如果有人已经修改了状态Status,一样会触发验证,确保不会被二次重复处理。
另外一种更常用的,就是使用rowversion,给每行加一个版本,每次更新行数据时会同步更新rowversion。
EFCore在 SaveChange 的时候,会去验证 rowversion 是否和查询出来的一致,与上述流程同效,只不过验证的字段不一样。
EFCore的具体做法是在类中添加 1632960096 特性 和 TimeStamp 字段,如下:
[Timestamp]
public Byte[] TimeStamp { get; set; }
当验证发生异常时,会抛出 DbUpdateConcurrencyException 异常,我们可以通过catch DbUpdateConcurrencyException 来实现具体的处理逻辑。
比如:
相关链接:
EFCore 并发冲突处理
EFCore 数据注释