对SQL Server存储过程的查询对于特定数据比对所有数据要长

时间:2017-06-06 19:16:12

标签: sql-server stored-procedures

我在存储过程中有一个查询,它从我的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

2 个答案:

答案 0 :(得分:1)

无论如何震撼,我敢打赌,可以通过向索引添加额外的列(CenterId)来解决这个问题。

当差异归结为'当我向where子句添加一个额外的列时,查询需要更长的时间',这表示查询可以使用的索引不包含附加列子句。

要解决此问题,请查找查询使用的现有索引而不使用additional子句,并将附加列添加到该索引的include(),或者创建一个新索引(似乎浪费了一个新的索引来添加但是还有一个int列。

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

我只能(模糊地)猜测索引会是什么样子而不了解Event如何加入查询的其余部分,但索引可能看起来像这样,只缺少{{1 }}:

CenterId

答案 1 :(得分:0)

我在工作中面临这样的情况,我们只是在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