引言,数据并发冲突这是一篇公司内部研发人员的指导文章,写的并不是很深,其内部的机制讲述很少,完全是实践使用的干货。
在一些场景,需要避免并发的产生,比如充值,扣费等,如果产生并发,可能金额增加结果不正确。
简单说下数据库的并发处理:
- 乐观并发:当用户阅读时,行不会被锁定。当用户试图更新这一行时,系统必须确定该记录是否被另一个用户修改过,因为它被读取了。
- 悲观并发: 悲观并发包括锁行,以防止其他用户以影响当前用户的方式修改数据。
在需要处理并发冲突的表内增加 TS 字段
//EF定义DbContext时entity.Property(e => e.Ts) .HasColumnName("TS") .HasColumnType("timestamp") .HasDefaultValueSql("CURRENT_TIMESTAMP"); protected override void OnModelCreating(ModelBuilder modelBuilder) { //设置时间戳并发控制ExtendBuilder.OnModelCreating(modelBuilder); } //扩展类,定义ts字段为 IsConcurrencyTokenpublic class ExtendBuilder{public static void OnModelCreating(ModelBuilder modelBuilder){var concurrencyType = new Type[] { typeof(表1实体定义类型), typeof(表1) };foreach (var type in concurrencyType) { modelBuilder.Entity(type, a => { a.Property("Ts").IsConcurrencyToken(); }); } } }复制代码2、悲观锁\行级锁
建议为了避免锁表的风险,仅仅只在where条件是主键或唯一索引时采用。
这里以排他锁为例:
需要配合dappercontext进行select * from table where id = 1 for update;当然EF Context也是可以执行的,使用FromRawSql即可。
3、采用分布式RedisKey分布式ResisKey,应该之前的博客有讲过实现。 这里就仅仅放出使用的例子。
List<DistributedLock> locks = new List<DistributedLock>();try{foreach (var purOrder in purchaseModelList) {//每单加锁var qtLock = Context.TryGetLock($"{nameof(ImportPurchaseDetail)}-{ttid}-{purOrder.PurchaseId}");if (qtLock == null) { BmmHelp.SetProgress(this.Context.Args.rid, PublicErrorCode.UnknownException.GetEnumStatusCode($"采购单{purOrder.PurchaseId}正在导入,请稍后重试"), saleDetailEditDtos.Count, saleDetailEditDtos.Count, 0);return Nullables.NullValue; } locks.Add(qtLock); }var contextData = new ComboxClass<string, List<Model.Models.PurchaseOrder>, List<PurchaseAddress>, int, CheckConfigData, bool>() { V1 = Reserve, V2 = purchaseModelList, V3 = purchaseAddressList, V4 = maxLineNum, V5 = fkData, V6 = isGetWhPrice }; TaskEx<PurchaseOrderDetailEditDTO>.DoRun(saleDetailEditDtos.ToArray(), this.ImportRunOne, this.SaveExcelCallBack, this.Context, contextData, 1); }finally{foreach (var dlock in locks) { dlock.ReleaseLock(); } }复制代码4、总结
数据高并发时,是需要进行并发冲突解决的,在我实现消息中心时,因为可能存在多个消息中心服务,因此选用了2的方案,用行锁简单解决了下消息拉取的锁定问题。