Optimize for Ad Hoc Workloads: Shrinking a Bloated Plan Cache

Optimize for ad hoc workloads is a server setting that stores a tiny stub, not a full plan, the first time a query runs. Run the query again and SQL Server keeps the full plan. On a server buried in one-off queries, that saves a lot of plan cache memory.

Small wooden cloakroom tokens sit beside a peg holding one full coat.

What the default does

By default, every ad hoc query gets a full compiled plan in the cache, even if it never runs again. I will build a tiny table, confirm the setting is off, then run two queries. The first runs once and the second runs twice. EXEC sends each string as its own batch, so each query gets its own plan.

DROP TABLE IF EXISTS dbo.AdHocDemo;
CREATE TABLE dbo.AdHocDemo (DemoId int PRIMARY KEY, Note varchar(20) NOT NULL);
INSERT dbo.AdHocDemo VALUES (1, 'one'), (2, 'two'), (3, 'three');
SELECT name, value_in_use FROM sys.configurations WHERE name = 'optimize for ad hoc workloads';
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
GO
EXEC (N'SELECT Note FROM dbo.AdHocDemo WHERE DemoId = 1');
EXEC (N'SELECT Note FROM dbo.AdHocDemo WHERE DemoId = 2');
EXEC (N'SELECT Note FROM dbo.AdHocDemo WHERE DemoId = 2');
SELECT p.cacheobjtype, p.usecounts, p.size_in_bytes, t.text
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
WHERE t.text LIKE 'SELECT Note FROM dbo.AdHocDemo%'
ORDER BY t.text;

Both queries hold a full Compiled Plan of 16,384 bytes on my server, with use counts of 1 and 2. That is fine for a query you run all day. It is a waste for one you will never see again.

Two hundred one-off queries

Now the realistic case. An app pastes literal values into SQL text, so every value looks like a brand new query. This loop sends 200 of them, then totals what landed in the cache.

ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
GO
SET NOCOUNT ON;
CREATE TABLE #Sink (Note varchar(20));
DECLARE @i int = 1, @sql nvarchar(200);
WHILE @i <= 200
BEGIN
    SET @sql = N'SELECT Note FROM dbo.AdHocDemo WHERE DemoId = ' + CAST(@i AS nvarchar(10));
    INSERT #Sink EXEC (@sql);
    SET @i += 1;
END;
DROP TABLE #Sink;
SELECT p.cacheobjtype, COUNT(*) AS Plans, SUM(CAST(p.size_in_bytes AS bigint)) AS TotalBytes
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
WHERE t.text LIKE 'SELECT Note FROM dbo.AdHocDemo WHERE DemoId =%'
GROUP BY p.cacheobjtype;
SET NOCOUNT OFF;

The cache now holds 200 full plans, 3,276,800 bytes, for queries that will never repeat. Sizes vary by server and version, but the shape holds: one full plan per literal.

Check your own server first

Do not flip the switch blind. This query counts plans that ran exactly once, by type, with their size in kilobytes.

SELECT objtype, COUNT(*) AS SingleUsePlans, SUM(CAST(size_in_bytes AS bigint)) / 1024 AS SizeKB
FROM sys.dm_exec_cached_plans
WHERE usecounts = 1 AND cacheobjtype = 'Compiled Plan' AND objtype IN ('Adhoc', 'Prepared')
GROUP BY objtype
ORDER BY objtype;

A big Adhoc count with a big size is the signal. My count includes the 200 plans I just created, so yours should be far smaller on a quiet dev box. If your single-use plans are a small slice of the cache, leave the setting alone. Run it after a day of real traffic, because a freshly cleared cache tells you nothing.

Turn it on and watch the stubs

The demo below changes this server setting and puts it back at the end, so try it on a dev box. It repeats the same experiment with the setting on.

EXEC sys.sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sys.sp_configure 'optimize for ad hoc workloads', 1;
RECONFIGURE;
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
GO
EXEC (N'SELECT Note FROM dbo.AdHocDemo WHERE DemoId = 1');
EXEC (N'SELECT Note FROM dbo.AdHocDemo WHERE DemoId = 2');
EXEC (N'SELECT Note FROM dbo.AdHocDemo WHERE DemoId = 2');
SELECT p.cacheobjtype, p.usecounts, p.size_in_bytes, t.text
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
WHERE t.text LIKE 'SELECT Note FROM dbo.AdHocDemo%'
ORDER BY t.text;
The cached plan stub uses 136 bytes and the compiled plan uses 16384 bytes.
Notice that the first ad hoc query is stored as a stub of only 136 bytes, while the query run a second time is stored as a full 16384-byte plan.

The query that ran once now holds a Compiled Plan Stub of 136 bytes, with a use count of 1. The query that ran twice has a full 16,384 byte plan, but its use count is 1. The second run replaced the stub, so SQL Server compiled that query a second time. After that it is cached like normal.

Now the 200-query loop again.

ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
GO
SET NOCOUNT ON;
CREATE TABLE #Sink (Note varchar(20));
DECLARE @i int = 1, @sql nvarchar(200);
WHILE @i <= 200
BEGIN
    SET @sql = N'SELECT Note FROM dbo.AdHocDemo WHERE DemoId = ' + CAST(@i AS nvarchar(10));
    INSERT #Sink EXEC (@sql);
    SET @i += 1;
END;
DROP TABLE #Sink;
SELECT p.cacheobjtype, COUNT(*) AS Plans, SUM(CAST(p.size_in_bytes AS bigint)) AS TotalBytes
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
WHERE t.text LIKE 'SELECT Note FROM dbo.AdHocDemo WHERE DemoId =%'
GROUP BY p.cacheobjtype;
SET NOCOUNT OFF;

This time the cache holds 200 stubs totaling 27,200 bytes, down from 3,276,800. Same workload, roughly a hundredth of the memory. You still get one cache entry per query, so you save bytes, not entries.

Same 200 one-off queries

What it does not fix

The setting trims memory, not compile work. Every new literal still compiles, and a query that repeats compiles one extra time before its plan sticks. The real cure is parameters. Use parameterized queries or sp_executesql so one plan serves every value. I treat this setting as a safety net, not a repair.

Between runs I cleared the cache for this database only, with ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE. I would never run DBCC FREEPROCCACHE on a production server just to test an idea.

EXEC sys.sp_configure 'optimize for ad hoc workloads', 0;
RECONFIGURE;
DROP TABLE IF EXISTS dbo.AdHocDemo;

Look at your single-use plans first, and change the setting only if the numbers justify it.

The plan cache is not a museum, it is a workbench.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

Ad Hoc Query, Execution Plan, SQL Cache, SQL Server Configuration
Previous Post
SQL SERVER – Practical SQL Server XML: Part One – Query Plan Cache and Cost of Operations in the Cache
Next Post
SQL SERVER – 2008 – Location of Activity Monitor – Where is SQL Serve Activity Monitor Located

Related Posts

20 Comments. Leave new

  • Hi Pinal,

    Is it so that the following code runs only in SQL Server 2008, because when I am running this on SQL Express 2005 it giving error “Msg 15123, Level 16, State 1, Procedure sp_configure, Line 51
    The configuration option ‘optimize for ad hoc workloads’ does not exist, or it may be an advanced option.”

    sp_CONFIGURE ‘optimize for ad hoc workloads’,0
    RECONFIGURE
    GO

    I checked in sys.configurations table under master db and there is no option such as ‘optimize for ad hoc workloads’ available over there.

    Thanks
    Pradeep Nair

    Reply
  • Pradeep,

    It is possible to run in SQL Server 2005 and it works fine. You need to turn on advance option first as I have suggested in the blog post.

    sp_CONFIGURE ’show advanced options’,1
    RECONFIGURE
    GO

    sp_CONFIGURE ‘optimize for ad hoc workloads’,0
    RECONFIGURE
    GO

    Regards,
    Pinal

    Reply
  • Hi Pinal,

    Thanks for the response. I am executing both the command together and separately. But I am still getting the same error. The first command “sp_CONFIGURE ‘show advanced options’, 1” its running successfully and giving a response as “Configuration option ‘show advanced options’ changed from 1 to 1. Run the RECONFIGURE statement to install.”. But the second script is throwing the same error which I mentioned in my previous comment. Thanks.

    Regards
    Pradeep Nair

    Reply
  • Hm.. interesting. May be because it is SQL Server Express. I will have to test with my Express box. I Will do so and will let you know.

    Regards,
    Pinal

    Reply
  • Works in 2008

    I just tested it in SQL 2005 Standard 64 bit SP2

    There is no “optimize for ad hoc workloads” option, if you just run SP_CONFIGURE to display all options

    sp_CONFIGURE ‘show advanced options’,1
    RECONFIGURE
    GO

    sp_CONFIGURE ‘optimize for ad hoc workloads’,1
    RECONFIGURE
    GO

    gives me
    Msg 15123, Level 16, State 1, Procedure sp_configure, Line 51
    The configuration option ‘optimize for ad hoc workloads’ does not exist, or it may be an advanced option.

    Reply
  • Thanks Jerry Hung and Pinal…I thought so, because when I ran a select statement on sys.configurations table under master database, the option was not there.

    And, Pinal I read your latest blog entry which you wrote today(26 Mar 08 – SQL Server – Fix : Error : Msg 15123….). Thanks once again.

    Regards,

    Pradeep Nair

    Reply
  • how does this differ from Plan Caching introduced in SQL Sever 2005?

    Reply
    • Hello Henry,

      In SQL Server 20055 and earlier version the cached plan of ad hoc queries are cleared from memory only on memory pressure. Then SQl engine identify the cached plans that are least useful. This approach affect the performance of running job. In SQL Server 2008, the option “optimize for ad hoc workloads” implements a proactive approach to avoid memory pressure and improve memory utilization.

      Regards,
      Pinal Dave

      Reply
  • Eko Indriyawan
    January 16, 2010 9:04 am

    Work in MS SQL Server Express 2008, but there is no affect, I mean, it still show the ad hoc query in cache.

    Can you explain to me about this?

    Reply
  • Hello Pinal,

    It was interesting your material is really helpful

    but the thing is in here

    how to delete the cached plans used less than 2 times

    i.e., I dont want to delete all cached plans only some of them.

    Reply
  • I ran this on 6 Dec 2010 using SQL Server 2008 Enterprise Edition SP1. With ‘optimize for ad hoc workloads’ = 1, running a query once produces one record when I query sys.dm_exec_cached_plans, but column cacheobjtype shows ‘Compiled Plan Stub’. When I run the same query again, cacheobjtype shows ‘Compiled Plan’.

    This seem to be a change from when you ran your tests.

    Good article. I will use this information to demonstrate to some co-workers.

    Reply
  • I have tried same example and I am not getting expected result.

    my version of Sql server is following.

    Can you please help in this?

    Microsoft SQL Server 2008 (RTM) – 10.0.1600.22 (Intel X86) Jul 9 2008 14:43:34 Copyright (c) 1988-2008 Microsoft Corporation Enterprise Edition on Windows NT 5.1 (Build 2600: Service Pack 3)

    Reply
  • I am getting the following error in SQL Server 2008 R2.

    “pctfreemem is [ 2 ] threshold is 5 [ MEM – pctfreemem ]”

    I have enabled the ‘optimize for ad hoc workloads’,1

    though i am getting the memory issue. Installed RAM is 16GB and SQL Server Max server memory setting is 10240.

    Please guide me to resolve this problem. Server is in production state.

    Reply
  • Lakshmi,

    Its a bit late when I am stumbling on this blog. I hope that issue is resolved by now. Your memory settings look good. HOwever, the error is not very descriptive. Looks like a kernal memory type pressure.Each solution has to evaluated indivifually as one solution does not fit all similiar issues.

    Reply
  • Hi Penal,
    There isn’t any impact of settings as defined the blog.
    It still leaves execution plan in every scenario.

    No record is wrong because because of a very minor mistake
    TEXT LIKE ‘SELECT * FROM HumanResources.Shift%’

    TEXT LIKE ‘%SELECT * FROM HumanResources.Shift%’

    I need to know one thing is it possible to reuse any Execution Plan intentionally for any query again?

    Reply
  • You are my inspiration but in time article you explained wrong point. Its the Compiled Plan and Compiled Plan Stub that matters. So far here what I come to know

    Reply
  • HI Pinal,
    I am always find procedure cache hit ratio in my server below 90%. Even i am having “optimize for ad hoc workloads” is true in my box. Can you please help me what should i do?

    Reply
  • Jitesh KHilosia
    July 21, 2017 11:44 am

    Hi Pinal,
    After we have found many Adhoc quries are getting executed in my system, we have enabled the option ‘Optimize the Adhoc Worklod’. After that we have observed that some of the SQl Server:Memory Manager counters like Lock Blocks, Lock Blocks Allocated indicates very high values which seems unusual. Also same time SQL Cache Memory is getting dropped to 2 MB.

    Can you please explain?

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.