0

I am trying to tweak the stored procedure below as its called 30k times a minute on our website.

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

table structure

    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

Indexes

[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]

I noticed these variables are unused @MaxWorkerThreads, @MaxIOThreads, @AvailWorkerThreads, @AvailIOThreads. also could make the variables @goodcalls & @Badcalls TINYINTS. I also noticed this conversion in the insert statement VALUES (CONVERT(DATETIME, getDate(), 103). but I think this is the default so could change to Getdate()

my issues is its hard to measure the improvements as they are both so quick, is it worth me making these changes, will I even see a small gain ?

5
  • 2
    What is the table structure including indexes? Commented Jan 8, 2013 at 12:58
  • Which version of SQL Server are you using? 2005, 2008 or 2012? Commented Jan 8, 2013 at 13:04
  • @MartinSmith Hi Martin, ive added them now.. its a 9 million row table
    – Chris Wood
    Commented Jan 8, 2013 at 13:27
  • @Chris Gessler im on 2012 Enterprise
    – Chris Wood
    Commented Jan 8, 2013 at 13:28
  • If you're calling this 500 times a second then I'm pretty sure that you will find that you have some duplicates in there for hours where two concurrent transactions do the update, find no rows then proceed to do the insert. I'd probably just have a DATE column and a separate int hours column and add an appropriate unique index. Commented Jan 8, 2013 at 13:49

4 Answers 4

2

Try this:

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

Notice that I calculate the start hour and end hour prior to the query, then I use that as the range of dates to search for. Since your primary key's first column is DayDate, this should make a big improvement in performance.

0
2

I think you're optimising in the wrong place. You're worrying about a conversion for one row on an insert, while ignoring the conversion/function in the where clause that precedes it. It looks like you've moved the logic from a scalar function back to the where clause, which will help, but I'd focus on changing this:

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

Into something that will work with indexes. Take a look at sargability http://en.wikipedia.org/wiki/Sargable and work out a way of avoiding this with your data. That'll be where the real gains are.

Also, still in the where clause, just check all your other conditions have the same data type, to prevent implicit conversions.

4
  • @MartinSmith true, it sticks out to me though so thought I'd point it out. Without data distribution I suppose it's always a bit of a guess as to where the problem is.
    – Meff
    Commented Jan 8, 2013 at 13:06
  • yes it was originally a UDF that was simply using a date function inside of it
    – Chris Wood
    Commented Jan 8, 2013 at 13:32
  • @Meff I deleted that comment because if there is one record matching the 5 equality predicates for every hour then over the course of a year that means 8,760 datetimes so making the condition on DayDate sargable would of course help. Commented Jan 8, 2013 at 13:33
  • @DamagedGoods - Can you ensure all datetimes are stored in a consistent format rounded to the hour and just use an equality predicate on it or do you actually need the minutes and seconds? Commented Jan 8, 2013 at 13:40
1

Couple of things:

The following converts GETDATE() to 'dd/mm/yyyy' (no time information) for all inserts

CONVERT(DATETIME, getDate(), 103)

Therefore, the datatype can be changed to DATE instead of DATETIME. With that change, the following items can be changes/removed:

CONVERT(DATETIME, getDate(), 103) 

Can change to:

CAST(GETDATE() AS DATE)

This can be removed all together, as the time portion is not being saved:

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

This

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

Can be changed to:

DayDate = CAST(GETDATE() AS DATE)

And because you're using SQL server 2008+, you can use MERGE instead of UPDATE/INSERT logic

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

3
  • Chris, will the merge statement be faster ? I attempted to replace DayDate = CAST(GETDATE() AS DATE) but it didn't return any results.. the hour part is needed to select the correct number of records as without i get differing results
    – Chris Wood
    Commented Jan 8, 2013 at 13:59
  • @DamagedGoods - Merge performance is probably most noticable in bulk insert/updates, so you might not see much of a gain. And I find it hard to believe that DayDate holds anything but date information unless there's another piece to the puzzle that you're not sharing. Commented Jan 8, 2013 at 14:24
  • ok thanks for the effort, I used a variable to move the getdate code out of the WHERE clause as it was an immediate fix. it worked a treat.. CPU on the dropped on the server dropped from 80% to 10%
    – Chris Wood
    Commented Jan 9, 2013 at 13:57
1
  1. Drop index [IX_HttpCallStatus_tbl] nonclustered [SupplierId]. It is covered by [idx_HttpCallStatus_tbl_1].
  2. Change index [idx_HttpCallStatus_tbl_1] nonclustered [SupplierId], [ScriptTypeId], [SiteTypeId], [SiteName], [HostName], [DayDate], [GoodCalls], [BadCalls], [TotalConnectionTime] to an index on [SupplierId], [ScriptTypeId], [SiteTypeId], [SiteName], [HostName], [DayDate] and include columns [GoodCalls], [BadCalls], [TotalConnectionTime]

As far as the sproc goes, I would add this above the update:

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)

and change the first two lines of the where to be

WHERE  DayDate>=MinDate AND DayDate<MaxDate 
3
  • thanks for the comments, I will drop the first index, the 2nd index is actually got included columns, it was lost during formatting, sorry
    – Chris Wood
    Commented Jan 8, 2013 at 13:52
  • @DamagedGoods - The narrower index might still be useful for some queries. It is impossible for any of us to say that you should definitely drop it without knowing your query workload. You can use the various DMVs on index usage stats to assess whether it is being used first. Commented Jan 8, 2013 at 13:55
  • @MartinSmith it has a very low read to write ratio since last restart so I dropped it
    – Chris Wood
    Commented Jan 8, 2013 at 14:06

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.