LinqToSQL - ToList()似乎非常慢

时间:2013-08-26 04:41:48

标签: c# sql-server linq-to-sql

我对LinqToSQL很新,我正在处理的项目不能改为其他东西。我正在将一些旧的SQL代码翻译成Linq。在linq不那么热,我用Linqer为我做翻译。查询大约需要90秒才能运行,因此我认为它必须是linqToSQL。但是,当我复制LinqToSQL生成的查询并在datacontext上运行一个ExecuteQuery时,它就像我预期的那样超级快。我已经复制了完整的查询,而不是试图将其删除,但看起来问题是LinqToSQL在幕后做的事情。

总结一下,如果我复制linq创建的T-SQL并运行

var results = DB.ExecuteQuery<InvoiceBalanceCheckDTO.InvoiceBalanceCheck>(@"T-SQL created by Linq - see below").ToList()

它以约0.5秒的预期结果完成。 它直接在SSMS中运行大致相同的时间。但是,如果我使用创建T-SQL并执行ToList()的linqToSQL代码,则需要很长时间。结果只有9条记录,尽管没有约束来检查余额&lt;&gt; 0,将有大约19,000条记录。这就好像它获得全部19,000然后检查&lt;&gt;获得记录后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

2 个答案:

答案 0 :(得分:2)

我会赌钱,问题出在这一行:

where Convert.ToDecimal(g.Sum(p => p.Balance)) != 0m

可能发生的是,它无法将其转换为SQL并静默尝试从db到内存获取所有行,然后在内存对象(LINQ to objects)中进行过滤 也许尝试将其更改为:

where g.Sum(p=>.Balance!=0)

答案 1 :(得分:2)

嗯,答案结果证明不是LinqToSQL本身(虽然可能是它创建查询的方式可能受到指责),但SQL服务器处理查询的方式。当我在数据库上运行查询以检查速度(并在DB.ExecuteQuery中运行创建的T = SQL)时,我将所有变量都硬编码。当我更改它以使用Linq生成的确切sql(即使用替换的变量)时,它在SSMS中运行速度一样慢。

看看两者的执行计划,他们是完全不同的。对SO的快速搜索将我带到了这个页面:Why does a parameterized query produces vastly slower query plan vs non-parameterized query表明问题是SQL服务器的“参数嗅探”。

罪魁祸首原来是“无账号记录”字符串

为了完整性,这是Linq创建的生成的T-SQL。 将@ p10更改为实际的硬编码字符串,它将恢复全速! 最后,我刚从linq中删除了该行,然后设置了帐户代码,一切都很好。

谢谢@ Botis,@ Blorgbeard,@ ElectricLlama&amp; @Scott寻求建议。

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