NET应用程序中的SQL查询速度较慢,但在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'
)

基本上,有一个Users表,一个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;
n4rzul asked 2019-11-08T02:16:02Z
14个解决方案
64 votes

以我的经验,查询在SSMS中快速运行但从.NET运行缓慢的通常原因是由于连接的SET-tings不同。 当通过SSMS或SET ARITHABORT ON打开连接时,会自动发出一系列SET266命令以设置执行环境。 不幸的是,SSMS和SqlConnection具有不同的SET默认值。

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

SQL Profiler可用于监视SSMS和.NET发出的SET命令,因此您可以找到其他差异。

以下代码演示了如何发出SET命令,但请注意,此代码尚未经过测试。

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.
}
Daniel Renshaw answered 2019-11-08T02:17:12Z
28 votes

如果这是参数嗅探,请尝试将option(recompile)添加到查询的末尾。我建议创建一个存储过程,以更易于管理的方式封装逻辑。 也达成共识-根据示例判断,为什么只需要三个参数就传递五个参数?您可以改用此查询吗?

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

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

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
Piotr Rodak answered 2019-11-08T02:16:26Z
11 votes

尽管实时系统(在同一SQL服务器上)运行良好,但在测试环境中也存在相同的问题。 添加OPTION(RECOMPILE)和OPTION(OPTIMIZE FOR(@ p1 UNKNOWN))都没有帮助。

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

将捕获的查询文本放入SSMS,可以确认其运行速度与.net客户端一样慢。

我发现将参数的类型更改为AnsiText可以解决此问题:

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

我永远无法解释为什么测试和实际环境在性能上有如此显着的差异。

Daz answered 2019-11-08T02:18:12Z
7 votes

希望您的特定问题现在已经解决,因为它是旧帖子。

以下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的客户端应用程序可以接收不同的查询计划,从而很难对性能不佳的查询进行故障排除。 也就是说,同一查询可以在Management Studio中快速执行,但在应用程序中执行缓慢。

另一个需要理解的有趣主题是SET,如应用程序中的慢速,SSMS中的快速? 了解性能奥秘-Erland Sommarskog

还有另一种可能性是,使用Unicode输入参数将VARCHAR列(内部)转换为NVARCHAR,如对varchar列的SQL索引性能进行故障排除中所述-Jimmy Bogard

优化未知

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

选项(建议)

如果唯一的解决方法是使用“ OPTION(RECOMPILE)”而不是“ WITH RECOMPILE”。 它有助于参数嵌入优化。 读取参数嗅探,嵌入和RECOMPILE选项-Paul White

SET选项

基于msdn-SQL Server 2008中的计划缓存,以下SET选项可能会影响计划重用

  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.日期9.日期格式10. FORCEPLAN 11.语言12. NO_BROWSETABLE 13. NUMERIC_ROUNDABORT 14。
LCJ answered 2019-11-08T02:20:05Z
6 votes

问题很可能出在准则上

tal.TrustAccountLogDate < @TrustAccountLogDate2

最佳执行计划将高度依赖于参数的值,传递1910-01-01(不返回任何行)最有可能导致与2100-12-31(返回所有行)不同的计划。

在查询中将值指定为文字时,SQL Server会知道在计划生成期间使用哪个值。 使用参数时,SQL Server将仅生成一次计划,然后重新使用它,并且如果后续执行中的值与原始执行的值相差太大,则该计划将不是最佳选择。

要解决这种情况,您可以在查询中指定OPTION(RECOMPILE)。 除非将查询添加到存储过程中不能解决此特定问题,除非  您可以使用RECOMPILE创建过程。

其他人已经提到了这一点(“参数嗅探”),但是我认为对该概念进行简单的解释不会有什么坏处。

erikkallen answered 2019-11-08T02:20:58Z
3 votes

可能是类型转换问题。 数据层上的所有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;

由于它们都被分配了相同的变量。

(这可能导致服务器制定不同的计划,因为它期望将四个不同的变量作为对4个常量的选择-将其设为2个变量可能会对服务器优化产生影响。)

Hogan answered 2019-11-08T02:21:50Z
2 votes

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

    object value = cmd.ExecuteScalar();

    if (value == null)
        return 0;
    else
        return (double)value;
Dan Diplo answered 2019-11-08T02:22:17Z
2 votes

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

参数嗅探:SQL存储过程执行计划性能较差-参数嗅探

我以前没有在代码中看到过此内容,仅在过程中见过,但这值得一看。

Meff answered 2019-11-08T02:22:56Z
2 votes

就我而言,问题是我的实体框架正在生成使用exec sp_executesql的查询。

当参数的类型不完全匹配时,执行计划将不使用索引,因为它决定将转换放入查询本身。可以想象,这会导致性能大大降低。

在我的情况下,该列定义为CHR(3),并且实体框架在查询中传递了N'str',这导致了从nchar到char的转换。 因此,对于如下所示的查询:

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

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

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

在我的情况下,最简单的解决方案是更改列类型,或者,您也可以费心处理代码以使其首先通过正确的类型。

Eyal answered 2019-11-08T02:23:57Z
1 votes

我今天遇到了这个问题,这解决了我的问题:[https://www.mssqltips.com/sqlservertip/4318/sql-server-stored-procedure-runs-fast-in-ssms-and-slow-in-application/]

我将我的代码放在开头:设置ARITHABORT ON

Holp可以帮助您!

Italo Reis answered 2019-11-08T02:24:37Z
0 votes

您似乎并没有关闭数据读取器-这可能会在许多迭代中加起来...

Paddy answered 2019-11-08T02:25:03Z
0 votes

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

在我的情况下,问题在于结果集在循环返回的每个记录并针对数据库执行另外三个查询时,由应用程序的.NET代码保持打开状态! 根据SQL Server的计时信息,这在数千行中产生了误导性,使原始查询看起来很难完成。

因此,解决方法是重构进行调用的.NET代码,以使其在处理每一行时不会使结果集保持打开状态。

Tim Abell answered 2019-11-08T02:25:43Z
0 votes

我意识到OP并未提及存储过程的使用,但是在使用存储过程时,还有一种解决参数嗅探问题的替代方案,虽然不太优雅,但在OPTION(RECOMPILE)似乎什么也没做的时候对我有用。

只需将参数复制到过程中声明的变量中,然后使用它们即可。

例:

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
GlacialSpoon answered 2019-11-08T02:26:18Z
-1 votes

我建议您尝试创建一个存储过程-可以由Sql Server对其进行编译和缓存,从而提高性能

Julius A answered 2019-11-08T02:26:45Z
translate from https://stackoverflow.com:/questions/2736638/sql-query-slow-in-net-application-but-instantaneous-in-sql-server-management-st