使用和不使用存储过程调用的不同 SQL 行为

Different SQL behavior with and without a stored procedure call

运行 MS SQL Server 2008,如果我执行以下查询,它运行得相当快(2 秒或更少)和 returns 906 行:

DECLARE @ValueTime    datetime2
DECLARE @PriceUpdTime datetime2
SELECT  @ValueTime = '2014-11-28 23:00:00.000000'
SELECT  @PriceUpdTime = CURRENT_TIMESTAMP
SELECT  *
  FROM dbo.fMyTableFunction(@PriceUpdTime, @ValueTime) AS prices

但是,当我将此代码移动到存储过程中时:

CREATE PROCEDURE dbo.MDMTmp
(
    @ValueTime    datetime2 = NULL,
    @PriceUpdTime datetime2 = NULL
)
AS
BEGIN
    SET NOCOUNT ON;

    IF @PriceUpdTime IS NULL
        SELECT @PriceUpdTime = CURRENT_TIMESTAMP

    SELECT *
      FROM dbo.fMyTableFunction(@PriceUpdTime, @ValueTime) AS prices
END

来电

EXEC dbo.MDMTmp '2014-11-28 23:00:00.000000', NULL

运行速度慢得多(需要很长时间 - 我在 30 分钟后停止等待)。

在试验时,我只是将没有参数的代码放入这样的存储过程中

CREATE PROCEDURE dbo.MDMTmpVars AS
BEGIN
    DECLARE @ValueTime    datetime2
    DECLARE @PriceUpdTime datetime2

    SELECT  @ValueTime = '2014-11-28 23:00:00.000000'
    SELECT  @PriceUpdTime = CURRENT_TIMESTAMP

    SELECT  *
      FROM dbo.fMyTableFunction(@PriceUpdTime, @ValueTime) AS prices
END

然后执行

EXEC dbo.MDMTmpVars

效果非常好——1 秒内完成 906 行。知道可能有什么问题以及如何使原始存储过程正常工作吗?

更新 如果我像这样只传递 2 个参数之一

ALTER PROCEDURE dbo.MDMTmp
(
    @ValueTime    datetime2 = NULL
)
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @PriceUpdTime datetime2
    SELECT  @PriceUpdTime = CURRENT_TIMESTAMP

    IF @PriceUpdTime IS NULL
        SELECT @PriceUpdTime = CURRENT_TIMESTAMP

    SELECT *
      FROM dbo.fBondTickerValueAtRunValueDTs(@PriceUpdTime, @ValueTime) AS prices
END

然后执行

EXEC dbo.MDMTmp '2014-11-28 23:00:00.000000'

它工作正常,但是如果我将 PriceUpdTime 作为参数,它就不起作用,无论是我传递 NULL 还是今天的日期作为字符串。

更新 2 这也是有效的:

ALTER PROCEDURE dbo.MDMTmp
(
    @ValueTimeIn    datetime2 = NULL,
    @PriceUpdTimeIn datetime2 = NULL
)
AS
BEGIN
    SET NOCOUNT ON;

    IF @PriceUpdTimeIn IS NULL
        SELECT @PriceUpdTimeIn = CURRENT_TIMESTAMP

    DECLARE @ValueTime    datetime2
    DECLARE @PriceUpdTime datetime2
    SELECT  @ValueTime    = @ValueTimeIn
    SELECT  @PriceUpdTime = @PriceUpdTimeIn

    SELECT *
      FROM dbo.fBondTickerValueAtRunValueDTs(@PriceUpdTime, @ValueTime) AS prices
END

然后执行

EXEC dbo.MDMTmp '2014-11-28 23:00:00.000000', NULL
EXEC dbo.MDMTmp '2014-11-28 23:00:00.000000', '2015-02-10'

也许是参数嗅探,我该如何解决?

一个存储过程有一个存储的执行计划。使用传递给它的第一个参数创建和优化该计划。如果它是一个可能差异很大的参数,请考虑在 table 值函数中使用选项 (OPTIMIZE FOR (@ValueTime UNKNOWN, @PriceUpdTime UNKNOWN))。

更多信息here

您遇到了一个叫做参数嗅探的事情,您会找到很多视频/其他 material。基本上,它的发生是因为 SQL 服务器将根据它获得的参数为过程创建计划,而你的参数是 NULL @PriceUpdTime -- 计划是基于此完成的。

如果对于这种情况,添加 "option (optimize for (@PriceUpdTime unknown))" 将创建具有未知值的计划,而不是 NULL,这对您来说可能是足够好的解决方案。

如果您知道您将获得几种不同类型的日期作为参数,您还可以考虑将 "option (recompile)" 添加到语句中。这样,每次调用过程时都会完成查询计划。它会消耗一些 CPU,但如果你是 table,你正在访问的是巨大的,糟糕的计划可能会导致严重的问题,这可能是值得的。