优化SQL存储过程

Posted

技术标签:

【中文标题】优化SQL存储过程【英文标题】:optimizing TSQL stored procedure 【发布时间】:2013-01-08 12:51:14 【问题描述】:

我正在尝试在我们的网站上将下面的存储过程调整为每分钟 30k 次。

CREATE PROCEDURE [dbo].[mltHttpCallStatus] 

    @SupplierId     AS INTEGER,
    @CallIsGood     AS BIT,
    @MaxWorkerThreads   AS INT,
    @MaxIOThreads       AS INT,
    @AvailWorkerThreads AS INT,
    @AvailIOThreads     AS INT,
    @ScriptTypeId       AS INT,
    @SiteTypeId         AS VARCHAR(50),
    @ConnectionTime     AS INT,
    @SiteName       AS VARCHAR(50),
    @HostName       AS VARCHAR(50)

AS

    --DEBUG BEN (Flight details keep failing) 07012008 19:30
    --Return
SET NOCOUNT ON

DECLARE @GoodCalls      AS INT,
    @BadCalls       AS INT

SET @BadCalls = 0
SET @GoodCalls = 0

IF @CallIsGood = 1
    SET @GoodCalls = 1

ELSE
    SET @BadCalls = 1

    UPDATE  HttpCallStatus_tbl SET
        GoodCalls       = GoodCalls + @GoodCalls,
        BadCalls        = BadCalls + @BadCalls,
        TotalConnectionTime = TotalConnectionTime + @ConnectionTime
     --WHERE dbo.datepart_fn(DayDate) = dbo.datepart_fn(getDate())
    WHERE DATEADD(dd, 0, DATEDIFF(dd, 0, DayDate)) = DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE()))
        AND DATEPART(HOUR, DayDate) = DATEPART(HOUR, getDate()) 
        AND SupplierId   = @SupplierId
        AND ScriptTypeId = @ScriptTypeId
        AND SiteTypeId = @SiteTypeId
        AND SiteName = @SiteName
        AND HostName = @HostName

    IF @@ROWCOUNT = 0

    BEGIN
        INSERT INTO HttpCallStatus_tbl (DayDate,SupplierId,GoodCalls,BadCalls,ScriptTypeId,SiteTypeId,TotalConnectionTime, 
                        MaxWorkerThreads,MaxIOThreads,AvailWorkerThreads,AvailIOThreads,SiteName,HostName)
             VALUES (CONVERT(DATETIME, getDate(), 103),
                @SupplierId,
                @GoodCalls,
                @BadCalls,
                @ScriptTypeId,
                @SiteTypeId,
                @ConnectionTime,
                0,
                0,
                0,
                0,
                @SiteName,
                @HostName)
    END

表结构

    Column_name         Type        Length
    DayDate             datetime    8
    SupplierId          int     4
    GoodCalls           int     4
    BadCalls            int     4
    ScriptTypeId                int     4
    SiteTypeId          varchar     50
    TotalConnectionTime         int     4
    MaxWorkerThreads            int     4
    MaxIOThreads                int     4
    AvailWorkerThreads          int     4
    AvailIOThreads              int     4
    SiteName            varchar     50
    HostName            varchar     50
    SearchCount         int     4
    DomainId            int     4

索引

[PK_HttpCallStatus_tbl] clustered, unique, primary key [DayDate],[SupplierId]

[IX_HttpCallStatus_tbl] nonclustered [SupplierId]

[idx_HttpCallStatus_tbl_1]  nonclustered    
[SupplierId], [ScriptTypeId], [SiteTypeId], [SiteName], [HostName]
 , [DayDate], [GoodCalls], [BadCalls], [TotalConnectionTime]

我注意到这些变量是未使用的@MaxWorkerThreads、@MaxIOThreads、@AvailWorkerThreads、 @AvailIOThreads。也可以使变量@goodcalls & @Badcalls TINYINTS。 我还在插入语句 VALUES (CONVERT(DATETIME, getDate(), 103) 中注意到了这种转换。但我认为这是默认值,因此可以更改为 Getdate()

我的问题是很难衡量改进,因为它们都很快,我是否值得进行这些更改,我什至会看到一点点收获吗?

【问题讨论】:

包含索引的表结构是什么? 您使用的是哪个版本的 SQL Server? 2005 年、2008 年还是 2012 年? @MartinSmith 嗨,马丁,我现在添加了它们......它是一个 900 万行的表 @Chris Gessler im on 2012 Enterprise 如果您每秒调用 500 次,那么我很确定您会发现在几个小时内有一些重复项,两个并发事务进行更新,找不到行然后继续做插入。我可能只有一个 DATE 列和一个单独的 int hours 列,并添加一个适当的唯一索引。 【参考方案1】:

试试这个:

CREATE PROCEDURE [dbo].[mltHttpCallStatus] 

    @SupplierId     AS INTEGER,
    @CallIsGood     AS BIT,
    @MaxWorkerThreads   AS INT,
    @MaxIOThreads       AS INT,
    @AvailWorkerThreads AS INT,
    @AvailIOThreads     AS INT,
    @ScriptTypeId       AS INT,
    @SiteTypeId         AS VARCHAR(50),
    @ConnectionTime     AS INT,
    @SiteName       AS VARCHAR(50),
    @HostName       AS VARCHAR(50)

AS

    --DEBUG BEN (Flight details keep failing) 07012008 19:30
    --Return
SET NOCOUNT ON

DECLARE @GoodCalls      AS INT,
        @BadCalls       AS INT,
        @StartHour DateTime,
        @EndHour   DateTime

Select  @StartHour = DATEADD(Hour, DATEDIFF(Hour, 0, GETDATE()), 0),
        @EndHour   = DATEADD(Hour, 1 + DATEDIFF(Hour, 0, GETDATE()), 0),
        @BadCalls = 0,
        @GoodCalls = 0

IF @CallIsGood = 1
    SET @GoodCalls = 1

ELSE
    SET @BadCalls = 1

    UPDATE  HttpCallStatus_tbl SET
        GoodCalls       = GoodCalls + @GoodCalls,
        BadCalls        = BadCalls + @BadCalls,
        TotalConnectionTime = TotalConnectionTime + @ConnectionTime
     --WHERE dbo.datepart_fn(DayDate) = dbo.datepart_fn(getDate())
    WHERE DayDate >= @StartHour
        And DayDate < @EndHour
        AND SupplierId   = @SupplierId
        AND ScriptTypeId = @ScriptTypeId
        AND SiteTypeId = @SiteTypeId
        AND SiteName = @SiteName
        AND HostName = @HostName

    IF @@ROWCOUNT = 0

    BEGIN
        INSERT INTO HttpCallStatus_tbl (DayDate,SupplierId,GoodCalls,BadCalls,ScriptTypeId,SiteTypeId,TotalConnectionTime, 
                        MaxWorkerThreads,MaxIOThreads,AvailWorkerThreads,AvailIOThreads,SiteName,HostName)
             VALUES (CONVERT(DATETIME, getDate(), 103),
                @SupplierId,
                @GoodCalls,
                @BadCalls,
                @ScriptTypeId,
                @SiteTypeId,
                @ConnectionTime,
                0,
                0,
                0,
                0,
                @SiteName,
                @HostName)
    END

请注意,我在查询之前计算了开始时间和结束时间,然后我将其用作要搜索的日期范围。由于您的主键的第一列是 DayDate,这应该会大大提高性能。

【讨论】:

【参考方案2】:

我认为您在错误的地方进行了优化。您担心插入时一行的转换,而忽略了它前面的 where 子句中的转换/函数。看起来您已将逻辑从标量函数移回 where 子句,这将有所帮助,但我将专注于更改这一点:

WHERE DATEADD(dd, 0, DATEDIFF(dd, 0, DayDate)) = DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE()))
        AND DATEPART(HOUR, DayDate) = DATEPART(HOUR, getDate()) 

进入可以与索引一起使用的东西。看看 sargability http://en.wikipedia.org/wiki/Sargable 并找出一种方法来避免您的数据出现这种情况。这才是真正的收获所在。

另外,还是在 where 子句中,只要检查所有其他条件是否具有相同的数据类型,以防止隐式转换。

【讨论】:

@MartinSmith 是的,它对我来说很重要,但我想我会指出它。如果没有数据分布,我想总是有点猜测问题出在哪里。 是的,它最初是一个 UDF,只是在其中使用了一个日期函数 @Meff 我删除了该评论,因为如果每小时有一条记录与 5 个相等谓词匹配,那么在一年的时间里,这意味着 8,760 个日期时间,因此在 DayDate sargable 上设置条件将课程帮助。 @DamagedGoods - 你能确保所有日期时间都以一致的格式存储,四舍五入到小时,并且只使用相等谓词还是你真的需要分钟和秒?【参考方案3】:

几件事:

以下将所有插入的 GETDATE() 转换为 'dd/mm/yyyy'(无时间信息)

CONVERT(DATETIME, getDate(), 103)

因此,可以将数据类型更改为 DATE 而不是 DATETIME。通过该更改,可以更改/删除以下项目:

CONVERT(DATETIME, getDate(), 103) 

Can change to:

CAST(GETDATE() AS DATE)

这可以一起删除,因为时间部分没有被保存:

AND DATEPART(HOUR, DayDate) = DATEPART(HOUR, getDate()) 

这个

DATEADD(dd, 0, DATEDIFF(dd, 0, DayDate)) = DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE()))

可以改为:

DayDate = CAST(GETDATE() AS DATE)

因为您使用的是 SQL Server 2008+,所以您可以使用 MERGE 而不是 UPDATE/INSERT 逻辑

http://technet.microsoft.com/en-us/library/bb510625.aspx

【讨论】:

克里斯,合并语句会更快吗?我试图替换 DayDate = CAST(GETDATE() AS DATE) 但它没有返回任何结果.. 需要小时部分来选择正确数量的记录,因为我没有得到不同的结果 @DamagedGoods - 合并性能可能在批量插入/更新中最为显着,因此您可能看不到太多收益。而且我很难相信 DayDate 除了日期信息之外还有其他任何东西,除非你没有分享另一块拼图。 好的,感谢您的努力,我使用了一个变量将 getdate 代码移出 WHERE 子句,因为这是一个即时修复。它奏效了。服务器上的 CPU 下降率从 80% 下降到 10%【参考方案4】:
    删除索引 [IX_HttpCallStatus_tbl] nonclustered [SupplierId][idx_HttpCallStatus_tbl_1]覆盖。 更改索引 [idx_HttpCallStatus_tbl_1] nonclustered [SupplierId], [ScriptTypeId], [SiteTypeId], [SiteName], [HostName], [DayDate], [GoodCalls], [BadCalls], [TotalConnectionTime] [SupplierId], [ScriptTypeId], [SiteTypeId], [SiteName], [HostName], [DayDate] 和include 列上的索引 [GoodCalls], [BadCalls], [TotalConnectionTime]

就存储过程而言,我会在更新上方添加:

DECLARE @MinDate DATETIME
DECLARE @MaxDate DATETIME
SET @MinDate=DATEADD(HOUR,DATEPART(HOUR, GETDATE()),CONVERT(DATETIME,CONVERT(VARCHAR,GETDATE(),101)))
SET @MaxDate=DATEADD(HOUR,1,@MinDate)

并更改 where to be 的前两行

WHERE  DayDate>=MinDate AND DayDate<MaxDate 

【讨论】:

感谢cmets,我将删除第一个索引,第二个索引实际上是包含列,它在格式化过程中丢失了,对不起 @DamagedGoods - 对于某些查询,较窄的索引可能仍然有用。我们中的任何人都不可能在不知道您的查询工作量的情况下说您应该绝对放弃它。您可以使用索引使用统计信息中的各种 DMV 来评估它是否首先被使用。 @MartinSmith 自上次重启以来它的读写比率非常低,所以我放弃了它

以上是关于优化SQL存储过程的主要内容,如果未能解决你的问题,请参考以下文章

数据库基础详解:存储过程、视图、游标、SQL语句优化以及索引

进一步优化 SQL Server 存储过程

优化使用函数的 SQL Server 存储过程?

SQL 存储过程优化经验

Sql server2014 内存优化表 本地编译存储过程

sqlserver存储过程会不会被阻塞