我有一个内置于 angularJs、C#、Linq 等中的 Web 应用程序。 我在生产环境上有近 2,00,000 条 lac 记录。
几乎每列都有过滤器(单击列时升序/降序),我还进行分页并每页显示 25 条记录。
现在的问题是,当我尝试使用 ID 列(数据库表中的主键)过滤记录时,其工作速度按预期快速,但其他表列过滤器工作速度太慢而无法响应。
这些列中很少有是从 C# 代码动态计算的,其中很少有来自其他连接表的地址、代理名称等。
我将发布我的小代码,它可以帮助别人理解这个场景。
//Do the sorting
if (!string.IsNullOrWhiteSpace(r.sortColumn))
{
switch (r.sortColumn)
{
case "id":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.TransactionID) : t = t.OrderBy(x => x.TransactionID);
break;
case "created":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.StatusDate ?? x.DateCreated) : t = t.OrderBy(x => x.StatusDate ?? x.DateCreated);
break;
case "salePrice":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.SalePrice) : t = t.OrderBy(x => x.SalePrice);
break;
case "agent":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.tblTransactionAgentDetails.Where(z => z.isPrimary).FirstOrDefault().tblAgent.LastName) : t = t.OrderBy(x => x.tblTransactionAgentDetails.Where(z => z.isPrimary).FirstOrDefault().tblAgent.LastName);
break;
case "state":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.Property_State) : t = t.OrderBy(x => x.Property_State);
break;
case "sob":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.SOBid) : t = t.OrderBy(x => x.SOBid);
break;
case "branch":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.tblBranch.BranchName) : t = t.OrderBy(x => x.tblBranch.BranchName);
break;
case "addr1":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.Property_Address1) : t = t.OrderBy(x => x.Property_Address1);
break;
case "mls":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.MLSno) : t = t.OrderBy(x => x.MLSno);
break;
case "side":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.SideRepresentingID) : t = t.OrderBy(x => x.SideRepresentingID);
break;
case "statusName":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.CurrentStatusId) : t = t.OrderBy(x => x.CurrentStatusId);
break;
case "sentToAccountingDate":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.tblStatusTransactions.Where(y => x.CurrentStatusId == 5 && y.NewStatusID == 5).OrderByDescending(z => z.EntryID).FirstOrDefault().StatusChangeDateTime) : t = t.OrderBy(x => x.tblStatusTransactions.Where(y => x.CurrentStatusId == 5 && y.NewStatusID == 5).OrderByDescending(z => z.EntryID).FirstOrDefault().StatusChangeDateTime);
break;
case "settleDate":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.SettlementDate) : t = t.OrderBy(x => x.SettlementDate);
break;
case "settlementActual":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.SettlementDate_Actual) : t = t.OrderBy(x => x.SettlementDate_Actual);
break;
case "remitAmount":
if (r.sortDirection.ToUpper() == "DESC")
{
t = t.OrderByDescending(x => x.tblTransactionAgentDetails.Where(y => y.isPrimary).FirstOrDefault().Agent_ServiceFee)
.ThenByDescending(x => x.tblTransactionAgentDetails.Where(y => y.isPrimary).FirstOrDefault().Agent_GrossGCC)
.ThenByDescending(x => x.AdminFeeAmtClient)
.ThenByDescending(x => x.tblTransactionAgentDetails.Where(y => y.isPrimary).FirstOrDefault().ParnershipPercentage)
.ThenByDescending(x => x.tblTransactionAgentDetails.Where(y => y.isPrimary).FirstOrDefault().Agent_Bonus);
}
else
{
t = t.OrderBy(x => x.tblTransactionAgentDetails.Where(y => y.isPrimary).FirstOrDefault().Agent_ServiceFee)
.ThenBy(x => x.tblTransactionAgentDetails.Where(y => y.isPrimary).FirstOrDefault().Agent_GrossGCC)
.ThenBy(x => x.AdminFeeAmtClient)
.ThenBy(x => x.tblTransactionAgentDetails.Where(y => y.isPrimary).FirstOrDefault().ParnershipPercentage)
.ThenBy(x => x.tblTransactionAgentDetails.Where(y => y.isPrimary).FirstOrDefault().Agent_Bonus);
}
break;
case "listPrice":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.ListPrice) : t = t.OrderBy(x => x.ListPrice);
break;
case "lender":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.tblCompany.CompanyName) : t = t.OrderBy(x => x.tblCompany.CompanyName);
break;
case "title":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.tblCompany.CompanyName) : t = t.OrderBy(x => x.tblCompany.CompanyName);
break;
case "commissionStatus":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.CommissionStatus) : t = t.OrderBy(x => x.CommissionStatus);
break;
case "fAssignedTo":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.FAssignedTo) : t = t.OrderBy(x => x.FAssignedTo);
break;
case "EMDStatus":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.EMDStatus) : t = t.OrderBy(x => x.EMDStatus);
break;
case "processingSchedule":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.ProcessingSchedule) : t = t.OrderBy(x => x.ProcessingSchedule);
break;
case "fileManagement":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.FileManagement) : t = t.OrderBy(x => x.FileManagement);
break;
case "trackerStatus":
t = (r.sortDirection.ToUpper() == "DESC") ? t = t.OrderByDescending(x => x.TrackerStatus) : t = t.OrderBy(x => x.TrackerStatus);
break;
default:
break;
}
}
//Execute the data
var final = t.Skip(r.perPage * r.index).Take(r.perPage).ToList();
此外,我也对此做了一些研究,但尚未得到合适的工作解决方案。
这是类似的问题,但这对我不起作用:linq order by take very long time
任何类型的帮助将不胜感激,谢谢!
这是一个常见问题,您尝试将系统设计得过于灵活。如果您为用户提供按任何内容排序和过滤的选项,而不考虑数据库实际上如何优化以适应这种情况,那么在构建原始 SQL 查询时,您将遇到完全相同的问题。如前所述,数据库使用索引来帮助优化排序和搜索。向用作过滤器或排序的列添加索引可以改善搜索行为,但索引本身是排序的,这会影响它们在使用它们进行过滤或排序的查询中的有用程度。然而,为每一列添加索引并不是没有很大的成本。索引占用大量空间,并且每当添加、删除或更新行时都需要刷新它们,这需要时间,特别是考虑到非常大的表会有多大的索引。
此类问题的解决方案通常是与业务分析师/产品负责人坐下来讨论最终用户实际将使用哪些内容进行搜索,并提供最有用、最常见的搜索功能以及支持索引。与尝试设计一个满足用户各种可能的想法的系统相反,该系统从未真正使用过,而是坐在那里等待,如果有人决定有意或无意地使用它们,数据库就会停止运行。
这些列中很少有是通过 C# 代码动态计算的,并且很少有 它们例如来自其他连接表的地址、代理名称等。
针对连接实体进行过滤和排序通常不是问题,但适用相同的索引规则。什么可能是一个大问题取决于您所说的“从 C# 代码动态计算”的含义以及您如何配置 EF 或可能显式调用客户端评估。例如,如果实体中的属性是用 C# 代码计算/组合的(例如调用 .Net 或自定义函数),并且想要按 Linq 查询中的这些计算属性进行排序或筛选,默认情况下 EF Core 将抛出当遇到这些场景之一时会出现异常。如果您已恢复 EF 的行为以使用警告而不是异常(不推荐)或通过插入
.ToList()
或 .AsEnumerable()
显式客户端评估,那么查询将“工作”,但它将产生巨大的性能影响,因为查询将执行并开始将其可能过滤到该点的所有实体加载到内存中,然后再尝试继续基于 C# 的过滤。最后,您将获得一页结果,但 EF 会将数千或数百万行加载到内存中。仔细检查您的查询,看看是否在 .AsEnumerable()
/.ToList()
之前的任何地方使用了 .Skip()
或 .Take()
。为了避免严重的性能问题,您需要删除这些属性,重写过滤/排序以使用基本的映射属性,即使它们看起来比简单的辅助方法更难看/更复杂。在项目中要注意的 EF 配置是您的 DbContext .OnConfiguring()
处理程序是否具有以下内容作为 optionsBuilder 的一部分:
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
如果您的 DbContext 有此设置,我强烈建议将其删除,以便客户端评估返回到抛出异常,并正确处理这些场景以消除对客户端评估的需要。