相信Linq经过简单的培训大家都会写了,但是但你们满意的写完一套逻辑的时候有没有考虑过,你写的linq查询会导致严重的性能问题呢?那就分享下我在项目中遇到的踩过坑吧。
安装分析工具-MiniProfiler.安装教程我之前也分享过了
https://blog.csdn.net/wangwengrui40/article/details/126531322
为什么要安装呢?因为linqtosql追踪都会被编译程sql语句,所以导致性能问题的原因全都是编译出来的sql语句有问题
至于为什么会触发提交,上节课的底层原理探讨已经演示过了,以下函数会触发 表达式树 编译成 sql语句的。
https://blog.csdn.net/wangwengrui40/article/details/129335657
GetAllListAsync/GetAllList
FirstAsync/First
FirstOrDefaultAsync/FirstOrDefault
ToListAsync/ToList
GetAsync/Get
for循环,if判断
几乎IQueryable变其他类型的都会进行数据库提交,IOrderedQueryable等特殊的除外
代码展示
//产生1次sql语句public async Task GetXL0(){var ce = _entityRepository.GetAll();var ce2 = ce.Where(t=>t.Id==1);var ce3 = ce.Where(t => t.Id == 2);//var ce =await _entityRepository.GetAll().ToListAsync();return await ce3.FirstOrDefaultAsync();}//产生2次sql语句public async Task GetXL() {var ce = _entityRepository.GetAll();var ce2 = await ce.ToListAsync();var ce3 = await ce.CountAsync();//var ce =await _entityRepository.GetAll().ToListAsync();return ce3;}//产生2次sql语句public int GetXL2(){var ce = _entityRepository.GetAll();//var ce =await _entityRepository.GetAll().ToListAsync();int count = 0;foreach (var i in ce) {count++;}foreach (var i in ce){Console.WriteLine(i.Id);}return count;}
分享下灾难性写法
//嵌套产生 20条参数21次sqlpublic async Task GetXL3(){//有20条数据var ce = _entityRepository.GetAll();int count = 0;foreach (var i in ce){var ii = ce.Where(t => t.Id == i.Id).FirstOrDefault();count++;}return count;}
编译出来的sql语句截图
Get/GetAsync是默认了主键是Id的,如果你的组件是BillNo或者其他,将不能进行id查询,编译器会默认为全表查询
public async Task GetXLA() {var entitty =await _TraApplyMasterRepository.GetAll().FirstOrDefaultAsync(t=>t.BillNo== "20221226001");var entitty2 = await _TraApplyMasterRepository.GetAsync( "20221226001");return entitty;}
可以看到放编译出来也不是表达式树
//sql语句 select * from TraApplyMasterRepository where BillNo== "20221226001"public List GetXLB2Ok(){var entitty = _TraApplyMasterRepository.GetAll().Where(t => t.BillNo== "20221226001").ToList();return entitty;}public List GetXLB2(){//sql语句 select * from TraApplyMasterRepository var entitty = _TraApplyMasterRepository.GetAll().Where(t => t.BillNo.ToString() == "20221226001").ToList();return entitty;}
那为什么编译出来的sql语句不一样呢?问题出在他们编译的表达式树上面
很明显 tostring多了一行代码。这行代码是什么,上节课也没听我讲过?
(MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/),
其实这行代码,就是拼接 p.Name 的过程只不过全部都定义到了RuntimeMethodHandle里面了。因为linq反编译无法把 tostring编译成sql的函数,所以只能把前部分的 select * from TraApplyMasterRepository编译了
已知安全函数
Contains =sql的 like
日期类型的.Date
//join联查尽可能不要带函数public async Task> GetXLBOk(){var entitty = _TraApplyMasterRepository.GetAll().Join(_TraApplyDetailRepository.GetAll(), sa => sa.BillNo, se => se.BillNo, (sa, se) => sa).Where(t => t.BillNo == "20221226001").ToList();//.Where(t=> Convert.ToDateTime(t.CreationTime.ToString("yyyy-MM-dd"))== Convert.ToDateTime(t.CreationTime.ToString("yyyy-MM-dd")))/*var entitty2 = await _TraApplyMasterRepository.GetAll().Join(_TraApplyDetailRepository.GetAll(), sa => sa.BillNo, se => se.BillNo, (sa, se) => sa).Where(t => t.BillNo == "20221226001").OrderByDescending(t => t.BillNo).ToListAsync();*/return entitty;}public async Task> GetXLB(){var entitty = _TraApplyMasterRepository.GetAll().Join(_TraApplyDetailRepository.GetAll().OrderByDescending(t => t.BillNo), sa => sa.BillNo, se => se.BillNo, (sa, se) => sa).Where(t => t.BillNo == "20221226001").ToList();return entitty;}
2个方法编译出来的表达式树
在sql语句中是没有在联查中排序的写法的。个人猜测在 Linq的解析器模式中,遇到不合理的sql语句,就会把联查的2句 查询 拆开查询。这也解释了上面三号坑位为什么会拆开为2个查询
SELECT * FROM TraApplyBillMaster
JOIN TraApplyBillDetail ORDER BY TraApplyBillDetail.BillNo on TraApplyBillDetail.BillNo = TraApplyBillMaster.BillNo
因为很大原因都是因为表达式编译出了问题,纯纯分享踩过的坑
//写法没问题public async Task GetXLC(){var entitty = _TraApplyMasterRepository.GetAll().Join(_TraApplyDetailRepository.GetAll(), sa => sa.BillNo, se => se.BillNo, (sa, se) => new { sa, se });var entitty2 = await _TraApplyPatientRepository.GetAll().Join(entitty, sc => sc.BillNo, sd => sd.sa.BillNo, (sc, sd) => sd.sa).FirstOrDefaultAsync();return entitty2;}
//性能问题写法public async Task GetXLC1(){var entitty = _TraApplyMasterRepository.GetAll().Join(_TraApplyDetailRepository.GetAll(), sa => sa.BillNo, se => se.BillNo, (sa, se) => new XLCDto { BillNo=sa.BillNo, BillNoDetail=se.BillNo });var entitty2 = await _TraApplyPatientRepository.GetAll().Join(entitty, sc => sc.BillNo, sd => sd.BillNo, (sc, sd) => sd).FirstOrDefaultAsync();return entitty2;}//写法没问题public async Task GetXLC2(){var entitty =await _TraApplyMasterRepository.GetAll().Join(_TraApplyDetailRepository.GetAll(), sa => sa.BillNo, se => se.BillNo, (sa, se) => new {sa,se }).Join(_TraApplyPatientRepository.GetAll(), sc => sc.sa.BillNo, sd => sd.BillNo, (sc, sd) => sc.sa).FirstOrDefaultAsync();return entitty;}
尽量不要用导航属性,用join。
//导航属性容易出事//没问题public async Task GetXLD(){var product =await _productRepository.GetAll().Include(t => t.BasBloodVariety).Join(_TraApplyDetailRepository.GetAll(), sa => sa.BasBloodVarietyId, se => se.BasBloodVarietyId, (sa, se) => sa).FirstOrDefaultAsync();return product;}
//没问题public async Task GetXLD1(){var product = await _productRepository.GetAll().Include(t => t.BasBloodVariety).Where(t => t.BasBloodVariety.Name != "1").Join(_TraApplyDetailRepository.GetAll(), sa => sa.BasBloodVarietyId, se => se.BasBloodVarietyId, (sa, se) => sa).FirstOrDefaultAsync();return product;}public async Task GetXLD2(){
//出问题了,进行了全表查询var product = await _TraApplyDetailRepository.GetAll().Join(_productRepository.GetAll().Include(t => t.BasBloodVariety).Where(t => t.BasBloodVariety.Name !="1"), sa => sa.BasBloodVarietyId, se => se.BasBloodVariety.Id, (sa, se) => se.BasBloodVariety.Name).FirstOrDefaultAsync();return product;}
//没问题public async Task GetXLD3(){var product = await _TraApplyDetailRepository.GetAll().Join(_productRepository.GetAll().Include(t => t.BasBloodVariety), sa => sa.BasBloodVarietyId, se => se.BasBloodVariety.Id, (sa, se) => se.BasBloodVariety.Name).FirstOrDefaultAsync();return product;}
我们避免踩坑的目的都是提高查询性能,那么LinqToSql最终编译出来的都是sql语句。那么sql语句的优化策略,在linq里面也同样适用
下一篇:聊一聊AIGC