在查询窗口中通过exec SP与exec SP代码执行相同的代码报告相同的结果但执行时间不同

时间:2015-09-29 08:45:36

标签: sql sql-server

问题

我们试图理解为什么通过调用存储过程而不是在查询窗口中执行存储过程内容来执行相同的代码会显示非常不同的执行时间,但会返回完全相同的183行结果集。

测试1

从SSMS执行以下SP需要5分钟才能返回结果。

  

EXEC uspFleetSummaryReportSelectByDateCommand @UserID = 1468,@ Date ='2015-09-28'

作为参考,这是SP细节:

    CREATE PROCEDURE [dbo].[uspFleetSummaryReportSelectByDateCommand]
    (
      @UserID int,
      @Date DateTime
      )
    AS

    DECLARE @CustomerID int
    SET @CustomerID = (Select CustomerID FROM [User] WHERE UserID = @UserID)

    SELECT  j.JourneyID,
            j.DeviceID,
            j.StartDate,
        j.EndDate,
        ISNULL(JourneyDistance, 0.0) AS [JourneyDistance],
        CONVERT(VARCHAR(8), DATEADD(SECOND, DATEDIFF(SECOND, j.StartDate, j.EndDate), 0), 114) AS [Duration],
        v.Registration,
        v.InitOdometer,
        jt.Name AS [JourneyType],
        dt.Name AS [DeviceType],
        PrivateJourney = (dbo.fxIsPrivateJourney(j.JourneyTypeID, j.DeviceID, @UserID)),
        CONVERT(VARCHAR(8), DATEADD(SECOND, ISNULL(e.IdleTime, 0), 0), 114) AS [IdleTime]
FROM Journey j WITH (NOLOCK) 
INNER JOIN Vehicle v WITH (NOLOCK) ON v.DeviceID = j.DeviceID
INNER JOIN JourneyType jt WITH (NOLOCK) ON jt.JourneyTypeID = j.JourneyTypeID
INNER JOIN Device d WITH (NOLOCK) ON d.DeviceID = j.DeviceID
    INNER JOIN Configuration config WITH (NOLOCK) ON config.ConfigurationID = d.ConfigurationID
INNER JOIN DeviceType dt WITH (NOLOCK) ON dt.DeviceTypeID = config.DeviceTypeID
LEFT OUTER JOIN (
    SELECT 
    e.JourneyId,
    SUM(DATEDIFF(SECOND, e.StartDateTime, e.EndDateTime)) AS [IdleTime]
    FROM [Event] e WITH (NOLOCK)
    WHERE e.JourneyId = JourneyID AND e.EventType = 4/*Idle Event*/
    GROUP BY e.JourneyId
) e ON e.JourneyId = j.JourneyID
WHERE   j.StartDate BETWEEN @Date AND DATEADD(DAY,1,@Date)
        AND (j.JourneyDistance IS NOT NULL)
        AND DATEDIFF(MINUTE,j.StartDate,ISNULL(j.EndDate,getdate())) > 0
        AND j.DeviceID IN (Select v.DeviceID 
                                    FROM Vehicle v WITH (NOLOCK)
                                INNER JOIN Customer c WITH (NOLOCK) ON         c.CustomerID = v.CustomerID
                                INNER JOIN [User] u ON u.CustomerID = c.CustomerID
                                WHERE   v.CustomerID = @CustomerID AND u.UserID = @UserID
                                        AND (v.LevelOneID = u.LevelOneID Or u.LevelOneID is null) 
                                        AND (v.LevelTwoID = u.LevelTwoID Or u.LevelTwoID is null) 
                                        AND (v.LevelThreeID = u.LevelThreeID Or u.LevelThreeID is null) 
                                        AND (v.LevelFourID = u.LevelFourID Or u.LevelFourID is null) 
                                        AND (v.LevelFiveID = u.LevelFiveID Or u.LevelFiveID is null) 
                                        AND (v.DriverID = u.LevelSixID Or u.LevelSixID is null)
                                        AND ISNULL(v.HideFromCustomer,0) != 1
                             )
ORDER BY Registration,j.JourneyID

的Test2

但执行相同的SP代码并设置变量需要10秒钟才能返回结果。

请在下面找到设置了变量的相同SP。从SSMS查询窗口执行以下脚本。

DECLARE @UserID INT = 13651
DECLARE @Date DATETIME = '2015-09-28'

DECLARE @CustomerID int
SET @CustomerID = (Select CustomerID FROM [User] WHERE UserID = @UserID)

SELECT    j.JourneyID,
        j.DeviceID,
        j.StartDate,
        j.EndDate,
       ISNULL(JourneyDistance, 0.0) AS [JourneyDistance],
       CONVERT(VARCHAR(8), DATEADD(SECOND, DATEDIFF(SECOND, j.StartDate, j.EndDate), 0), 114) AS [Duration],
       v.Registration,
       v.InitOdometer,
       jt.Name AS [JourneyType],
       dt.Name AS [DeviceType],
        PrivateJourney = (dbo.fxIsPrivateJourney(j.JourneyTypeID, j.DeviceID, @UserID)),
        CONVERT(VARCHAR(8), DATEADD(SECOND, ISNULL(e.IdleTime, 0), 0), 114) AS [IdleTime]
FROM Journey j WITH (NOLOCK) 
INNER JOIN Vehicle v WITH (NOLOCK) ON v.DeviceID = j.DeviceID
INNER JOIN JourneyType jt WITH (NOLOCK) ON jt.JourneyTypeID = j.JourneyTypeID
INNER JOIN Device d WITH (NOLOCK) ON d.DeviceID = j.DeviceID
INNER JOIN Configuration config WITH (NOLOCK) ON config.ConfigurationID =     d.ConfigurationID
INNER JOIN DeviceType dt WITH (NOLOCK) ON dt.DeviceTypeID = config.DeviceTypeID
LEFT OUTER JOIN (
    SELECT 
    e.JourneyId,
    SUM(DATEDIFF(SECOND, e.StartDateTime, e.EndDateTime)) AS [IdleTime]
    FROM [Event] e WITH (NOLOCK)
    WHERE e.JourneyId = JourneyID AND e.EventType = 4/*Idle Event*/
    GROUP BY e.JourneyId
) e ON e.JourneyId = j.JourneyID
    WHERE    j.StartDate BETWEEN @Date AND DATEADD(DAY,1,@Date)
       AND (j.JourneyDistance IS NOT NULL)
       AND DATEDIFF(MINUTE,j.StartDate,ISNULL(j.EndDate,getdate())) > 0
       AND j.DeviceID IN (Select v.DeviceID 
                                    FROM Vehicle v WITH (NOLOCK)
                                   INNER JOIN Customer c WITH (NOLOCK) ON     c.CustomerID = v.CustomerID
                                    INNER JOIN [User] u ON u.CustomerID =     c.CustomerID
                                   WHERE    v.CustomerID = @CustomerID AND u.UserID = @UserID
                                            AND (v.LevelOneID = u.LevelOneID Or u.LevelOneID is null) 
                                           AND (v.LevelTwoID = u.LevelTwoID Or u.LevelTwoID is null) 
                                           AND (v.LevelThreeID =     u.LevelThreeID Or u.LevelThreeID is null) 
                                           AND (v.LevelFourID =     u.LevelFourID Or u.LevelFourID is null) 
                                           AND (v.LevelFiveID =     u.LevelFiveID Or u.LevelFiveID is null) 
                                           AND (v.DriverID = u.LevelSixID Or u.LevelSixID is null)
                                            AND ISNULL(v.HideFromCustomer,0) != 1
                            )
ORDER BY Registration,j.JourneyID

调试到目前为止

将两个陈述并排比较,它们与变量的设置相同。

并排比较结果集,它们是相同的。

隔离选择变量CUSTOMERID需要几毫秒。

传递的日期变量采用相同的格式。

我们多次运行此测试以排除缓存相关问题。

在两个测试中检查了查询执行计划。执行SP时,很明显执行TEST1时表EVENT上缺少索引。

已添加索引

   CREATE NONCLUSTERED INDEX [290915_EventTypeJourneyID, EventTypeJID,>] ON     [dbo].[Event] 
(
    [EventType] ASC,
    [JourneyId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB =     OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  =     ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

结果

TEST1的执行时间降至1秒。

问题

好的,原则问题已经解决但从根本上我不明白为什么两个测试之间的性能差距基本上都在运行相同的代码?同样的代码,应该使用相同的索引,执行时间应该相似。

感谢您对此行为的任何见解。

参考

Sql server 2008 64bit标准版。

Table.JOURNEY(350米行)

CREATE TABLE [dbo].[Journey](
    [JourneyID] [int] IDENTITY(1,1) NOT NULL,
    [StartAddress] [varchar](500) NULL,
    [StartPostcode] [varchar](50) NULL,
    [EndAddress] [varchar](500) NULL,
    [EndPostcode] [varchar](50) NULL,
    [JourneyTypeID] [int] NULL,
    [Comment] [varchar](500) NULL,
    [DriverID] [int] NULL,
    [StartDate] [datetime] NULL,
    [EndDate] [datetime] NULL,
    [IdleTimeEngineOn] [int] NULL,
    [TimeSinceLastJourney] [int] NULL,
    [JourneyDistance] [decimal](8, 2) NULL,
    [DeviceID] [int] NOT NULL,
    [tempJourneyID] [int] NULL,
    [tempCustomerID] [int] NULL,
 CONSTRAINT [Journey_PK] PRIMARY KEY CLUSTERED 
(
    [JourneyID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

ALTER TABLE [dbo].[Journey]  WITH CHECK ADD  CONSTRAINT [Device_Journey_FK1]     FOREIGN KEY([DeviceID])
REFERENCES [dbo].[Device] ([DeviceID])
GO

ALTER TABLE [dbo].[Journey] CHECK CONSTRAINT [Device_Journey_FK1]
GO

ALTER TABLE [dbo].[Journey]  WITH CHECK ADD  CONSTRAINT [Driver_Journey_FK1]     FOREIGN KEY([DriverID])
REFERENCES [dbo].[Driver] ([DriverID])
GO

ALTER TABLE [dbo].[Journey] CHECK CONSTRAINT [Driver_Journey_FK1]
GO

ALTER TABLE [dbo].[Journey]  WITH NOCHECK ADD  CONSTRAINT [JourneyType_Journey_FK1] FOREIGN KEY([JourneyTypeID])
REFERENCES [dbo].[JourneyType] ([JourneyTypeID])
GO

ALTER TABLE [dbo].[Journey] CHECK CONSTRAINT [JourneyType_Journey_FK1]
GO

Table.EVENT(36米行)

CREATE TABLE [dbo].[Event](
    [EventID] [int] IDENTITY(1,1) NOT NULL,
    [StartDateTime] [datetime] NULL,
    [EndDateTime] [datetime] NULL,
    [StartLocationID] [int] NOT NULL,
    [EndLocationID] [int] NULL,
    [AlertRaised] [bit] NULL,
    [EventRuleID] [int] NULL,
    [DeviceID] [int] NOT NULL,
    [EventMessage] [varchar](max) NULL,
    [TopSpeed] [decimal](4, 1) NULL,
    [SpeedZone] [int] NULL,
    [EventType] [int] NULL,
    [ImpactId] [int] NULL,
    [NotificationStatus] [bit] NULL,
    [CableBreakZone0] [int] NULL,
    [CableBreakDistance0] [int] NULL,
    [CableBreakZone1] [int] NULL,
    [CableBreakDistance1] [int] NULL,
    [AdValue] [int] NULL,
    [DriverId] [int] NULL,
    [VehicleId] [int] NULL,
    [JourneyId] [int] NULL,
 CONSTRAINT [Event_PK] PRIMARY KEY CLUSTERED 
(
    [EventID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY =     OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

ALTER TABLE [dbo].[Event]  WITH CHECK ADD  CONSTRAINT [Device_Event_FK1]     FOREIGN KEY([DeviceID])
REFERENCES [dbo].[Device] ([DeviceID])
GO

ALTER TABLE [dbo].[Event] CHECK CONSTRAINT [Device_Event_FK1]
GO

ALTER TABLE [dbo].[Event]  WITH CHECK ADD  CONSTRAINT [Event_Impact_FK]     FOREIGN KEY([ImpactId])
REFERENCES [dbo].[Impact] ([ImpactID])
GO

ALTER TABLE [dbo].[Event] CHECK CONSTRAINT [Event_Impact_FK]
GO

ALTER TABLE [dbo].[Event]  WITH CHECK ADD  CONSTRAINT [EventRule_Event_FK1]     FOREIGN KEY([EventRuleID])
REFERENCES [dbo].[EventRule] ([EventRuleID])
GO

ALTER TABLE [dbo].[Event] CHECK CONSTRAINT [EventRule_Event_FK1]
GO

ALTER TABLE [dbo].[Event]  WITH CHECK ADD  CONSTRAINT [FK_Event_Driver]     FOREIGN KEY([DriverId])
REFERENCES [dbo].[Driver] ([DriverID])
GO

ALTER TABLE [dbo].[Event] CHECK CONSTRAINT [FK_Event_Driver]
GO

ALTER TABLE [dbo].[Event]  WITH CHECK ADD  CONSTRAINT [FK_Event_Journey]     FOREIGN KEY([JourneyId])
REFERENCES [dbo].[Journey] ([JourneyID])
ON DELETE CASCADE
GO

ALTER TABLE [dbo].[Event] CHECK CONSTRAINT [FK_Event_Journey]
GO

ALTER TABLE [dbo].[Event]  WITH CHECK ADD  CONSTRAINT [FK_Event_Vehicle]     FOREIGN KEY([VehicleId])
REFERENCES [dbo].[Vehicle] ([VehicleID])
ON DELETE CASCADE
GO

ALTER TABLE [dbo].[Event] CHECK CONSTRAINT [FK_Event_Vehicle]
GO

3 个答案:

答案 0 :(得分:2)

有几个因素会影响SQL Server中的查询计划创建,这可能会导致真正奇怪的事情发生。

  1. 使用该时间的参数(通常)在第一次执行中创建存储过程的计划。即使参数发生变化,该计划也会保存并用于所有未来的执行。

    • 如果例如统计数据发生变化,该程序可以获得新计划。

    • 如果程序是这样的,那么最佳计划完全不同,这取决于传入的值或者例如程序有很多所谓的可选参数(例如field = @variable或@variable is NULL风格的编码) - 这可能导致非常糟糕的情况,这通常被称为参数嗅探。

    • 用于编制计划的参数可以在计划中最左边的对象的属性中看到。

  2. 如果在管理工作室中运行相同的语句,但参数是在开头分配的局部变量,则语句将使用未知值进行优化,因为此时的值是未知的,即使似乎很明显这些价值观会是什么。

    • 使用过程中定义的局部变量的过程也是如此。
  3. 如果您正在运行具有不同会话设置的应用程序,则步骤1中创建的计划可能无法使用,并且将存储和使用不同的计划。这可能导致在应用程序和管理工作室中使用相同参数执行时,相同过程似乎表现不同的情况。

  4. 有关详细信息,您可以查看Erland Sommarskog的示例Slow in the Application, Fast in SSMS? Understanding Performance Mysteries

    编辑:要了解发生了什么,请始终查看实际执行计划和统计IO输出。那些应该告诉你为什么某些东西比另一个慢(除非它阻塞,等待相关等)

答案 1 :(得分:1)

在SSMS中运行一批脚本中的查询与在存储过程中运行查询之间的区别至少在两个方面:

  1. 在创建使用SP之前SET ANSI_NULLS ON
  2. 在查询中使用内部变量代替SP的参数,如下所示:

    DECLARE @pUserID INT, @pDate DATETIME;
    SELECT @pUserID = @UserID, @pDate = @Date;
    
    SELECT ... @pUserID ...;
    

答案 2 :(得分:0)

在sql server 2008上遇到同样的问题 运行这个曾经为我修好了:

USE master
ALTER DATABASE [dbname] SET ARITHABORT ON WITH NO_WAIT;

ssms始终设置ARITHABORT,用dotnet编写的客户端不会,这就是为什么两者都使用不同的统计信息的计划。