Happy Friday! We made it. Here’s something I came across while testing optimize for ad hoc for this week’s blog posts.
First, the (better) way to use sp_executesql
The rest of this post will be using ad hoc queries with optimize for ad hoc ON at the several level. But first, take a look at using a parameter inside sp_executesql and what it shows inside the plan cache.
I want to say that this is not the best way to use sp_executesql. Here’s my query and how I could properly wrap it in sp_executesql.
--Here's the ad hoc version
SELECT Score FROM Posts AS p
JOIN Users as u ON u.Id = p.OwnerUserId
WHERE p.Id = 13
GO
--And here's it wrapped it in sp_executesql
--with a variable, @PostId instead of the literal, 13
EXEC sp_executesql N'SELECT Score FROM Posts AS p
JOIN Users as u ON u.Id = p.OwnerUserId
WHERE p.Id = @PostId', N'@PostId int', '13'
GO
And here’s the plan cache after running those two queries, with optimize for ad hoc on.
SELECT TOP 10
cache.size_in_bytes,
cache.objtype,
stat.execution_count,
stext.text,
splan.query_plan
FROM sys.dm_exec_query_stats as stat
CROSS APPLY sys.dm_exec_sql_text(stat.sql_handle) as stext
CROSS APPLY sys.dm_exec_query_plan(stat.plan_handle) as splan
JOIN sys.dm_exec_cached_plans as cache on cache.plan_handle = stat.plan_handle
Okay, so this is as expected. The Adhoc query didn’t save the execution plan into cache, and the size is a lot smaller. But what if we didn’t replace 13 with the variable @PostId?
Leaving things as they are
Sometimes, we don’t have time to change the code. Sometimes the code is still using literals inside sp_executesql, essentially running ad hoc statements. Let’s take a look at that scenario.
--ad hoc version
SELECT Score FROM Posts AS p
JOIN Users as u ON u.Id = p.OwnerUserId
WHERE p.Id = 13
GO
EXEC sp_executesql N'SELECT Score FROM Posts AS p
JOIN Users as u ON u.Id = p.OwnerUserId
WHERE p.Id = 13'
GO
Here’s the results of our plan cache query.
Changing the order of statement – sp_executesql first
Now, you might wonder what happens if we change the order of the statements. Since I ran the sp_executesql as the second statement, maybe it cached that plan. Let’s run the sp_executefirst.
EXEC sp_executesql N'SELECT Score FROM Posts AS p
JOIN Users as u ON u.Id = p.OwnerUserId
WHERE p.Id = 13'
GO
--ad hoc version
SELECT Score FROM Posts AS p
JOIN Users as u ON u.Id = p.OwnerUserId
WHERE p.Id = 13
GO
So, why does sp_executesql cause ad hoc plans to be stored as full plans in cache?
I don’t know. I came across this while blogging and thought it was worth its own post. If you know why or know more, feel free to send me a message or leave a comment!
Stay tuned!
Hi Arthur,
nice posts about the plan caches.
From Microsoft Docs page of sp_executesql we can read that:
“sp_executesql can be used instead of stored procedures to execute a Transact-SQL statement many times when the change in parameter values to the statement is the only variation. Because the Transact-SQL statement itself remains constant and only the parameter values change, the SQL Server query optimizer is likely to reuse the execution plan it generates for the first execution.”
So I believe SQL Server treats sp_executesql the same way of a Stored Procedure, caching the plan. It’s misleading that in the sys.dm_exec_cached_plans table the objtype is Adhoc…
https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-executesql-transact-sql?view=sql-server-2017