这是一篇公司内部研发人员的指导文章,写的并不是很深,其内部的机制讲述很少,完全是实践使用的干货。

引言,数据并发冲突

在一些场景,需要避免并发的产生,比如充值,扣费等,如果产生并发,可能金额增加结果不正确。

简单说下数据库的并发处理:

  • 乐观并发:当用户阅读时,行不会被锁定。当用户试图更新这一行时,系统必须确定该记录是否被另一个用户修改过,因为它被读取了。
  • 悲观并发: 悲观并发包括锁行,以防止其他用户以影响当前用户的方式修改数据。
1、采用EF的TS 机制 ,实现乐观并发,防止并发操作

在需要处理并发冲突的表内增加 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的方案,用行锁简单解决了下消息拉取的锁定问题。