SQL Server proc运行速度比普通查询慢5倍

时间:2011-08-17 22:59:49

标签: sql-server stored-procedures

我有以下查询:

DECLARE @DaysNotUsed int = 14
DECLARE @DaysNotPhoned int = 7

--Total Unique Students
DECLARE @totalStudents TABLE (SchoolID uniqueidentifier, TotalUniqueStudents int)
INSERT INTO @totalStudents
SELECT 
        SSGG.School,
        COUNT(DISTINCT S.StudentID)
    FROM Student S
        INNER JOIN StudentStudents_GroupGroups SSGG ON (SSGG.Students = S.StudentID AND SSGG.School = S.School)
        INNER JOIN [Group] G ON (G.GroupID = SSGG.Groups AND G.School = SSGG.School)
        INNER JOIN SessionHistory SH ON (SH.Student = S.StudentID AND SH.School = S.School AND SH.StartDateTime > GETDATE() - @DaysNotUsed)
    WHERE G.IsBuiltIn = 0
        AND S.HasStartedProduct = 1
    GROUP BY SSGG.School

--Last Used On
DECLARE @lastUsed TABLE (SchoolID uniqueidentifier, LastUsedOn datetime)
INSERT INTO @lastUsed
SELECT
        vi.SchoolID,
        MAX(sh.StartDateTime)
    FROM View_Installation as vi
        INNER JOIN SessionHistory as sh on sh.School = vi.SchoolID
    GROUP BY vi.SchoolID

SELECT 
        VI.SchoolID, 
        INS.DateAdded,
        INS.Removed,
        INS.DateRemoved,
        INS.DateToInclude,
        VI.SchoolName AS [School Name], 
        VI.UsersLicensed AS [Licenses],
        ISNULL(TS.TotalUniqueStudents, 0) as [Total Unique Students],
        ISNULL(TS.TotalUniqueStudents, 0) * 100 / VI.UsersLicensed as [% of Students Using],
        S.State,
        LU.LastUsedOn,
        DATEDIFF(DAY, LU.LastUsedOn, GETDATE()) AS [Days Not Used],
        SI.AreaSalesManager AS [Sales Rep],
        SI.CaseNumber AS [Case #],
        SI.RenewalDate AS [Renewal Date],
        SI.AssignedTo AS [Assigned To],
        SI.Notes AS [Notes]
    FROM View_Installation VI
        INNER JOIN School S ON S.SchoolID = VI.SchoolID
        LEFT OUTER JOIN @totalStudents TS on TS.SchoolID = VI.SchoolID
        INNER JOIN @lastUsed LU on LU.SchoolID = VI.SchoolID
        LEFT OUTER JOIN InactiveReports..SchoolInfo SI ON S.SchoolID = SI.SchoolID
        LEFT OUTER JOIN InactiveReports..InactiveSchools INS ON S.SchoolID = INS.SchoolID
    WHERE VI.UsersLicensed > 0
        AND VI.LastPhoneHome > GETDATE() - @DaysNotPhoned
        AND
        (
            (
                SELECT COUNT(DISTINCT S.StudentID)
                    FROM Student S
                        INNER JOIN StudentStudents_GroupGroups SSGG ON (SSGG.Students = S.StudentID AND SSGG.School = S.School)
                        INNER JOIN [Group] G ON (G.GroupID = SSGG.Groups AND G.School = SSGG.School)
                    WHERE G.IsBuiltIn = 0
                        AND S.School = VI.SchoolID
            ) * 100 / VI.UsersLicensed < 50
            OR
            VI.SchoolID NOT IN 
            (
                SELECT DISTINCT SH1.School
                FROM SessionHistory SH1
                WHERE SH1.StartDateTime > GETDATE() - @DaysNotUsed
            ) 
        )
    ORDER BY [Days Not Used] DESC

在SSMS中运行这样的普通sql需要大约10秒才能运行。当我使用完全相同的代码创建存储过程时,查询需要50秒。 proc的实际代码中唯一的区别是IDE默认放入的SET NOCOUNT ON,但是将该行添加到查询中没有任何影响。有什么想法会导致如此戏剧性的减速吗?

编辑我在开头忽略了声明语句。这些不在proc中,而是它的参数。这可能是区别吗?

4 个答案:

答案 0 :(得分:2)

我同意潜在的参数嗅探问题,但我也会检查这些设置。

程序:

SELECT uses_ansi_nulls, uses_quoted_identifier
  FROM sys.sql_modules
  WHERE [object_id] = OBJECT_ID('dbo.procedure_name');

对于查询运行速度快的SSMS查询窗口:

SELECT [ansi_nulls], [quoted_identifier]
  FROM sys.dm_exec_sessions
  WHERE session_id = @@SPID;

如果其中任何一个不匹配,您可以考虑删除存储过程并使用这两个匹配的设置重新创建它。例如,如果过程具有uses_quoted_identifier = 0且会话的quoted_identifier = 1,则可以尝试:

DROP PROCEDURE dbo.procedure_name;
GO
SET QUOTED_IDENTIFIER ON;
GO
CREATE PROCEDURE dbo.procedure_name
AS
BEGIN
    SET NOCOUNT ON;
    ...
END
GO

理想情况下,所有模块都将使用完全相同的QUOTED_IDENTIFIER和ANSI_NULLS设置创建。设置关闭时可能会创建该过程(两者都默认为打开),或者您执行查询的位置可能会关闭一个或两个选项(您可以在工具/选项/下的SSMS中更改此行为)查询执行/ SQL Server / ANSI)。

我不会对使用不同设置的存储过程的行为做任何免责声明(例如,您可能希望ANSI_NULLS关闭,因此您可以比较NULL = NULL),您必须测试,但是至少你会将正在运行的查询与相同的选项进行比较,这将有助于缩小潜在的参数嗅探问题。但是,如果你故意使用SET ANSI_NULLS OFF,我提醒你找到其他方法,因为这种行为最终将不受支持。

参数嗅探的其他方法:

  • 确保您不会无意中使用非典型参数编译程序
  • 在程序上或在似乎是受害者的语句上使用重新编译选项(我不确定所有这些是否有效,因为我只能告诉您使用的是SQL Server 2005或更高版本,以及其中一些是在2008年推出的)
  • 声明与您的输入参数类似的局部变量,并将输入参数值传递给它们,稍后使用文档中的局部变量并忽略输入参数

最后一个选项是我最不喜欢的,但它是在故障排除和用户抱怨时最快/最简单的修复。

答案 1 :(得分:1)

此外,除了提及的所有内容之外,如果您使用的是SQL Server 2008及更高版本,请查看OPTIMIZE FOR UNKNOWN http://blogs.msdn.com/b/sqlprogrammability/archive/2008/11/26/optimize-for-unknown-a-little-known-sql-server-2008-feature.aspx

答案 2 :(得分:0)

我建议重新编译存储过程的执行计划。

用法:sp_recompile'[target]'

示例:sp_recompile'dbo.GetObject'

当您从SSMS执行查询时,查询计划会在每次执行时自动重做。但是,对于存储过程,sql server会缓存存储过程的执行计划,以及每次调用存储过程时都会使用的执行计划。

Link for sp_recompile

您还可以在存储过程中更改要与WITH RECOMPILE子句一起使用的过程。

示例:

CREATE PROCEDURE dbo.GetObject
(
    @parm1 VARCHAR(20)
)
WITH RECOMPILE
AS
BEGIN 
  -- Queries/work here.
END

但是,这会强制执行计划重新编译每次时间调用存储过程。这对于开发/测试,其中proc和/或数据经常变化很有用。确保在将其部署到生产环境时将其删除,因为这可能会影响性能。

sp_recompile仅重新编译执行计划一次。如果您需要在以后再次进行,则需要再次拨打电话。

祝你好运!

答案 3 :(得分:0)

好的,谢谢大家的帮助。原来这是一个非常愚蠢的菜鸟错误。我第一次创建proc时,它是在我的用户架构而不是dbo架构下创建的。当我调用proc时,我只是在做'exec proc_name',我现在意识到它正在使用我用户模式下的proc版本。运行'exec dbo.proc_name'按预期运行。