【问题标题】:Calling SQL Functions much slower when using SqlCommand Parameters使用 SqlCommand 参数时调用 SQL 函数要慢得多
【发布时间】:2023-03-21 08:23:01
【问题描述】:

我正在从我的 C# ASP.Net 应用程序中调用一个 SQL 函数 (UDF)。我正在调用的函数需要一个“uniqueidentifier”类型的参数。

如果我进行调用并在 SqlCommand 的 CommandText 中传递“null”,结果会在大约 3 秒后返回...

SqlCommand Command = new SqlCommand();
Command.Connection = (SqlConnection)(DbDataModifier.CreateConnection());
Command.CommandText = "select * from GetLocalFirmLoginsSummary(null) order by Date asc"
Command.CommandType = CommandType.Text;

Command.Connection.Open();

SqlDataReader Reader = Command.ExecuteReader(); // takes 3 seconds

但是,如果我进行调用并将 DBNull.Value 作为 SqlParameter 传递,结果需要 60 多秒才能返回...

SqlCommand Command = new SqlCommand();
Command.Connection = (SqlConnection)(DbDataModifier.CreateConnection());
Command.CommandText = "select * from GetLocalFirmLoginsSummary(@CustomerGroupID) order by Date asc"
Command.CommandType = CommandType.Text;

SqlParameter Param = new SqlParameter();
Param.ParameterName = "@CustomerGroupID";
Param.SqlDbType = SqlDbType.UniqueIdentifier;
Param.Direction = ParameterDirection.Input;
Param.IsNullable = true;
Param.Value = DBNull.Value;
Command.Parameters.Add(Param);

Command.Connection.Open();

SqlDataReader Reader = Command.ExecuteReader(); // takes over 60 seconds

如果我在 SQL Management Studio 中运行相同的查询,大约需要 3 秒,即使将 null 作为参数传递...

declare @CustomerGroupID uniqueidentifier
set @CustomerGroupID = null

select * from GetLocalFirmLoginsSummary(@CustomerGroupID)

我调用的函数定义为:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[GetLocalFirmLoginsSummary]
(   
    @CustomerGroupID uniqueidentifier
)
RETURNS TABLE 
AS
RETURN 
(

SELECT     CustomerGroupID, CustomerGroupName, USR, DATEADD(MONTH, DATEDIFF(MONTH, 0, createdDate), 0) AS Date, COUNT(*) AS Quantity
FROM         dbo.GetLocalFirmLogins(@CustomerGroupID) AS Logins
GROUP BY USR, CustomerGroupID, CustomerGroupName, DATEADD(MONTH, DATEDIFF(MONTH, 0, createdDate), 0)

)

...

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[GetLocalFirmLogins]
(   
@CustomerGroupID uniqueidentifier
)
RETURNS TABLE 
AS
RETURN 
(

SELECT     vw_CustomerGroupAccountDetails.CustomerGroupID, vw_CustomerGroupAccountDetails.CustomerGroupName, Organisations.USR, Organisations.town AS FirmTown, 
                  Users.id AS ID, Users.userName, Users.password, Users.isPartner, Users.isFeeEarner, Users.nlisUserName, Users.nlisPassword, Users.email, Users.lastLoginDate, Users.createdDate
FROM         vw_CustomerGroupAccountDetails RIGHT OUTER JOIN
                      Organisations ON vw_CustomerGroupAccountDetails.AccountID = CAST(Organisations.id AS nvarchar(50)) RIGHT OUTER JOIN
                  Users ON Organisations.clientId = Users.uploadedBy
WHERE     (Users.canLogin = 1) AND (Users.isDeleted = 0) AND (NOT (Organisations.USR IS NULL)) AND 
                      ((vw_CustomerGroupAccountDetails.CustomerGroupID = @CustomerGroupID) OR (@CustomerGroupID IS NULL))

)

那么,当我使用 SqlCommand 的 SqlParameter 从 C# 调用 SQL 函数时,是什么导致它花费了这么长时间?

我使用的是 SQL Server 2000。

我已经到处搜索了所有我能想到的方法,但找不到其他人有同样的问题。

【问题讨论】:

  • 当您说 SQL 时,您不是在谈论独立于数据库的Structured Query Language,而是在讨论 Microsoft 数据库产品SQL Server,对吗?
  • 很可能是参数嗅探。
  • 您是否测试过它是否特定于null?什么是正常值?
  • 我怀疑 Martin 说这可能是参数嗅探,尝试将参数变量分配给局部变量并在查询中使用局部变量,这可能有助于提高性能。
  • 在GetLocalFirmLoginsSummary调用的名为GetLocalFirmLogins的UDF中,过滤“@CustomerGroupID IS NULL”。

标签: c# sql sql-server performance user-defined-functions


【解决方案1】:

Jim,你能发布 dbo.GetLocalFirmLogins 的来源吗?

正如 Martin 所说,这可能是参数嗅探。如果是这样,那么在代码中寻找@CustomerGroupID 的查询提示可能是“外科手术”解决此问题的最佳方法。

在 dbo.GetLocalFirmLogins 中的查询中添加 OPTION (LOOP JOIN) 可以在此处修复。

我想我也可以解释为什么 SSMS 看不到问题,而 C# 可以。这部分查询:

(CustomerGroupID = @CustomerGroupID) OR (@CustomerGroupID IS NULL) 

根据您来自的连接的 ANSI NULL 设置,可能有两种不同的计划。 ANSI NULL 和效果在这里描述:

SET ANSI_NULLS

您可以通过检索此查询的结果来查看连接的 ANSI 设置:

DBCC USEROPTIONS

SSMS 可能有一个不同的 NULL 设置,您的 C# 代码会导致对查询的不同评估。

【讨论】:

  • 我现在已将 GetLocalFirmLogins 添加到原始帖子的底部。
  • 顺便说一句,我希望有一个可以在 C# 代码中实现的解决方案,因为我希望我可以把它留给其他 SQL 经验较少的人来调整 UDF 以适应他们需要。
  • 我还发现我使用的实际上是 SQL Server 2000,而不是 2008 :(
  • 我已经对参数嗅探进行了一些阅读,除非我弄错了,否则该问题是否也适用于在 SQL Management Studio 中执行的查询?如果我从 SMS 中的查询窗口调用我的 UDF,它又好又快。只有从我的 C# 代码中调用它们时才会很慢。
  • @JimRarbs - 不。为了让您的应用程序使用的缓存计划被重用many things need to be the same。 SSMS 具有不同的默认 set_options,因此除非您使用与默认不同的选项,否则它不会重用与您的应用程序使用的相同计划。请参阅Slow in the Application, Fast in SSMS? Understanding Performance Mysteries 了解更多信息。
【解决方案2】:

Management Studio 和 .NET 代码之间的性能差异通常与 ANSI_NULLS 等设置有关。

这可能是 SQL Server 2000 中的错误 - 请参阅 this post,这似乎暗示未考虑 CREATE FUNCTION 之前的 SET ANSI_NULLS ON。

我没有 SQL Server 2000 来测试这个,但也许你可以尝试编写你的函数。

【讨论】:

  • 通常不是设置本身。只是不同的设置意味着计划不会在会话之间共享,因此您无需使用为不具代表性的参数编译的缓存计划,而是获得一个新的编译计划,该计划执行良好。可能如果 OP 保持所有设置相同并清除计划缓存,问题就会消失(暂时因为它可能会在下次重新编译计划时再次出现)
  • 好的,我已经使用与 SSMS 中相同的选项设置了我的 C# 连接,包括将 ANSI_NULLS 设置为 ON。现在,当我使用填充有唯一标识符的参数调用 UDF 时,缓存计划显示它作为 nvarchar(36) 传递,但是当我使用 DBNull.Value 作为参数调用它时,它似乎作为 nvarchar 传递(4000),我猜这会导致性能问题?当我在 UDF 的定义中指定这样做时,为什么它并不总是作为“唯一标识符”传递?
  • 我可以确认 nvarchar(4000) 是问题所在。如果我在 @CustomerGroupID 和 CustomerGroupID 列引用周围放置一个 CAST(x AS NVARCHAR(36)) ,则速度会显着提高。那么如何强制 Null 参数值不作为 NVARCHAR(4000) 传递给 SQL?
  • @JimRarbs,您可以尝试设置参数的大小 (Param.Size = ...),尽管我不知道为什么这对于 UniqueIdentifier 是必需的。
  • @JimRarbs 更改查询的文本将意味着(再次)不能使用糟糕的计划。没有什么特别的原因可以说明为什么一个人会比另一个人表现更好,尽管查看您的代码我不确定为什么它使用nvarchar 作为参数数据类型。看起来很奇怪。
【解决方案3】:

这可能是由于 SQL Server 中的参数嗅探问题引起的。

您可以在以下链接中找到更多详细信息:

Parameter Sniffing

希望对您有所帮助。

【讨论】:

  • 嗨。感谢您提到“参数嗅探”。我想问..基于没有博文,我们需要在函数内部声明变量。这是否意味着......我们必须使用多语句表值函数 (MSTVF),而不再使用“内联表值函数”?
  • @zeroflaw 我最近看到了这篇关于参数嗅探的帖子,它也可以帮助你:blog.sqlauthority.com/2019/12/21/…
  • 欣赏~
猜你喜欢
  • 2010-10-22
  • 1970-01-01
  • 2017-08-31
  • 1970-01-01
  • 2022-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-24
相关资源
最近更新 更多