我有一个相当复杂的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
答案 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;