What is the best way to accurately measure the performance (time to complete) of a stored procedure?
I’m about to start an attempt to optimize a monster stored procedure, and in order to correctly determine if my tweaks have any effect, I need something to compare the before and after.
My ideas so far:
Looking a query execution time SQL Management Studio: Not very accurate, but very convenient.
Adding timers in the stored procedure and printing the elapsed time: Adding debug code like that stinks.
Using the SQL Server Profiler, adding filters to target just my stored procedure. This is my best option so far.
Any other options?
There's lots of detailed performance information in the DMV dm_exec_query_stats
DECLARE #procname VARCHAR(255)
SET #procname = 'your proc name'
SELECT * FROM sys.dm_exec_query_stats WHERE st.objectid = OBJECT_ID(#procname)
This will give you cumulative performance data and execution counts per cached statement.
You can use DBCC FREEPROCCACHE to reset the counters (don't run this in a production system, since will purge all the cached query plans).
You can get the query plans for each statement by extending this query:
SELECT SUBSTRING(st.text, (qs.statement_start_offset/2)+1,
((CASE statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) [sub_statement]
,*, CONVERT(XML, tqp.query_plan)
FROM sys.dm_exec_query_stats qs CROSS APPLY
sys.dm_exec_sql_text(sql_handle) st CROSS APPLY
sys.dm_exec_query_plan(plan_handle) qp CROSS APPLY
sys.dm_exec_text_query_plan(plan_handle, statement_start_offset, statement_end_offset ) tqp
WHERE st.objectid = OBJECT_ID(#procname)
ORDER BY statement_start_offset, execution_count
This will give you pointers about which parts of the SP are performing badly, and - if you include the execution plans - why.
Profiler is the most reliable method. You can also use SET STATISTICS IO ON and SET STATISTICS TIME ON but these don't include the full impact of scalar UDFs.
You can also turn on the "include client statistics" option in SSMS to get an overview of the performance of the last 10 runs.
One possible improvement on your timers/debug option is to store the results in a table. In this way you can slice-and-dice the resulting timing data with SQL queries rather than just visually parsing your debug output.
You want to ensure that you are performing fair tests i.e. comparing like with like. Consider running your tests using a cold cache in order to force your stored procedure execution to be served from the IO Subsystem each time you perform your tests.
Take a look at the system stored procedures DBCC FREEPROCCACHE and DBCC FREESYSTEMCACHE
Related
currenty i am working on a report system for our data archive.
the aim is to select data for every 1st of a month, every full hour and so on.
So I have a bunch of parameters to select the data down to a single hour.
To achieve that I used CASE statements to adjust the select like this:
SELECT
MIN(cd.Timestamp) as Mintime,
--Hours
CASE
WHEN
#SelHour IS NOT NULL
THEN
DATEPART(HOUR, cd.Timestamp)
END as Hour,
... -- more CASES up to DATEPART(YEAR, cd.Timestamp)
FROM dbo.CustomerData cd
... -- filter data and other stuff
This statements works good for me so far, but I am a bit worried about the performance of the stored procedure. Because I don't know how the server will behave with this "changing" statement. The result can vary between a 20 row result up to a 250.000 rows and more. Depending on the given parameters. As far as I know the sql server saves the query plan and reuses it for future execution.
When it saves the plan for the 20 row result the performance for the 250.000 result is propably pretty poor.
Now I am wondering whats the better aproach. Using this stored procedure or create the statement inside my c# backend and pass the "adjusted" statement to the sql server?
Thanks and greetings
For 20 rows result set it will work good anywhere. But for returning 250k records to c# code seems change in design for this code since loading 250k records in memory & looping will also consume significant memory and such concurrent requests from different session/user will multiply load exponentially.
Anyway to address problem with SQL Server reusing same query plan, you can recompile query plans selectively or every time. These are options available for Recompile execution plan:
OPTION(RECOMPILE)
SELECT
MIN(cd.Timestamp) as Mintime,
--Hours
CASE
WHEN
#SelHour IS NOT NULL
THEN
DATEPART(HOUR, cd.Timestamp)
END as Hour,
... -- more CASES up to DATEPART(YEAR, cd.Timestamp)
FROM dbo.CustomerData cd
... -- filter data and other stuff
OPTION(RECOMPILE)
WITH RECOMPILE Option this will recompile execution plan every time
CREATE PROCEDURE dbo.uspStoredPrcName
#ParamName varchar(30) = 'abc'
WITH RECOMPILE
AS
...
RECOMPILE Query Hint providing WITH RECOMPILE in execute
NOTE: this will require CREATE PROCEDURE permission in the database and ALTER permission on the schema in which the procedure is being created.
EXECUTE uspStoredPrcName WITH RECOMPILE;
GO
sp_recompile System Stored Procedure
NOTE: Requires ALTER permission on the specified procedure.
EXEC sp_recompile N'dbo.uspStoredPrcName ';
GO
For more details on Recompile refer Microsoft Docs:
https://learn.microsoft.com/en-us/sql/relational-databases/stored-procedures/recompile-a-stored-procedure?view=sql-server-ver15
I've executed a stored procedure with the following T-SQL code:
exec myStoredProcedure
go 10
After the execution of the procedure I reviewed the information in sys.dm_exec_query_stats and observed that for some queries in the stored procedure, the value in Execution Count is different.
Apparently, some execution plans have been ran only a number of times, 7 out of 10, for some queries in the stored procedure.
The data in the above screenshot is being returned with the following query:
select ...
qs.execution_count [Execution Count]
from sys.dm_exec_query_stats as qs
cross apply sys.dm_exec_sql_text (qs.sql_handle) as st
cross apply sys.dm_exec_text_query_plan (qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) as qp
where st.objectid = object_id('myStoredProcedure')
And as you can see, there is no other execution plan stored for this procedure where the execution count is 3, thinking that the Optimizer decided to run a query with another plan.
Truth is some of the queries that have an execution count of 7 are inserts into different temporary tables, with SELECT INTO #temptable, but not all of them.
So, my question is why do some queries have a plan which has been executed a smaller number of times than others and how did those queries execute and with what plan?
I'd like to mention that there is no logic in the stored procedure that would generate different execution flows so that some queries do not get executed. (no IFs)
Also, no statistics update or DML queries have been ran in the meantime so that row-count or index changes have occurred.
Is my query which goes over the DMV not correct and does not pick up these "rogue" plans? Or has the data been cached in memory / tempdb for the temporary tables and read from there on subsequent executions?
Update:
Added a screenshot containing the column with plan_generation_num requested by #MartinBrown
The Execution_Count field is defined as:
"Number of times that the plan has been executed since it was last compiled."
That would suggest that on on the fourth run some of the plans were re-compiled. I would suspect this happened due to the original plans falling out of the cache.
See https://msdn.microsoft.com/en-us/library/ms189741.aspx
Pardon me if this is a duplicate. The closest I could find was Random timeout running a stored proc - drop recreate fixes but I'm not certain the answers there about recompiling the stored procedure apply.
I have an Azure SQL database, latest version, that has a lot of traffic from an Azure web app front end. I have a nightly remote job that runs a batch to rebuild indexes on the Azure SQL database as that seems to help greatly with controlling database size and performance.
Normally, the rebuilding of indexes takes about 20 minutes. Last night it timed out after 2 hours. The error handler in that batch did not log any errors.
Soon after rebuilding indexes was started, one particular stored procedure starting timing out for every client calling it. Other stored procedures using the same tables were not having any issues. When I discovered the problem, I could alleviate all the timeouts and suspended processes by altering the stored procedure to immediately return. When I altered the stored procedure again to behave normally, the issues reappeared immediately. My understanding is that altering the stored procedure forced it to recompile but that didn't fix it.
Ultimately, I completely dropped and recreated the procedure with the original code and the issue was resolved.
This procedure and the schema it uses have been completely stable for many months. The procedure itself is quite simple:
CREATE Procedure [dbo].[uspActivityGet] (#databaseid uniqueidentifier) AS
begin
SET NOCOUNT ON;
--There may be writing activities to the table asynchronously, do not use nolock on tblActivity - the ActivityBlob might be null in a dirty read.
select top 100 a.Id, h.HandsetNumber, a.ActivityBlob, a.ActivityReceived
from dbo.tblDatabases d with(nolock) join dbo.tblHandsets h with(nolock) on d.DatabaseId = h.DatabaseId join dbo.tblActivity a on h.Id = a.HandsetId
where d.DatabaseId = #databaseid and a.ActivitySent is null
order by a.ActivityReceived
end
While the procedure would hang and time out with something like this:
exec dbo.uspActivityGet 'AF3EA01B-DB22-4A39-9E1C-D096D2DF1215'
Running the identical select in a query window would return promptly and successfully:
declare #databaseid uniqueidentifier; set #databaseid = 'AF3EA01B-DB22-4A39-9E1C-D096D2DF1215'
select top 100 a.Id, h.HandsetNumber, a.ActivityBlob, a.ActivityReceived
from dbo.tblDatabases d with(nolock) join dbo.tblHandsets h with(nolock) on d.DatabaseId = h.DatabaseId join dbo.tblActivity a on h.Id = a.HandsetId
where d.DatabaseId = #databaseid and a.ActivitySent is null
order by a.ActivityReceived
Any ideas how I can prevent this from happening in the future? Thank you.
Edit - Adding execution plan screenshot
Edit - Adding query used to view running processes. There were many, guessing aproximately 150, in the suspended state and they were all for the same stored procedure - uspActivityGet. Also, Data IO Percentage was maxed out the whole time when it normally runs 20 - 40% in peak demand times. I don't recall what the wait type was. Here is the query used to view that.
select * from sys.dm_Exec_requests r with(nolock) CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) order by r.total_elapsed_time desc
Edit - It happened again tonight. Here is the execution plan of the same procedure during the issue. After dropping and creating the procedure again, the execution plan returned to normal and the issue was resolved.
During the issue, sp_executesql with the identical query took about 5 minutes to execute and I believe that is representative of what was happening. There were about 50 instances of uspActivityGet suspended with wait type SLEEP_TASK or IO_QUEUE_LIMIT.
Perhaps the next question is why is index rebuilding or other nightly maintenance doing this to the execution plan?
The clues are in the query and the troublesome execution plan. See Poor Performance with Parallelism and Top
The normal execution plan seems quite efficient and shouldn't need recompiled as long as the relevant schema doesn't change. I also want to avoid parallelism in this query. I added the following two options to the query for assurance on both points and all is happy again.
OPTION (KEEPFIXED PLAN, MAXDOP 1)
We've a stored procedure that happens to build up some dynamic SQL and execute via a parametrised call to sp_executesql.
Under normal conditions, this works wonderfully, and has made a large benefit in execution times for the procedure (~8 seconds to ~1 second), however, under some unknown conditions, something strange happens, and performance goes completely the other way (~31 seconds), but only when executed via RPC (i.e. a call from a .Net app with the SqlCommand.CommandType of CommandType.StoredProcedure; or as a remote query from a linked server) - if executed as a SQL Batch using SQL Server Management Studio, we do not see the degradation in performance.
Altering the white-space in the generated SQL and recompiling the stored procedure, seems to resolve the issue at least in the short term, but we'd like to understand the cause, or ways to force the execution plans to be rebuilt for the generated SQL; but at the moment, I'm not sure how to proceed with either?
To illustrate, the Stored Procedure, looks a little like:
CREATE PROCEDURE [dbo].[usp_MyObject_Search]
#IsActive AS BIT = NULL,
#IsTemplate AS BIT = NULL
AS
DECLARE #WhereClause NVARCHAR(MAX) = ''
IF #IsActive IS NOT NULL
BEGIN
SET #WhereClause += ' AND (svc.IsActive = #xIsActive) '
END
IF #IsTemplate IS NOT NULL
BEGIN
SET #WhereClause += ' AND (svc.IsTemplate = #xIsTemplate) '
END
DECLARE #Sql NVARCHAR(MAX) = '
SELECT svc.[MyObjectId],
svc.[Name],
svc.[IsActive],
svc.[IsTemplate]
FROM dbo.MyObject svc WITH (NOLOCK)
WHERE 1=1 ' + #WhereClause + '
ORDER BY svc.[Name] Asc'
EXEC sp_executesql #Sql, N'#xIsActive BIT, #xIsTemplate BIT',
#xIsActive = #IsActive, #xIsTemplate = #IsTemplate
With this approach, the query plan will be cached for the permutations of NULL/not-NULL, and we're getting the benefit of cached query plans. What I don't understand is why it would use a different query plan when executed remotely vs. locally after "something happens"; I also don't understand what the "something" might be?
I realise I could move away from parametrisation, but then we'd lose the benefit of caching what are normally good execution plans.
I would suspect parameter sniffing. If you are on SQL Server 2008 you could try including OPTIMIZE FOR UNKNOWN to minimise the chance that when it generates a plan it does so for atypical parameter values.
RE: What I don't understand is why it would use a different query plan when executed remotely vs. locally after "something happens"
When you execute in SSMS it won't use the same bad plan because of different SET options (e.g. SET ARITHABORT ON) so it will compile a new plan that works well for the parameter values you are currently testing.
You can see these plans with
SELECT usecounts, cacheobjtype, objtype, text, query_plan, value as set_options
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
cross APPLY sys.dm_exec_plan_attributes(plan_handle) AS epa
where text like '%FROM dbo.MyObject svc WITH (NOLOCK)%'
and attribute='set_options'
Edit
The following bit is just in response to badbod99's answer
create proc #foo #mode bit, #date datetime
as
declare #Sql nvarchar(max)
if(#mode=1)
set #Sql = 'select top 0 * from sys.objects where create_date < #date /*44FC79BD-2AF5-4774-9674-04D6C3D4B228*/'
else
set #Sql = 'select top 0 * from sys.objects where modify_date < #date /*44FC79BD-2AF5-4774-9674-04D6C3D4B228*/'
EXEC sp_executesql #Sql, N'#date datetime',
#date = #date
go
declare #d datetime
set #d = getdate()
exec #foo 0,#d
exec #foo 1, #d
SELECT usecounts, cacheobjtype, objtype, text, query_plan, value as set_options
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
cross APPLY sys.dm_exec_plan_attributes(plan_handle) AS epa
where text like '%44FC79BD-2AF5-4774-9674-04D6C3D4B228%'
and attribute='set_options'
Returns
Recompilation
Any time the execution of the SP would be significantly different due to conditional statements the execution plan which was cached from the last request may not be optimal for this one.
It's all about when SQL compiles the execution plan for the SP. They key section regarding sp compilation on Microsoft docs is this:
... this optimization occurs automatically the first time a stored procedure is run after SQL Server is restarted. It also occurs if an underlying table that is used by the stored procedure changes. But if a new index is added from which the stored procedure might benefit, optimization does not occur until the next time that the stored procedure is run after SQL Server is restarted. In this situation, it can be useful to force the stored procedure to recompile the next time that it executes
SQL does recompile execution plans at times, from Microsoft docs
SQL Server automatically recompiles stored procedures and triggers when it is advantageous to do this.
... but it will not do this with each call (unless using WITH RECOMPILE), so if each execution could be resulting in different SQL, you may be stuck with the same old plan for at least one call.
RECOMPILE query hint
The RECOMPILE query hint, takes into account your parameter values when checking what needs to be recompiled at the statement level.
WITH RECOMPILE option
WITH RECOMPILE (see section F) will cause the execution plan to be compiled with each call, so you will never have a sub-optimal plan, but you will have the compilation overhead.
Restructure into multiple SPs
Looking at your specific case, the execution plan for the proc never changes and the 2 sql statements should have prepared execution plans.
I would suggest that restructuring the code to split the SPs rather than have this conditional SQL generation would simplify things and ensure you always have the optimal execution plan without any SQL magic sauce.
I am able to view the Estimated Execution Plan (Management Studio 9.0) for a query without a problem but when it comes to stored procedures I do not see an easy way to do this without copying the code from the ALTER screen and pasting it into a query window, otherwise it will show the plan for the ALTER and not the procedure. Even after doing this, any inputs are missing and I would need to DECLARE them as such.
Is there an easier way to do this on stored procedures?
Edit:
I just thought of something that might work but I am not sure.
Could I do the estimated execution plan on
exec myStoredProc 234
SET SHOWPLAN_ALL ON
GO
-- FMTONLY will not exec stored proc
SET FMTONLY ON
GO
exec yourproc
GO
SET FMTONLY OFF
GO
SET SHOWPLAN_ALL OFF
GO
Select the storedprocedure name (just type it in a query window), right click, and choose the 'Display Estimated Execution Plan' button in the toolbar of SQl Server Mgmt Studio.
Note that you don't have to have the stored procedure code open. Just the procedure name has to be selected.
The plan for the stored procedure from with in the called procedures will also be displayed in graphical form.
When executing a stored procedure in SQL Management Studio 2008 you can click Query -> Include Actual Execution Plan from the menu...its also on the tool bar
After reading through the comments executing seems to be an issue and to solve this issue i would recommend wrapping the execution of the stored procedure in a transaction rolling it back at the end
Use
SET SHOWPLAN_ALL ON
Go
exec myStoredProc 234
GO
SET SHOWPLAN_ALL OFF
GO
See http://msdn.microsoft.com/en-us/library/aa259203.aspx
As long as you aren't using tmp tables i think this will work
I know answer was submitted a while ago but I find query below useful
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT [ProcedureName] = OBJECT_NAME([ps].[object_id], [ps].[database_id])
,[ProcedureExecutes] = [ps].[execution_count]
,[VersionOfPlan] = [qs].[plan_generation_num]
,[ExecutionsOfCurrentPlan] = [qs].[execution_count]
,[Query Plan XML] = [qp].[query_plan]
FROM [sys].[dm_exec_procedure_stats] AS [ps]
JOIN [sys].[dm_exec_query_stats] AS [qs] ON [ps].[plan_handle] = [qs].[plan_handle]
CROSS APPLY [sys].[dm_exec_query_plan]([qs].[plan_handle]) AS [qp]
WHERE [ps].[database_id] = DB_ID()
AND OBJECT_NAME([ps].[object_id], [ps].[database_id]) = 'TEST'
There are quite a few ways to get the actual execution plan of a stored procedure.
SELECT
qp.query_plan,
SQLText.text
FROM sys.dm_exec_cached_plans AS CP
CROSS APPLY sys.dm_exec_sql_text( plan_handle)AS SQLText
CROSS APPLY sys.dm_exec_query_plan( plan_handle)AS QP
WHERE objtype = 'Proc' and cp.cacheobjtype = 'Compiled Plan'
looking at the plans on a production server with the statistics of the data in the production server may show a different plan then of a dev box with a smaller dataset.
There is a lot more data to look at, like how often is a procedure executed according to query cache
SELECT
qp.query_plan,
CP.usecounts as [Executed],
DB_name(QP.dbid) as [Database],
OBJECT_NAME(QP.objectid) as [Procedure],
SQLText.text as [TSQL],
so.create_date as [Procedure Created],
so.modify_date as [Procedure Modified]
FROM sys.dm_exec_cached_plans AS CP
CROSS APPLY sys.dm_exec_sql_text( plan_handle)AS SQLText
CROSS APPLY sys.dm_exec_query_plan( plan_handle)AS QP
join sys.objects as so on so.[object_id]=QP.objectid
WHERE objtype = 'Proc' and cp.cacheobjtype = 'Compiled Plan'
The XML query plan (the first column in both queries), contains the XML of the execution plan allowing you, in SSMS to click on it and view the actual plans but also allows you to scan for things you do not like to have like index scan or "god forbid" table scans.
SELECT
qp.query_plan,
CP.usecounts as [Executed],
DB_name(QP.dbid) as [Database],
OBJECT_NAME(QP.objectid) as [Procedure],
SQLText.text as [TSQL],
so.create_date as [Procedure Created],
so.modify_date as [Procedure Modified]
FROM sys.dm_exec_cached_plans AS CP
CROSS APPLY sys.dm_exec_sql_text( plan_handle)AS SQLText
CROSS APPLY sys.dm_exec_query_plan( plan_handle)AS QP
join sys.objects as so on so.[object_id]=QP.objectid
WHERE objtype = 'Proc' and cp.cacheobjtype = 'Compiled Plan'
and cast(qp.query_plan as nvarchar(max)) like '%loop%'
I sample this using a really bad way by casting the XML to string and then doing a wildcard search however XML queries are not things most do every day and string wildcards are easy for everyone.
Running the stored procedure in management studio (or query analyser) with show actual execution plan (from the query menu) enabled will show you the plan for the stored procedure after you have run it. If you cant run it there is show estimated execution plan (though in my experience that is often less accurate.)
You can also use Profiler to see the execution plan. You'll want to include the Performance : Show Plan Statistics Profile option and be sure to inlcude Binary Data in your columns.
You can then run any query or procedure and see the execution plan.
Edit
If you can't use profiler, and you don't want to open another window I suggest that you include a comment block at the begining of your stored procs. For example imagine the following:
/*
Description: This procedure does XYZ etc...
DevelopedBy: Josh
Created On: 4/27/09
Execution: exec my_procName N'sampleparam', N'sampleparam'
*/
ALTER PROCEDURE my_procName
#p1 nvarchar(20),
#p2 nvarchar(20)
AS
What this allows is that you can highlight just the execution purpose and turn on show execution plan. And run it.
Here's a screenshot.Took me a while to figure out where to look for.