SQL查询在.NET应用程序中速度较慢,但​​在SQL Server Management Studio中是即时的

这是SQL

SELECT tal.TrustAccountValue FROM TrustAccountLog AS tal INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID INNER JOIN Users usr ON usr.UserID = ta.UserID WHERE usr.UserID = 70402 AND ta.TrustAccountID = 117249 AND tal.trustaccountlogid = ( SELECT MAX (tal.trustaccountlogid) FROM TrustAccountLog AS tal INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID INNER JOIN Users usr ON usr.UserID = ta.UserID WHERE usr.UserID = 70402 AND ta.TrustAccountID = 117249 AND tal.TrustAccountLogDate < '3/1/2010 12:00:00 AM' ) 

基本上有一个用户表TrustAccount表和TrustAccountLog表。
用户:包含用户及其详细信息
TrustAccount:用户可以有多个TrustAccounts。
TrustAccountLog:包含所有TrustAccount“移动”的审计。 一个
TrustAccount与多个TrustAccountLog条目相关联。 现在,这个查询在SQL Server Management Studio中以毫秒为单位执行,但是由于一些奇怪的原因,我的C#应用​​程序甚至有时会超时(120s)。

这里是简单的代码。 它在一个循环中被多次调用,语句得到了准备。

 cmd.CommandTimeout = Configuration.DBTimeout; cmd.CommandText = "SELECT tal.TrustAccountValue FROM TrustAccountLog AS tal INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID INNER JOIN Users usr ON usr.UserID = ta.UserID WHERE usr.UserID = @UserID1 AND ta.TrustAccountID = @TrustAccountID1 AND tal.trustaccountlogid = (SELECT MAX (tal.trustaccountlogid) FROM TrustAccountLog AS tal INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID INNER JOIN Users usr ON usr.UserID = ta.UserID WHERE usr.UserID = @UserID2 AND ta.TrustAccountID = @TrustAccountID2 AND tal.TrustAccountLogDate < @TrustAccountLogDate2 ))"; cmd.Parameters.Add("@TrustAccountID1", SqlDbType.Int).Value = trustAccountId; cmd.Parameters.Add("@UserID1", SqlDbType.Int).Value = userId; cmd.Parameters.Add("@TrustAccountID2", SqlDbType.Int).Value = trustAccountId; cmd.Parameters.Add("@UserID2", SqlDbType.Int).Value = userId; cmd.Parameters.Add("@TrustAccountLogDate2", SqlDbType.DateTime).Value =TrustAccountLogDate; // And then... reader = cmd.ExecuteReader(); if (reader.Read()) { double value = (double)reader.GetValue(0); if (System.Double.IsNaN(value)) return 0; else return value; } else return 0; 

如果这是参数嗅探,尝试添加option(recompile)到您的查询结束。 我build议创build一个存储过程,以更易于pipe理的方式封装逻辑。 也同意 – 为什么你通过5个参数,如果你只需要三个,从这个例子来看? 你可以用这个查询吗?

 select TrustAccountValue from ( SELECT MAX (tal.trustaccountlogid), tal.TrustAccountValue FROM TrustAccountLog AS tal INNER JOIN TrustAccount ta ON ta.TrustAccountID = tal.TrustAccountID INNER JOIN Users usr ON usr.UserID = ta.UserID WHERE usr.UserID = 70402 AND ta.TrustAccountID = 117249 AND tal.TrustAccountLogDate < '3/1/2010 12:00:00 AM' group by tal.TrustAccountValue ) q 

而且,根据执行查询的用户的语言设置,您使用的是不明确的date格式。 对我来说,例如,这是一月三日,而不是三月一日。 看一下这个:

 set language us_english go select @@language --us_english select convert(datetime, '3/1/2010 12:00:00 AM') go set language british go select @@language --british select convert(datetime, '3/1/2010 12:00:00 AM') 

推荐的方法是使用'ISO'格式yyyymmdd hh:mm:ss

 select convert(datetime, '20100301 00:00:00') --midnight 00, noon 12 

根据我的经验,为什么查询在SSMS中运行速度快,但.NET速度慢是由于连接的SET -tings不同造成的。 当通过SSMS或SqlConnection打开连接时,会自动发出一堆SET命令来设置执行环境。 不幸的是,SSMS和SqlConnection具有不同的SET默认值。

一个共同的区别是SET ARITHABORT 。 尝试发出SET ARITHABORT ON作为.NET代码的第一个命令。

SQL事件探查器可以用来监视SSMS和.NET发出的SET命令,所以你可以find其他的区别。

下面的代码演示了如何发出一个SET命令,但是请注意这个代码还没有被testing。

 using (SqlConnection conn = new SqlConnection("<CONNECTION_STRING>")) { conn.Open(); using (SqlCommand comm = new SqlCommand("SET ARITHABORT ON", conn)) { comm.ExecuteNonQuery(); } // Do your own stuff here but you must use the same connection object // The SET command applies to the connection. Any other connections will not // be affected, nor will any new connections opened. If you want this applied // to every connection, you must do it every time one is opened. } 

在testing环境中有相同的问题,虽然实时系统(在同一个SQL服务器上)运行良好。 添加OPTION(RECOMPILE)和OPTION(OPTIMIZE FOR(@ p1 UNKNOWN))没有帮助。

我使用SQL事件探查器来捕捉.net客户端发送的确切查询,并发现这是用exec sp_executesql N'select ...包装的,并且参数已被声明为nvarchars – 被比较的列是简单的variables。

把捕获的查询文本放到SSMS中,证实它的运行速度和.net客户端一样慢。

我发现将参数的types更改为AnsiText清除了问题:

p = cm.CreateParameter() p.ParameterName = "@company" p.Value = company p.DbType = DbType.AnsiString cm.Parameters.Add(p)

我永远无法解释为什么testing和现场环境在性能上有如此显着的差异。

问题最可能在于标准

 tal.TrustAccountLogDate < @TrustAccountLogDate2 

最佳的执行计划将高度依赖于参数的值,通过1910-01-01(它不返回任何行)肯定会导致与2100-12-31(返回所有行)不同的计划。

当在查询中将该值指定为文字时,SQL Server将知道在生成计划时应使用哪个值。 当使用参数时,SQL服务器将只生成一次计划然后再使用它,如果后续执行中的值与原始值相差太多,则计划将不是最优的。

为了纠正这种情况,您可以在查询中指定OPTION(RECOMPILE) 。 将查询添加到存储过程将不会帮助您解决此特定问题,除非您创buildWITH RECOMPILE过程。

其他人已经提到过这个(“参数嗅探”),但是我认为这个概念的简单解释不会受到伤害。

这可能是types转换问题。 数据层上的所有ID都是SqlDbType.Int吗?

另外,为什么有4个参数,其中2会做?

 cmd.Parameters.Add("@TrustAccountID1", SqlDbType.Int).Value = trustAccountId; cmd.Parameters.Add("@UserID1", SqlDbType.Int).Value = userId; cmd.Parameters.Add("@TrustAccountID2", SqlDbType.Int).Value = trustAccountId; cmd.Parameters.Add("@UserID2", SqlDbType.Int).Value = userId; 

可能

 cmd.Parameters.Add("@TrustAccountID", SqlDbType.Int).Value = trustAccountId; cmd.Parameters.Add("@UserID", SqlDbType.Int).Value = userId; 

因为它们都被分配了相同的variables。

(这可能会导致服务器制定一个不同的计划,因为它需要四个不同的variables作为op。to。4个常量 – 使得两个variables可以为服务器优化做出改变。

由于您似乎只是从一列中的一行返回值,因此您可以在命令对象上使用ExecuteScalar() ,这应该更有效:

  object value = cmd.ExecuteScalar(); if (value == null) return 0; else return (double)value; 

在我的情况下,问题是我的entity framework正在生成使用exec sp_executesql查询。

当参数在types中不完全匹配时,执行计划不使用索引,因为它决定将转换放入查询本身。 你可以想象这会导致性能下降得更慢。

在我的情况下,列被定义为CHR(3),entity framework在查询中传递N'str',导致从nchar到char的转换。 所以对于这样的查询:

ctx.Events.Where(e => e.Status == "Snt")

它正在生成一个如下所示的SQL查询:

FROM [ExtEvents] AS [Extent1] ... WHERE (N''Snt'' = [Extent1].[Status]) ...

在我的情况下,最简单的解决scheme是改变列的types,或者你可以摔跤你的代码,使其通过正确的types在第一位。

希望你的具体问题现在已经解决了,因为它是一个旧的post。

以下SET选项有可能影响计划重用(最后的完整列表)

 SET QUOTED_IDENTIFIER ON GO SET ANSI_NULLS ON GO SET ARITHABORT ON GO 

以下两条语句来自msdn – SET ARITHABORT

将ARITHABORT设置为OFF可能会对导致性能问题的查询优化产生负面影响。

SQL Server Management Studio的默认ARITHABORT设置为ON。 将ARITHABORT设置为OFF的客户端应用程序可能会收到不同的查询计划,从而难以解决性能不佳的查询问题。 也就是说,相同的查询可以在pipe理工作室中快速执行,但在应用程序中速度慢。

另一个需要了解的有趣的话题是Parameter Sniffing ,如应用中的慢速,SSMS中的快速? 了解性能之谜 – 通过Erland Sommarskog

还有另外一种可能性,就是在使用Unicodeinput参数时,将VARCHAR列转换(内部)为NVARCHAR,正如在varchar列上对SQL索引性能进行故障排除一样 – 由Jimmy Bogard

优化为UNKNOWN

在SQL Server 2008及更高版本中,请考虑OPTIMIZE FOR UNKNOWN。 UNKNOWN:指定查询优化器在查询优化期间使用统计数据而不是初始值来确定本地variables的值。

选项(RECOMPILE)

如果重新编译是唯一的解决scheme,请使用“选项(重新编译)”而不是“重新编译”。 它有助于参数embedded优化。 读取参数嗅探,embedded和RECOMPILE选项 – Paul White

SET选项

以下SET选项可以影响计划重用,基于SQL Server 2008中的msdn – Plan Caching

  1. ANSI_NULL_DFLT_OFF 2. ANSI_NULL_DFLT_ON 3. ANSI_NULLS 4. ANSI_PADDING 5. ANSI_WARNINGS 6. ARITHABORT 7. CONCAT_NULL_YIELDS_NUL 8. DATEFIRST 9. DATEFORMAT 10. FORCEPLAN 11. LANGUAGE 12. NO_BROWSETABLE 13. NUMERIC_ROUNDABORT 14. QUOTED_IDENTIFIER

听起来可能与参数嗅探有关? 您是否尝试捕获客户端代码发送到SQL Server的什么(使用事件探查器来捕获确切的语句),然后在Management Studio中运行?

参数嗅探: SQL糟糕的存储过程执行计划性能 – 参数嗅探

我之前没有在代码中看过这个,只是在程序中,但值得一看。

你似乎没有closures你的数据阅读器 – 这可能会开始叠加多次迭代…

我有一个不同的根本原因,这个问题的症状标题完全匹配的问题。

在我的情况下,问题是结果集被应用程序的.NET代码保持打开,而它通过每个返回的logging循环,并执行另外三个针对数据库的查询! 在数千行中,这个误导性使原来的查询看起来像是基于SQL Server的计时信息来完成的。

因此修复是重构调用.NET代码,以便在处理每一行时不会使结果集处于打开状态。

我意识到OP没有提到存储过程的使用,但有一个替代解决scheme参数嗅探问题时,使用存储过程不那么优雅,但已经为我工作时, OPTION(RECOMPILE)似乎没有做任何事情。

只需将您的参数复制到过程中声明的variables,然后使用它们。

例:

 ALTER PROCEDURE [ExampleProcedure] @StartDate DATETIME, @EndDate DATETIME AS BEGIN --reassign to local variables to avoid parameter sniffing issues DECLARE @MyStartDate datetime, @MyEndDate datetime SELECT @MyStartDate = @StartDate, @MyEndDate = @EndDate --Rest of procedure goes here but refer to @MyStartDate and @MyEndDate END 

我build议你尝试创build一个存储过程 – 可以通过Sql Server进行编译和caching,从而提高性能