脚本宝典收集整理的这篇文章主要介绍了使用并行Foreach优化SqlSugarMapper,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
最近在迁移公司导入导出项目时,发现导出速度特别慢,大概2K数据需要导出近半个小时,通过在程序各个地方埋点,最终定位到了Sqlsugar的Mapper中,随后通过并行Foreach单独抽出Mapper中的业务方法,性能提升近30倍,当然,此属于个人总结可能并不适用于读者业务逻辑,最重要的一点:业务上优化远比技术层面优化要来得快,效率更高!
SqlSugar的Mapper经过打印日志发现,即使mapper中的执行是串行的,在内存中处理数据速度也是非常快的,但是当在mapper中有些耗时操作时,数据量越大处理时间便成线性增长。 例如在此导出业务中,有涉及到手机号码加解密的逻辑,因为解密耗时将近0.5秒,所以导出1000条数据的时候,光手机号码处理就需要耗时500秒,此间还没法做其他操作,所以我认定性能瓶颈在Sqlsugar的Mapper上,准备从此处开刀。
秉着大胆猜想,小心求证的原则,
既然猜想问题是处在Mapper上,先上代码。
这是一个非常复杂的数据查询,从行号即可看到,此方法有近600行代码,给他稍微整理一下,这个查询结构如下:
var aList = await DbContext.Queryable<TableA>().Where(x => x.code == input.Acode).ToList();
var bList = ...
var cList = ...
......
var queryable = DBContext.Queryable<TableMaster>().Where(x => x.SystemId == input.SystemId)
.WhereIF(!input.Phone.IsNullOrWhiteSpace(), x => x.Phone == input.Phone.Trim().Encrypt())
.WhereIF(input.Acode>0),x => aList.Contains(x.Acode))
.WhereIF(input.Bcode>0),x => bList.Contains(x.Bcode))
......
.OrderBy(x => x.CreateTime, OrderByType.Desc)
.Select(x => new RetrunListModel
{
ID = x.Id,
SystemId = x.SystemId,
Phone = x.Phone,
Acode = x.Acode,
Bcode = x.Bcode,
......
})
.Mapper((model, cache) =>
{
// 类型一:查询中间表赋值
// 使用Master表查询结果中的ACode,从A表中的Name查询数来
var aList = cache.Get(h =>
{
var aCodeList = h.Where(x => x.ACode > 0).Select(x => x.ACode).Distinct().ToList();
return DbContext.Queryable<TableA>().Where(x => x.SystemId == input.SystemId)
.Where(x => aCodeList.Contains(x.ACode))
.Select(x => new TableA
{
Id = x.Id,
Name = x.Name,
}).ToList();
}
// 将查询的结果赋值给model,即最终的接收结果
model.AName = aCodeList.Where(x => x.Id == n.ACode).FirstOrDefault()?.Name ?? "";
// 类型二:对数据解密
if(input.IsDecrypt)
{
model.Phone = xxxService.DecryptPhone(model.Phone)
}
......
});
直接看向Mapper,在这里的业务有两种类型:
干说可能不好懂,画了张图
很显然从上图可以看出,由于循环解密需要耗时较长,就算把Mapper单独抽出来,还是需要循环去将字段解密,看起来无解,但是这里可以是使用并行foreach去处理的,也可以用多线程这里不做展开,但是再次给读者提个醒,业务上去做优化远比技术上优化来的快,效率更高!
.Net Framework4 引入了新的Task Parallel Library(任务并行库,TPL),它支持数据并行、任务并行和流水线。 当并行循环运行时,TPL会将数据源按照内置的分区算法(或者你可以自定义一个分区算法)将数据划分为多个不相交的子集,然后,从线程池中选择线程并行地处理这些数据子集,每个线程只负责处理一个数据子集。在后台,任务计划程序将根据系统资源和工作负荷来对任务进行分区。如有可能,计划程序会在工作负荷变得不平衡的情况下在多个线程和处理器之间重新分配工作。 在对任何代码(包括循环)进行并行化时,一个重要的目标是利用尽可能多的处理器,但不要过度并行化到使行处理的开销让任何性能优势消耗殆尽的程度。比如:对于嵌套循环,只会对外部循环进行并行化,原因是不会在内部循环中执行太多工作。少量工作和不良缓存影响的组合可能会导致嵌套并行循环的性能降低。 由于循环体是并行运行的,迭代范围的分区是根据可用的逻辑内核数、分区大小以及其他因素动态变化的,因此无法保证迭代的执行顺序。 TPL引入了System.Threading.Tasks ,主类是Task,这个类表示一个异步的并发的操作,然而我们不一定要使用Task类的实例,可以使用Parallel静态类。 它提供了Parallel.Invoke, Parallel.For,Parallel.Forecah 三个方法 当然此处是我读了《.net 并发编程实战》,大神博客以及官方文档,稍微总结的,后文贴上链接,他们文章更详细。
这里的思路就是,先将结果查询出出来,然后将之前的从表查询以及字段赋值处理,单独抽出来通过并行Foreach的方式,快速处理加解密这类耗时操作。
var aList = await DbContext.Queryable<TableA>().Where(x => x.code == input.Acode).ToList();
var bList = ...
var cList = ...
......
var queryable =await DBContext.Queryable<TableMaster>().Where(x => x.SystemId == input.SystemId)
.WhereIF(!input.Phone.IsNullOrWhiteSpace(), x => x.Phone == input.Phone.Trim().Encrypt())
.WhereIF(input.Acode>0),x => aList.Contains(x.Acode))
.WhereIF(input.Bcode>0),x => bList.Contains(x.Bcode))
......
.OrderBy(x => x.CreateTime, OrderByType.Desc)
.Select(x => new RetrunListModel
{
ID = x.Id,
SystemId = x.SystemId,
Phone = x.Phone,
Acode = x.Acode,
Bcode = x.Bcode,
......
}).ToListAsync();
var aCodeList = h.Where(x => x.ACode > 0).Select(x => x.ACode).Distinct().ToList();
var aList = DbContext.Queryable<TableA>().Where(x => x.SystemId == input.SystemId)
.Where(x => aCodeList.Contains(x.ACode))
.Select(x => new TableA
{
Id = x.Id,
Name = x.Name,
}).ToList();
......
// 并行的方式 给列表绑值
var rangesize = (int)(clueQueryList.Count / Environment.ProcessorCount) + 1;
var rangePartitioner = Partitioner.Create(0, clueQueryList.Count, rangesize);
Parallel.ForEach(rangePartitioner, range =>
{
var newList = clueQueryList.Skip(range.Item1).Take(range.Item2 - range.Item1);
foreach (var model in newList)
{
// 字段赋值
model.AName = aList.Where(x => x.Id == n.ACode).FirstOrDefault()?.Name ?? "";
// 手机号码加解密
model.Phone = xxxService.DecryptPhone(model.Phone)
......
}
});
return clueQueryList;
System.Threading.Tasks.Parallel.ForEach(list, new ParallelOptions() { MaxDegreeOfParallelism = 12 }, range =>
{
#region 业务代码
#endregion
});
大致总结一下几点
【书籍】《.net并发编程实战》 【官方文档】《.NET 中的并行编程》https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-programming/ 【博客园】《.Net并行编程高级教程--Parallel》https://www.cnblogs.com/stoneniqiu/p/4857021.html 【博客园】《8天玩转并发》 https://www.cnblogs.com/huangxincheng/category/368987.html 【博客园】《异步编程:.NET4.X 数据并行》 https://www.cnblogs.com/heyuquan/archive/2013/03/13/parallel-for-foreach-invoke.html 【博客园】《Parallel.ForEach 之 MaxDegreeOfParallelism》 https://www.cnblogs.com/QinQouShui/p/12134232.html 【自己总结】《如何运用并行编程Parallel提升任务执行效率》https://mp.weixin.qq.com/s/3qli3cM9ZLweG9aj-nYdBw
以上是脚本宝典为你收集整理的使用并行Foreach优化SqlSugarMapper全部内容,希望文章能够帮你解决使用并行Foreach优化SqlSugarMapper所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。