views:

277

answers:

4

In responses to this question, KM said

if you are on or above SQL Server 2005, you can use IFs to have multiple queries in the same procedure and each will have an query plan saved for it (equiv to a procedure for each on older versions), see the article in my answer or this link to proper section: sommarskog.se/dyn-search-2005.html#IF

HLGEM added

You can do that in earlier versions of SQL Server as well.

I read that section of Sommarskog's excellent article but did not see anything about multiple plans.

In later research, I read a quote here from Gert Drapers:

Because SQL Server allows only one execution plan per stored procedure ...

I don't know the date of that original article or the version of SQL Server he is referring to.

Does anyone have a reliable reference that discusses this or, better yet, a test that proves that this is true?

A: 

From my knowledge I would say that each stored procedure or overall query generates a single plan, cached based on hash of the SQL and retrieved from the cache on that basis.

What I believe was referred to was when you write a proc that contains calls using sp_executesql or exec, since that would then be an additional query submitted which generated it's own plan.

On that basis you get multiple plans. The text in the article was as follows:

Each subprocedure has its own plan in the cache, and for search_orders_4a_sub1 and sub2 that is a plan that is based on good input values from the first call. The catch-all search_orders_4a_sub3, still has a WITH RECOMPILE at it serves a mix of conditions.
Andrew
+2  A: 

I believe that whats being referred to is that the Stored Procedure has an overall query plan that is made up of the query plans for the individual statements. In other words, there is a plan associated with each statement. (And AFAIK, you do not need to use either EXEC(..) or sp_ExecuteSQL() to get this).

So if you are using IF's to branch to different query statements, then yes, you get to exploit having different plans. However, if you just use your IF's to set different variable values that then all end up being executed by the same SQL statement, using those variables, then NO you effectively only have one query plan.

RBarryYoung
+6  A: 

Update #2:

I have one more step to add that ought to make everything clearer. After generating plan information, run the following statement (with the correct plan handle) to look at the ShowPlan XML.

DECLARE @val as VARBINARY(64)
-- NOTE: Replace the Hex string with the current plan_handle !
SET @val = CONVERT(VARBINARY(64), 0x05001300045A3D02B801BE11000000000000000000000000)
SELECT * FROM sys.dm_exec_query_plan(@val)

Looking at the generated XML reveals that there are 2 QueryPlan elements, 2 or more StmtSimple/StmtCond elements, and only 1 Batch overall. As gbn mentions- there is a difference between "Execution Plans" and "Query Plans". This seems to make it crystal clear what parts we are actually viewing in all the sys.dm_ queries.

sys.dm_exec_query_stats at MSDN, SQL 2008

sys.dm_exec_query_plan at MSDN, SQL 2008

So with all this information, the plan_handle returned is the Execution Plan; and the parts are Query Plan items.

--

Update:

After Andrew's comment, I retested and indeed the query plan handles are the same. And, just to be sure- I created a duplicate of the stored procedure, changing only the name, and reran my test against that stored procedure. This also caused a new set of query parts to be generated, that shared the same plan handle.

So, gbn's answer appears to be correct, at least with what I've tested here. Interesting stuff.

--

The latter quote from Gert Drapers appears to be false- here is my test. I'm using SQL 2005 here. In my test, I see 2 query plans generated for different parts of the same stored procedure.

First, I created two tables, tblTag1 and tblTagWithGUID. I made them somewhat similar so that my stored procedure can alternate between either table and return results with an identical result table layout.

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
-- Table #1, tblTag1
CREATE TABLE [dbo].[tblTag1](
  [id] [int] IDENTITY(1,1) NOT NULL,
  [createDate] [datetime] NOT NULL CONSTRAINT [DF_tblTag1_createDate]  DEFAULT (getdate()),
  [someTag] [varchar](100) NOT NULL,
 CONSTRAINT [PK_tblTag1] PRIMARY KEY CLUSTERED 
(
  [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
-- Table #2, tblTagWithGUID
CREATE TABLE [dbo].[tblTagWithGUID](
  [id] [int] IDENTITY(1,1) NOT NULL,
  [createDate] [datetime] NOT NULL CONSTRAINT [DF_tblTagWithGUID_createDate]  DEFAULT (getdate()),
  [someTag] [varchar](100) NOT NULL,
  [someGUID] [uniqueidentifier] NOT NULL CONSTRAINT [DF_tblTagWithGUID_someGUID]  DEFAULT (newid()),
 CONSTRAINT [PK_tblTagWithGUID] PRIMARY KEY CLUSTERED 
(
  [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

Second, the stored procedure. In the stored procedure I select from one or the other table depending on the argument.

CREATE PROCEDURE spLoadTags
@Pick AS BIT = NULL
AS
BEGIN

IF @Pick = 0
SELECT id, createDate, someTag FROM tblTag1

IF @Pick = 1
SELECT id, createDate, someTag FROM tblTagWithGUID

END

I added some data to each table- then ran the stored procedure a few dozen times with 0 or 1 as the argument.

Next, I ran this query to check up on the generated query plans. I'm sorry if anyone is offended with my sloppiness here- this isn't production code, of course.

WITH PlanData AS
(
SELECT
(SELECT SUBSTRING(text, statement_start_offset/2 + 1,
(CASE WHEN statement_end_offset = -1 
THEN LEN(CONVERT(nvarchar(MAX),text)) * 2 
ELSE statement_end_offset 
END - statement_start_offset)/2)
FROM sys.dm_exec_sql_text(sql_handle) WHERE [text] like '%SELECT id, createDate, someTag FROM tblTag%') AS query_text,
plan_handle
FROM sys.dm_exec_query_stats  
)
SELECT 
DISTINCT
execution_count, 
PlanData.query_text, 
sys.dm_exec_query_stats.plan_handle
FROM sys.dm_exec_query_stats, PlanData 
WHERE 
sys.dm_exec_query_stats.plan_handle = PlanData.plan_handle and 
PlanData.query_text IS NOT NULL
ORDER BY execution_count DESC

When I run this query, I see a bunch of plans, but because I've run the stored procedure a few dozen times, the distinct parts end up at the top.

execution_count query_text plan_handle
96  SELECT id, createDate, someTag FROM tblTag1 0x05001200045A3D02B8613E13000000000000000000000000
96  SELECT id, createDate, someTag FROM tblTagWithGUID 0x05001200045A3D02B8613E13000000000000000000000000

I've only included those two rows, but hopefully this is straightforward enough where someone else can verify my results. You may see other rows if you are using the SQL management tool like I am; presumably caused by browsing tables or other activity.

meklarian
I'm not sure Gert is wrong there, the plan handle is the same on both plans, if they were truely seperate plans, the handles should be different surely?
Andrew
Wow. Can't believe I missed that- I swear they were different during my test. I'll check again.
meklarian
meklarian, thanks for the detailed answer. I used that and made a comment below on GBN's answer related to your answer.
Rob Garrison
My results show the same plan_handle too (on 2005 and 2008) but different query_text.
Rob Garrison
Checked again and Andrew is right. Updated my answer.
meklarian
+1 Nice investigation
Andrew
+1 Agree. Thorough work.
gbn
Thanks guys. :)
meklarian
+4  A: 

We're all correct :-)

  • A "query plan" has at most 2 entries in cache: one serial and one parallel

  • Each user has their own "Execution context" that runs the plan

  • Plans differ if objects are not qualified

So, what you may think is a plan is not because tables are not qualified with schema (which is the same in SQL Server 2000 , 2005 and 2008)

From MSDN/BOL "Execution Plan Caching and Reuse"

Edit:

"Obtaining Statement-Level Query Plans" from MS blog

gbn
I made some slight modifications to meklarian's code and tested it on both 2005 and 2008. One of the things I changed was that each object is schema-qualified. The interesting thing is that the result shows two rows with the same plan_handle but different query_text values.Can you comment on that? (I posted the full code here for reference: http://robsonlinereference.blogspot.com/2009/10/stackoverflow-code-for-question-tf.html)
Rob Garrison
Another interesting bit is that I updated the code so that it SELECTs from each table once as schema-qualified and once without. The plan query returns four records, each with the same plan_handle and different query_text. This is not surprising considering that the text is different. The updated code is here: http://robsonlinereference.blogspot.com/2009/10/stackoverflow-code-for-question-tf_02.html
Rob Garrison