Reasons That a Stored Procedure is Recompiled
SQL Server recompiles stored procedures for a variety of reasons. Last month's article went into detail about the reasons. In short the reasons amount to:
You told it to. There are several ways that you can request a recompile such as using the WITH RECOMPILE clause on the procedure definition.
New distribution statistics are generated or a sufficient number of row modifications occur
One of these SET statements
- SET ANSI_DEFAULTS
- SET ANSI_NULLS
- SET ANSI_PADDING
- SET ANSI_WARNINGS
- SET CONCAT_NULL_YIELDS_NULL
Interleaving SQL DDL and SQL DML operations. For example creation of permanent or temporary tables and the creation of indexes on tables forces a recompile at the next SQL DML statement, such as a SELECT.
The plan is aged out of cache.
Let's take a look at each of these reasons for recompile and suggest what to do about it.
Reason 1. You asked SQL Server to Perform a Recompile
The WITH RECOMPILE clause requests that a stored procedure be recompiled every time 'it is used. The reason that you'd use WITH RECOMPILE is that the best plan for the statements in the stored procedure tends to vary depending on the procedure's parameters and caching is causing SQL Server to sometimes use a suboptimal plan.
The best time to use WITH RECOMPILE is when the cost of the recompile is very small compared to the time it takes to run the stored procedure. For example a complex report on a large amount of data. If that's the case then by all means, keep the WITH RECOMPILE clause. Otherwise, you are better off removing the WITH RECOMPILE option.
If there are specific values of the stored procedure's parameters that you know warrant a recompile, you can always put the WITH RECOMPILE option on the EXEC statement. If you do that, you will want to add an sp_recompile after the procedure runs. For example, let's assume that a stored procedure, usp_MyReport, takes a single parameter, @DepartmentNum and that the procedure is run in this way almost all of the time. Sometimes you want a report for the entire organization, not just a department and usp_MyReport accepts NULL for @DepartmentNum. Now let's say that when @DepartmentNum is supplied, the optimal plan is to use a range scan on the non-clustered index that starts with the DepartmentNum column. However, when a report is requested for the entire organization, the optimal plan is a table scan. Instead of invoking usp_MyReport, replace reference to it with references to usp_MyReportInvoker shown here:
CREATE PROCEDURE usp_MyReportInvoker @DepartmentNum INT AS IF @DepartmentNum IS NOT NULL EXEC usp_MyReport @DepartmentNum ELSE BEGIN EXEC usp_MyReport NULL WITH RECOMPILE EXEC sp_recompile usp_MyReport END
This procedure recompiles usp_MyReport only when you know that changing the plan is going to produce faster results. By doing this, an optimal plan is used every time the report is run. Most of the time, when department number is supplied, SQL Server can reuse the cached plan.
When I was a child, every once in a while as my mother and father would leave the house for an evening out, my mother would turn to my brother, two sisters and me and say, "When I come back, I don't want to find any of you with beans up your nose!" Not that any of use would have thought of putting beans up our nose had she not mentioned it in the first place. It was a reminder not to do things we knew we really shouldn't be doing. In the category of "Don't put beans up your nose," do not use DBCC FREEPROCCACHE, sp_recompile, or WITH RECOMPILE unless you have a real good reason. If you have a good enough reason, you will live with the stored procedure recompiles.
Reason 2: New Distribution Statistics or a Sufficient Number of Row Modifications
SQL Server does recompiles after statistics are generated or after a large enough number of row modifications. It does this because this approach tends to produce better execution plans. It's a good thing. However, there are circumstances where you know that recompiles are not changing the plan because the data is not really changing that much. In these cases you might want to turn off the automatic generation of statistics and update them your self on a schedule that you control.
Exactly when statistics get generated and the instructions to turn automatic generation on or off are well documented in Microsoft Knowledge Base article 195565. I refer you there if you're interested. You can read it at: http://support.microsoft.com/default.aspx?scid=kb;EN-US;195565
Reason 3: SET Statements That Change Session Options
Changing the value of five session options with the SET statement cause a recompile. The options are: ANSI_DEFAULTS, ANSI_WARNINGS, ANSI_PADDING, CONCAT_NULL_YEILDS_NULL.
To illustrate the causes of stored procedure recompilation including SET statements, let's bring back the stored procedure usp_Recompiles_Several_Times from last month's article. It was specifically constructed to cause recompilation at least four times each call. Here's the script to create it:
SET QUOTED_IDENTIFIER ON SET ANSI_NULLS ON GO CREATE PROCEDURE dbo.usp_Recompiles_SeveralTimes AS SET NOCOUNT ON -- Doesn't cause a recompile -- Cause a recompile due to creation of the temporary table CREATE TABLE #Temp1 ([id] int identity (1,1) , [name] varchar(64)) INSERT INTO #Temp1 ([name]) VALUES ('Gregor Samsa') -- Cause a recompile because of a change in a session option SET ANSI_NULLS OFF INSERT INTO #Temp1 ([name]) VALUES ('Padme Amidala') -- Cause a recompile because of a change in a session option SET ANSI_WARNINGS OFF INSERT INTO #Temp1 ([name]) VALUES ('Casper Guttman') -- Cause a recompile because of a new index CREATE CLUSTERED INDEX IDX_Name ON #Temp1 ([name]) SELECT * FROM #Temp1 ORDER BY [name] DROP TABLE #Temp1 -- Doesn't Cause a recompile GO
The best approach to minimize recompiles caused by changing these options is to not change them. Do this by establishing a consistent set of options and always using them. Unfortunately, many code changes may be required by this approach. For example, if you were to always have CONCAT_NULL_YEILDS_NULL set to OFF, you'd have to do additional checking with the ISNULL function or COALESCE on the occasions when you didn't want a NULL result.
If you establish consistent session options at the start of database development you may be able to avoid SET statements. Trying to change the code to use a consistent set of options after it has been written is very difficult. In those cases the best approach would be to minimize the recompiles by grouping SET statements together. That works because the recompile does not happen until the next SQL DML statement. So if our procedure really required that ANSI_NULLS be OFF and ANSI_WARNINGS be OFF it would be best to move them to the start of the procedure so that there would only be one recompile. The first few lines of our sample SP would be:
SET NOCOUNT ON -- Doesn't cause a recompile -- Cause a recompile because of a change in a session option SET ANSI_NULLS OFF SET ANSI_WARNINGS OFF INSERT INTO #Temp1 ([name]) VALUES ('Gregor Samsa')
One recompile is better than two.