LINQ查询慢,创建超时;生成的SQL很好吗?

时间:2016-08-08 11:36:34

标签: c# asp.net sql-server linq linq-to-sql

我有一个相当复杂的LINQ查询通常很慢,它会创建一个System.Data.SqlClient.SqlException:“等待操作超时”。

但是,当我记录生成的SQL时(通过将TextWriter分配给DataContext的{​​{1}}),并直接在SQL Server上执行它,它在大约4中完成秒,这很好。

差异来自何处以及如何调试?

编辑:我在Sql Server Management Studio的Activity Monitor中也注意到,当从.NET执行查询时,处理器时间增加到100%,但是当我执行生成的SQL查询时,只有3%左右。

我不确定发布我的代码会有什么帮助,但是因为它是被请求的,所以这里是包含查询的代码:

Log

其中var Db = MyProject.GetDataContext(); var statusPaymentSuccess = new string[] { "SUCCESS", "REMBOURS", "AFTERPAY" }; var items = Db.Orders.Where(item => (siteid == null || item.SiteId == siteid) && (ls_list.Contains(item.OrderOrderLifeCycles.OrderByDescending(it => it.Id).First().OrderLifeCycleId)) && (item.OrderOrderPaymentStatus.Any(ops => statusPaymentSuccess.Contains(ops.OrderPaymentStatus.Code)) && (CycleID == null || item.OrderOrderLifeCycles.First().OrderLifeCycleId == CycleID) && (LocationID == null || item.SaleLocationId == LocationID) && (string.IsNullOrEmpty(SalesPerson) || item.EmployeeName.ToLower() == SalesPerson.ToLower())) ); var betweenorders = items.Select(it => new OrderBetween() { FirstPayDate = it.OrderOrderPaymentStatus.FirstOrDefault(ops => statusPaymentSuccess.Contains(ops.OrderPaymentStatus.Code)).DateTime, OrderTotal = it.TotalAmount, VatTotal = it.OrderItems.Sum(it2 => it2.BTWAmount ?? 0), Quantity = it.OrderItems.Count, SiteId = it.SiteId }); return betweenorders.Where(item => item.FirstPayDate >= start && item.FirstPayDate < stop) .GroupBy(item => item.FirstPayDate.Value.Year + "-" + item.FirstPayDate.Value.Month).Select( item => new SaleTotal() { Count = item.Sum(sub => sub.Quantity), Month = item.FirstOrDefault().FirstPayDate.Value.Year + "-" + item.FirstOrDefault().FirstPayDate.Value.Month.ToString().PadLeft(2, '0'), Total = item.Sum(sub => sub.OrderTotal), VAT = item.Sum(sub => sub.VatTotal) }).OrderBy(item => item.Month).ToArray(); 是包含ls_list ID的List<int>

从日志中提取生成的SQL查询:

OrderOrderLifeCycles

2 个答案:

答案 0 :(得分:2)

改善查询的一种明显方法是在第一部分中立即引起注意:

var items = Db.Orders.Where(item =>
    (siteid == null || item.SiteId == siteid)
 && (ls_list.Contains(item.OrderOrderLifeCycles.OrderByDescending(it => it.Id).First().OrderLifeCycleId))
 && (item.OrderOrderPaymentStatus.Any(ops => statusPaymentSuccess.Contains(ops.OrderPaymentStatus.Code))
 && (CycleID == null || item.OrderOrderLifeCycles.First().OrderLifeCycleId == CycleID)
 && (LocationID == null || item.SaleLocationId == LocationID)
 && (string.IsNullOrEmpty(SalesPerson) || item.EmployeeName.ToLower() == SalesPerson.ToLower()))
);

请记住,整个LINQ语句都已转换为SQL,包括所有这些null检查。这使得SQL查询不必要地复杂并且难以由查询优化器处理。 (顺便说一下,您显示属于另一个LINQ语句的SQL查询)。

处理可空条件的推荐方法是撰写查询:

IQueryable<Order> items = var items = Db.Orders;

if(siteid != null)
{
    items = items.Where(item => item.SiteId == siteid);
}
if (CycleID != null)
{
    items = items.Where(item => item.OrderOrderLifeCycles.First().OrderLifeCycleId == CycleID);
}
// etc.

另一件事是

item.EmployeeName.ToLower() == SalesPerson.ToLower()

这将在应用搜索条件之前转换EmployeeName字段值。这意味着EmployeeName上的任何索引都无法使用(也称为 not sargable )。我认为您可以删除ToLower()来电。在SQL查询中,使用了EmployeeName字段的数据库排序规则,并且默认情况下很可能不区分大小写(CI)。

最后,您可以考虑执行分组......

GroupBy(item => item.FirstPayDate.Value.Year + "-" + item.FirstPayDate.Value.Month)

...在内存中(LINQ to objects)而不是在数据库中。那就是:

return betweenorders.Where(item => item.FirstPayDate >= start && item.FirstPayDate < stop)
.AsEnumerable() // Switch to LINQ to objects
.GroupBy(...

由于超出本答案范围的原因,分组被翻译为ORDER BY(非GROUP BY),并且数据库字段FirstPayDate的转换再次禁用索引。它还使SQL查询不那么复杂,在内存中执行此操作可能不是一个繁重的操作。

答案 1 :(得分:0)

部分差异在this SA答案中解释:

  

SSMS通常使用ARITHABORT ON并且代码通常使用   ARITHABORT OFF - 这基本上是一个如何处理什么的选项   如果代码中的数学行有错误 - 例如划分   零。

     

但这里的主要内容是两种方法都有所不同   执行计划 - 这就是为什么同样的事情可以(随机)占用很多   在网站上比在SSMS中更长。

     

执行计划是根据第一次估算编制的   它被使用,所以你随机找到的是执行计划   以一种适合你的第一个查询的可怕方式缓存,但是太可怕了   用于后续查询。这就是这里发生的事情,也是为什么   它刚刚突然开始工作 - 创建了一个新的查询计划   更改存储过程后。

使用SET ARITHABORT OFF在SSMS中执行查询已经大大减慢了查询的执行速度。它仍然比代码中的LINQ-to-SQL版本至少快300%,所以如果我找到它,我会更多地更新这个答案。

编辑:SSMS不必像LINQ那样处理对象跟踪,因此在处理只执行读取(与写入)的查询时,我们可以通过禁用对象跟踪来加速LINQ到SQL的执行。然后,您必须手动指定要加载的对象,这些(据我所知)只能通过反复试验来确定。
对于我的查询,可以使用以下代码处理禁用跟踪和加载对象:

db.ObjectTrackingEnabled = false;
var lo = new DataLoadOptions();
lo.LoadWith<Order>(x => x.OrderOrderPaymentStatus);
lo.LoadWith<OrderOrderPaymentStatus>(x => x.OrderPaymentStatus);
db.LoadOptions = lo;