LinqToSQL-ToList()似乎非常慢
本文关键字:非常 LinqToSQL-ToList | 更新日期: 2023-09-27 18:29:09
我是LinqToSQL的新手,我正在处理的项目不能更改为其他项目。我正在将一些旧的SQL代码翻译成Linq。由于对linq不太感兴趣,我使用了Linqer为我做翻译。查询运行大约需要90秒,所以我认为它一定是linqToSQL。然而,当我复制LinqToSQL生成的查询并在数据上下文上运行ExecuteQuery时,它的速度非常快。我已经复制了完整的查询,而不是试图将其提炼出来,但问题似乎与LinqToSQL在幕后所做的事情有关。
总之,如果我复制linq创建的T-SQL并运行
var results = DB.ExecuteQuery<InvoiceBalanceCheckDTO.InvoiceBalanceCheck>(@"T-SQL created by Linq - see below").ToList()
它在大约0.5秒内以预期的结果完成。它大约在同一时间直接在SSMS中运行。但是,如果我使用创建T-SQL的linqToSQL代码并执行ToList(),则需要很长时间。结果只有9条记录,尽管没有检查余额的约束<>0,将有大约19000条记录。就好像它得到了全部19000,然后检查<>0在它得到记录之后。我还将Linq改为投影到上面使用的类中,而不是匿名类型,但这对没有影响
这是原始SQL:
SELECT InvoiceNum, Max(AccountCode), Sum(AmountInc) AS Balance
FROM
(SELECT InvoiceNum, AccountCode, AmountInc From TourBookAccount WHERE AccDetailTypeE IN(20,30) AND InvoiceNum >= 1000
UNION ALL
SELECT InvoiceNum, '<no matching invoice>' AS AccountCode, AccountInvoiceDetail.AmountInc
FROM AccountInvoiceDetail
INNER JOIN AccountInvoice ON AccountInvoiceDetail.InvoiceID=AccountInvoice.InvoiceID
WHERE AccDetailTypeE IN(20,30)
AND InvoiceNum >= 1000
) as t
GROUP BY InvoiceNum
HAVING (Sum(t.AmountInc)<>0)
ORDER BY InvoiceNum
这是linq
var test = (from t in
(
//this gets the TourBookAccount totals
from tba in DB.TourBookAccount
where
detailTypes.Contains(tba.AccDetailTypeE) &&
tba.InvoiceNum >= dto.CheckInvoiceNumFrom
select new
{
InvoiceNum = tba.InvoiceNum,
AccountCode = tba.AccountCode,
Balance = tba.AmountInc
}
)
.Concat //note that concat, since it's possible that the AccountInvoice record does not actually exist
(
//this gets the Invoice detail totals.
from aid in DB.AccountInvoiceDetail
where
detailTypes.Contains(aid.AccDetailTypeE) &&
aid.AccountInvoice.InvoiceNum >= dto.CheckInvoiceNumFrom &&
select new
{
InvoiceNum = aid.AccountInvoice.InvoiceNum,
AccountCode = "<No Account Records>",
Balance = aid.AmountInc
}
)
group t by t.InvoiceNum into g
where Convert.ToDecimal(g.Sum(p => p.Balance)) != 0m
select new
{
InvoiceNum = g.Key,
AccountCode = g.Max(p => p.AccountCode),
Balance = g.Sum(p => p.Balance)
}).ToList();
这就是linq产生的T-SQL
SELECT [t5].[InvoiceNum], [t5].[value2] AS [AccountCode], [t5].[value3] AS [Balance]
FROM (
SELECT SUM([t4].[AmountInc]) AS [value], MAX([t4].[AccountCode]) AS [value2], SUM([t4].[AmountInc]) AS [value3], [t4].[InvoiceNum]
FROM (
SELECT [t3].[InvoiceNum], [t3].[AccountCode], [t3].[AmountInc]
FROM (
SELECT [t0].[InvoiceNum], [t0].[AccountCode], [t0].[AmountInc]
FROM [dbo].[TourBookAccount] AS [t0]
WHERE ([t0].[AccDetailTypeE] IN (20, 30)) AND ([t0].[InvoiceNum] >= 1000)
UNION ALL
SELECT [t2].[InvoiceNum],'<No Account Records>' AS [value], [t1].[AmountInc]
FROM [dbo].[AccountInvoiceDetail] AS [t1]
INNER JOIN [dbo].[AccountInvoice] AS [t2] ON [t2].[InvoiceID] = [t1].[InvoiceID]
WHERE ([t1].[AccDetailTypeE] IN (20, 30)) AND ([t2].[InvoiceNum] >= 1000)
) AS [t3]
) AS [t4]
GROUP BY [t4].[InvoiceNum]
) AS [t5]
WHERE [t5].[value] <> 0
我敢打赌,问题就在这一行:
where Convert.ToDecimal(g.Sum(p => p.Balance)) != 0m
可能发生的情况是,它无法将其转换为SQL,并默默地尝试将所有行从数据库获取到内存,然后对内存中的对象(LINQ到对象)进行过滤也许可以尝试将其更改为类似于:
where g.Sum(p=>.Balance!=0)
答案不是LinqToSQL本身(尽管可能是它创建查询的方式),而是SQL服务器处理查询的方式。当我在数据库上运行查询以检查速度时(并在DB.ExecuteQuery中运行创建的T=SQL),我对所有变量进行了硬编码。当我将其更改为使用Linq生成的精确sql(即使用替换的变量)时,它在SSMS中的运行速度同样慢。
从两者的执行计划来看,它们有很大的不同。在SO上快速搜索后,我看到了以下页面:为什么参数化查询产生的查询计划比非参数化查询慢得多,这表明问题出在SQL server的"参数嗅探"上。
罪魁祸首原来是"无账户记录"字符串
为了完整起见,这里是Linq创建的生成的T-SQL。将@p10更改为实际的硬编码字符串,它将恢复到全速!最后,我只是从linq中删除了这一行,然后设置了帐户代码,一切都很好。
感谢@Botis、@Blorgbeard、@ElectricLlama&斯科特的建议。
DECLARE @p0 as Int = 20
DECLARE @p1 as Int = 30
DECLARE @p2 as Int = 1000
DECLARE @p3 as Int = 20
DECLARE @p4 as Int = 30
DECLARE @p5 as Int = 1000
DECLARE @p6 as Int = 40
DECLARE @p7 as Int = 10
DECLARE @p8 as Int = 0
DECLARE @p9 as Int = 1
DECLARE @p10 as NVarChar(4000)= '<No Account Records>' /*replace this parameter with the actual text in the SQl and it's way faster.*/
DECLARE @p11 as Decimal(33,4) = 0
SELECT [t5].[InvoiceNum], [t5].[value2] AS [AccountCode], [t5].[value3] AS [Balance]
FROM (
SELECT SUM([t4].[AmountInc]) AS [value], MAX([t4].[AccountCode]) AS [value2], SUM([t4].[AmountInc]) AS [value3], [t4].[InvoiceNum]
FROM (
SELECT [t3].[InvoiceNum], [t3].[AccountCode], [t3].[AmountInc]
FROM (
SELECT [t0].[InvoiceNum], [t0].[AccountCode], [t0].[AmountInc]
FROM [dbo].[TourBookAccount] AS [t0]
WHERE ([t0].[AccDetailTypeE] IN (@p0, @p1)) AND ([t0].[InvoiceNum] >= @p2)
UNION ALL
SELECT [t2].[InvoiceNum], @p10 AS [value], [t1].[AmountInc]
FROM [dbo].[AccountInvoiceDetail] AS [t1]
INNER JOIN [dbo].[AccountInvoice] AS [t2] ON [t2].[InvoiceID] = [t1].[InvoiceID]
WHERE ([t1].[AccDetailTypeE] IN (@p3, @p4)) AND ([t2].[InvoiceNum] >= @p5) AND ([t2].[InvoiceStatusE] <= @p6) AND ([t2].[InvoiceTypeE] = @p7) AND ([t1].[BookNum] <> @p8) AND ([t1].[AccDetailSourceE] = @p9)
) AS [t3]
) AS [t4]
GROUP BY [t4].[InvoiceNum]
) AS [t5]
WHERE [t5].[value] <> @p11
SELECT [t5].[InvoiceNum], [t5].[value2] AS [AccountCode], [t5].[value3] AS [Balance]
FROM (
SELECT SUM([t4].[AmountInc]) AS [value], MAX([t4].[AccountCode]) AS [value2], SUM([t4].[AmountInc]) AS [value3], [t4].[InvoiceNum]
FROM (
SELECT [t3].[InvoiceNum], [t3].[AccountCode], [t3].[AmountInc]
FROM (
SELECT [t0].[InvoiceNum], [t0].[AccountCode], [t0].[AmountInc]
FROM [dbo].[TourBookAccount] AS [t0]
WHERE ([t0].[AccDetailTypeE] IN (20, 30)) AND ([t0].[InvoiceNum] >= 1000)
UNION ALL
SELECT [t2].[InvoiceNum], '<No Account Records>' AS [value], [t1].[AmountInc]
FROM [dbo].[AccountInvoiceDetail] AS [t1]
INNER JOIN [dbo].[AccountInvoice] AS [t2] ON [t2].[InvoiceID] = [t1].[InvoiceID]
WHERE ([t1].[AccDetailTypeE] IN (20, 30)) AND ([t2].[InvoiceNum] >= 0) AND ([t2].[InvoiceStatusE] <= 40) AND ([t2].[InvoiceTypeE] = 10) AND ([t1].[BookNum] <> 0) AND ([t1].[AccDetailSourceE] = 1)
) AS [t3]
) AS [t4]
GROUP BY [t4].[InvoiceNum]
) AS [t5]
WHERE [t5].[value] <> 0