这是一篇公司内部研发人员的指导文章,写的并不是很深,其内部的机制讲述很少,完全是实践使用的干货。
引言,数据并发冲突
在一些场景,需要避免并发的产生,比如充值,扣费等,如果产生并发,可能金额增加结果不正确。
简单说下数据库的并发处理:
- 乐观并发:当用户阅读时,行不会被锁定。当用户试图更新这一行时,系统必须确定该记录是否被另一个用户修改过,因为它被读取了。
- 悲观并发: 悲观并发包括锁行,以防止其他用户以影响当前用户的方式修改数据。
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字段为 IsConcurrencyToken
public 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的方案,用行锁简单解决了下消息拉取的锁定问题。