Performance Tuning

You are currently browsing articles tagged Performance Tuning.

Sometimes you’re in a situation where your stored procedure needs to insert several new records and some fields are based on calculations, or one of the fields contains a kind of batch number that needs to increase for every record where another certain field contains the same value.

In this situation you may have a habit of building a loop to insert the records, such as the following:

(I’ve used the Person.Address table from the AdventureWorks DB for demonstration purposes – it doesn’t contain too many fields :-) )

declare @counter int;
set @counter = 1;
while @counter <= 10000
begin
    insert into Person.Address (AddressLine1, AddressLine2, City, 

        StateProvinceID, PostalCode, ModifiedDate)
    values ('the first address line', 'second add line', 'some city', 1,
        'PC-0000' + cast(@counter as varchar(10)), getdate());
    set @counter = @counter + 1;
end

Executing the loop above takes 3.5 seconds on my laptop.  This is not very long, but it can be better.

If you think about this, what is going on in those 3.5 seconds?  Not only are we inserting 10000 records, every index in that table is getting 10000 updates.  Besides the clustered index there are 3 additional indexes.  In total this makes 40000 changes that are being processed on the table.  And that’s not all, every insert happens inside a transaction, so 10000 transactions.  SQL Server needs to keep track of what has been modified, so writes to the transaction log are going on as well.

Now, how can we avoid this?  The answer lies in the table variable!  Create a table variable that will hold your new records, loop to create the records in the table variable and then, after the loop, insert all records from the table variable into your actual table.

-- declaration of table var 
declare @table table
(
    [AddressLine1] [nvarchar](60) NOT NULL,
    [AddressLine2] [nvarchar](60) NULL,
    [City] [nvarchar](30) NOT NULL,
    [StateProvinceID] [int] NOT NULL,
    [PostalCode] [nvarchar](15) NOT NULL,
    [ModifiedDate] [datetime] NOT NULL
);
declare @counter int;
set @counter = 1; 

while @counter <= 10000
begin
    -- insert in table var instead of actual table  
   insert into @table (AddressLine1, AddressLine2, City, 

        StateProvinceID, PostalCode, ModifiedDate)
    values ('the first address line', 'second add line', 'some city', 1, 

        'PC-0000' + cast(@counter as varchar(10)), getdate());
    set @counter = @counter + 1;
end 

-- insert in actual table 
insert into Person.Address (AddressLine1, AddressLine2, City, 

    StateProvinceID, PostalCode, ModifiedDate)
select * from @table;

This now executes in 453 milliseconds, isn’t that great?  That’s an improvement of about 700%!

A good reason for this is that we now reduced the number of transactions to 1 because the data is being inserted in one go.  And the changes to the indexes are happening all at once, which should be better as well.

In my opinion, in specific situations this can be an interesting concept to optimize your code performance.

See Books Online for more info on the table variable itself: http://msdn.microsoft.com/en-us/library/ms175010.aspx

  • Share/Bookmark

Tags: , , ,

Since SP2 for SQL Server 2005 we have some Standard Reports available when right-clicking either server or database in the Object Explorer in Management Studio.  One of my favorites is the Index Usage Statistics report.

If you have the task to optimize performance of your server or database, Microsoft has provided an interesting additional download called the Performance Dashboard.  When you’ve installed the package, there will be some .RDL files and an interesting .CHM file in our SQL Server folder, for instance C:\Program Files\Microsoft SQL Server\90\Tools\PerformanceDashboard\.

Among this bunch of .RDLs there is one main report called performance_dashboard_main.rdl.  All other reports are used in drillthrough scenarios from the main Performance Dashboard report.  The main report can be opened by right-clicking your server in SSMS, then choose Reports > Custom Reports… and browse to the RDL.

For a more detailed explanation on the installation procedure, have a look at this Database Journal article.  And for an explanation of each report plus some screenshots, part two of that article is certainly useful as well.

Additional info is also available in the PerfDash.chm file located in the same folder as the RDLs.

A bug

Earlier this week I came across a bug in one of the stored procedures that the main Performance Dashboard report is using.  When I tried executing the report I got the following error:

Msg 535, Level 16, State 0, Line 1
Difference of two datetime columns caused overflow at runtime.

This issue manifests itself when there are open connections which have been open for a period longer than approx. 24.85 days.

You can simulate the same error by executing the following select:

select datediff(ms, '2007-12-27 08:49:50.853', GetDate())

The bug is known by Microsoft, see this Microsoft Connect page, but will not be fixed as these reports are not supported.  In the feedback you can get enough info on how to fix the issue.  I also got the above query from there although I added an m to select milliseconds instead of seconds (which would not generate the error). :-)

I’ve solved it by modifying the usp_Main_GetSessionInfo stored procedure like this:

CREATE PROCEDURE MS_PerfDashboard.usp_Main_GetSessionInfoas
BEGIN
select count(*) as num_sessions,
sum(convert(bigint, s.total_elapsed_time)) as total_elapsed_time,
sum(convert(bigint, s.cpu_time)) as cpu_time,
sum(convert(bigint, s.total_elapsed_time))
    - sum(convert(bigint, s.cpu_time)) as wait_time,
/*
 following line gives an error when login_time > 24 days
 see http://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=324906
 the line: sum(convert(bigint, datediff(ms, login_time, getdate())))
    - sum(convert(bigint, s.total_elapsed_time)) as idle_connection_time,
*/
sum(convert(bigint, datediff(s, login_time, getdate()))) * 1000
    - sum(convert(bigint, s.total_elapsed_time)) as idle_connection_time,
case when sum(s.logical_reads) > 0 then (sum(s.logical_reads)
    - isnull(sum(s.reads), 0)) / convert(float, sum(s.logical_reads))
else NULL
end as cache_hit_ratio
from sys.dm_exec_sessions s
where s.is_user_process = 0x1
END

  • Share/Bookmark

Tags: , , ,

© 2008-2010 A Developer's Blog All Rights Reserved