【问题标题】:Query on SQL Server stored procedure takes longer for specific data than for all data查询特定数据的 SQL Server 存储过程所需的时间比查询所有数据的时间长
【发布时间】:2025-12-02 19:20:03
【问题描述】:

我在一个存储过程中有一个查询,它从我的 SQL Server 数据库的几个表中检索数据。在我的应用程序中,我使用一些参数调用这个存储过程来过滤数据。

奇怪的是,对于一年中“X”周返回的所有数据(大约 2600 条记录),检索数据大约需要 30 秒。但是如果我为一年中的同一周添加一个特定的“中心”过滤器(大约 1800 条记录),大约需要 3 分钟才能获取数据!

如果我只运行查询,它运行良好(所有数据 30 秒,过滤数据大约 22 秒)。问题是当我通过存储过程运行查询时!

这怎么可能?为什么过滤后的数据比获取所有数据需要大约 x6 倍?为什么我失踪了?我是否正确编写存储过程?我怎样才能以更有效的方式做到这一点?

我的存储过程代码是这样的:

ALTER PROCEDURE EventMonitoring
    @EventType AS CHAR(1),
    @Year AS INT,
    @Week AS INT,
    @CenterID AS CHAR(2),
    @AreaID AS INT
AS
    DECLARE @SQLCommand AS VARCHAR(MAX)
    DECLARE @MessageError AS VARCHAR(MAX)

    SET @SQLCommand = ' SELECT ....
                   ....
                   ....
                   ....
                   FROM   Event E
                   INNER JOIN ...
                   INNER JOIN ...
                   INNER JOIN ... 
                   WHERE E.EventYear = ' + CAST(@Year AS VARCHAR) +  
                   ' And E.EventWeek = ' + CAST(@Week AS VARCHAR) +
                   ' And E.EventType = ' + CAST(@EventType AS VARCHAR) 

    IF @Centro <> '-' --If application sends - as the parameter, it gets all centers
    BEGIN
        @SQLCommand = @SQLCommand + ' AND E.CenterID = ''' + @CenterID + '''' 
    END

    IF @Area <> 0 --If application sends 0 as the parameter, it gets all  areas
    BEGIN
        @SQLCommand = @SQLCommand + ' AND E.AreaID = ' + CAST(@AreaID AS VARCHAR) 
    END

    SET @SQLCommand = @SQLCommand + 'GROUP BY ....'

    BEGIN TRY
        EXEC(@SQLCommand)
    END TRY
    BEGIN CATCH
        ....
    END CATCH

【问题讨论】:

  • 你能显示表结构和索引吗?表格有多少行?
  • 当您使用 DBCC FREEPROCCACHE 在 proc 之外运行查询时会发生什么?执行时间是否更接近您的 SP 执行时间?
  • 向我们展示两个计划? brentozar.com/pastetheplan
  • 它来自 ADO 连接?
  • 好的,我会试着给你看表,但是就像我说的,它有很多表,无论如何,如果是索引或表结构问题,为什么查询本身可以正常工作为什么当我将查询放在 SP 上时需要更长的时间?是的,它来自 ADO,但无论如何,如果我直接运行 SP,也会发生同样的情况

标签: sql-server stored-procedures


【解决方案1】:

无论如何,我敢打赌这可以通过在索引中添加一个额外的列 (CenterId) 来解决。

当差异归结为“当我在 where 子句中添加一个附加列时,查询需要更长的时间”,这表明附加列没有被索引覆盖,没有它,查询就可以使用子句。

要解决这个问题,找到查询使用的现有索引而不使用附加子句,并将附加列添加到该索引的include(),或者创建一个新索引(创建一个全新的索引再添加一个似乎很浪费int 列)。

使用快速查询找到正在用于Event 的索引,并查看它是否包含CenterId。如果不知道如何找到,基本上需要查看执行计划。如果您仍然找不到它,您可以分享您的执行计划,我们将帮助您找到它。使用Paste The Plan @ brentozar.com 分享您的执行计划,以下是说明:How to Use Paste the Plan

我只能(模糊地)猜测索引会是什么样子,而不知道更多关于Event如何加入你的查询的其余部分,但索引可能看起来像这样,只是缺少CenterId

create nonclustered index ix_Event_cover 
  on dbo.Event(EventYear,EventWeek,EventType)
    include (CenterId, AreaID, [JoinColumns], [SelectedColumns])

【讨论】:

  • 好的,我会试一试,但是,为什么当我运行查询本身时它运行良好?当我使用字符串查询通过 SP 运行完全相同的查询时,问题就出现了
  • @GustavoAlvarado 这将是您检查/粘贴执行计划以查找的内容。有一些晦涩难懂的可能性,但在您努力为您的问题包括执行计划之前,不值得深入研究它们。
  • 感谢@SqlZim,在生成查询计划时,我意识到缺少解决问题的索引
  • @GustavoAlvarado 乐于助人!
【解决方案2】:

我在工作中遇到了这样的情况,我们解决了在 proc 中添加 SET ARITHABORT ON 参数,因为 ADO 连接。

编辑

当一个过程在运行时 sql 引擎中生成脚本时,看不到真正的参数来为查询构建一个好的计划。它调用参数嗅探。所以尝试这样的事情:

ALTER PROCEDURE EventMonitoring
    @EventType AS CHAR(1),
    @Year AS INT,
    @Week AS INT,
    @CenterID AS CHAR(2),
    @AreaID AS INT
AS   
    DECLARE @MessageError AS VARCHAR(MAX)

    BEGIN TRY

    IF @Centro <> '-' --If application sends - as the parameter, it gets all centers
    BEGIN
        SELECT ....
                   ....
                   ....
                   ....
                   FROM   Event E
                   INNER JOIN ...
                   INNER JOIN ...
                   INNER JOIN ... 
                   WHERE E.EventYear = @Year 
                    And E.EventWeek = @Week  
                   And E.EventType =EventType  AND E.CenterID = @CenterID 
                   GROUP BY ....
    END

    IF @Area <> 0 --If application sends 0 as the parameter, it gets all  areas
    BEGIN

        SELECT ....
                   ....
                   ....
                   ....
                   FROM   Event E
                   INNER JOIN ...
                   INNER JOIN ...
                   INNER JOIN ... 
                   WHERE E.EventYear = @Year 
                    And E.EventWeek = @Week  
                   And E.EventType =EventType  AND E.AreaID = @AreaID 
                   GROUP BY ....
    END





    END TRY
    BEGIN CATCH
        ....
    END CATCH

再试一次

在快速阅读 SqlZim 指出的文章后,尝试使用 ARITHABORT ONoption(recompile) 进行最后一次拍摄

    ALTER PROCEDURE EventMonitoring
        @EventType AS CHAR(1),
        @Year AS INT,
        @Week AS INT,
        @CenterID AS CHAR(2),
        @AreaID AS INT
    AS
SET ARITHABORT ON
        DECLARE @SQLCommand AS VARCHAR(MAX)
        DECLARE @MessageError AS VARCHAR(MAX)

        SET @SQLCommand = ' SELECT ....
                       ....
                       ....
                       ....
                       FROM   Event E
                       INNER JOIN ...
                       INNER JOIN ...
                       INNER JOIN ... 
                       WHERE E.EventYear = ' + CAST(@Year AS VARCHAR) +  
                       ' And E.EventWeek = ' + CAST(@Week AS VARCHAR) +
                       ' And E.EventType = ' + CAST(@EventType AS VARCHAR) 

        IF @Centro <> '-' --If application sends - as the parameter, it gets all centers
        BEGIN
            @SQLCommand = @SQLCommand + ' AND E.CenterID = ''' + @CenterID + '''' 
        END

        IF @Area <> 0 --If application sends 0 as the parameter, it gets all  areas
        BEGIN
            @SQLCommand = @SQLCommand + ' AND E.AreaID = ' + CAST(@AreaID AS VARCHAR) 
        END

        SET @SQLCommand = @SQLCommand + 'GROUP BY .... OPTION (RECOMPILE)'

        BEGIN TRY
            EXEC(@SQLCommand)
        END TRY
        BEGIN CATCH
            ....
        END CATCH

【讨论】:

  • 我也在使用 ADO,但在我的情况下,如果我直接运行 SP,也会发生同样的情况,而不仅仅是当我从 ADO 调用 SP 时
  • 在这种情况下,我强烈建议将字符串查询转换为实际查询。
  • 这不太对。现有程序将为每次执行生成一个新的执行计划,您的版本将受到参数嗅探和相关问题的影响。 Parameter Sniffing, Embedding, and the RECOMPILE Options - Paul White
  • 我也尝试过,使用“if”语句验证参数是否设置为“all”或特定值以运行实际查询,问题是一样的!这就是为什么我不知道还能做什么
  • 感谢您的示例 Krismorte,但我添加了 SET ARITHABORT ON 和 OPTION (RECOMPILE),但仍然是同样的问题...