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