Fixed query plan for a stored procedure
-
30-09-2020 - |
Question
I am exploring the plan guides topic. Sounds easy, as far as we go through the examples in documentation.
The problem is that I cannot find any working example (nor did I manage to figure it out by myself) for setting a fixed plan for a stored procedure, or a statement inside the procedure.
I've read lots of blog posts, but all of them just copy/paste examples from microsoft docs.
Anyone more creative/expirienced?
Solution
Here is an example of forcing a plan for a statement inside a procedure using a plan guide:
USE tempdb;
GO
CREATE PROCEDURE dbo.test
AS
BEGIN
SET NOCOUNT ON;
SELECT svp.name AS login_name, dbp.name AS user_name
FROM sys.database_principals AS dbp
INNER JOIN sys.server_principals AS svp
ON dbp.sid = svp.sid;
END
GO
-- Use Ctrl+M to capture the execution plan
-- You will see loop joins used
EXEC dbo.test;
Now generate the execution plan for this statement with a forced join strategy:
SELECT svp.name AS login_name, dbp.name AS user_name
FROM sys.database_principals AS dbp
INNER JOIN sys.server_principals AS svp
ON dbp.sid = svp.sid
OPTION (HASH JOIN);
Copy the execution plan XML and paste it to the query window. Replace any occurence of ' (single quote) with '''' (four single quotes).
Your final parameter for the sp_create_plan_guide procedure will be in the form:
@xmlPlan = 'OPTION (USE PLAN N''your plan goes here'')'
Now you can see why four quotes: escape once to put the XML inside a string literal, escape twice to put the XML inside a string literal inside another string literal.
Make sure that the XML plan is pasted right after OPTION (USE PLAN N''
: any whitespace or newline will make it invalid.
DECLARE @planXML nvarchar(max) = 'OPTION (USE PLAN N''your modified XML plan here'')';
EXEC sp_create_plan_guide
@name = 'UseHash',
@stmt = 'SELECT svp.name AS login_name, dbp.name AS user_name
FROM sys.database_principals AS dbp
INNER JOIN sys.server_principals AS svp
ON dbp.sid = svp.sid;',
@type = 'OBJECT',
@module_or_batch = 'dbo.test',
@hints = @planXML
GO
-- Use Ctrl+M to capture the execution plan
-- You will see hash joins used
EXEC dbo.test;
OTHER TIPS
Having digged a little more I have found alternative solution in Itzik Ben-Gans et.al. T-SQL Programming book. His solution uses sp_create_plan_guide_from_handle.
IF OBJECT_ID('dbo.GetOrders', 'P') IS NOT NULL DROP PROC dbo.GetOrders;
GO
CREATE PROC dbo.GetOrders @odate AS DATETIME
AS
SELECT SalesOrderID, CustomerID, OrderDate /* 33145f87-1109-4959-91d6 */
FROM Sales.SalesOrderHeader
WHERE OrderDate >= @odate;
GO
EXEC dbo.GetOrders '99991231';
GO
DECLARE @plan_handle VARBINARY(64), @offset INT, @rc INT;
SELECT @plan_handle = plan_handle, @offset = statement_start_offset
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE
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 )
LIKE N'%SELECT SalesOrderID, CustomerID, OrderDate /* 33145f87-1109-4959-91d6 */%' AND st.text NOT LIKE '%sys%';
SET @rc = @@ROWCOUNT;
IF @rc = 1
EXEC sp_create_plan_guide_from_handle
@name = N'MySecondPlanFromProc',
@plan_handle = @plan_handle,
@statement_start_offset = @offset;
ELSE
RAISERROR ('Number of matching plans should be 1 but is %d. Plan guide not created.', 16, 1, @rc);
GO
SET SHOWPLAN_XML ON;
GO
EXEC dbo.GetOrders '99991231';
GO
SET SHOWPLAN_XML OFF;
GO