When running a query including the Actual Execution Plan, the root operator (SELECT) tells me that the Cached Plan Size is 32KB.

A query that joins sys.dm_exec_cached_plans and sys.dm_os_memory_objects, looking at the plan in question, says that values for pages_in_bytes and max_pages_in_bytes are 32768 (32KB), which matches the cached plan size.

What I don't understand is what the value in sys.dm_exec_cached_plans.size_in_bytes, which is 49152 (48KB) stands for. I've read BOL on all these columns, and especially size_in_bytes which says:

"Number of bytes consumed by the cache object."

I can't get that last bit of the puzzle in place, to understand what it really means.

I know that all operators (not talking about the additional memory grant used for sorts and hashes) requires some amount of fixed memory, to store state, make calculations etc., which is stored with the optimized plan in the cache, but where?

So, my questions are:

  • What does size_in_bytes really mean
  • Why is it a higher value than "Cached plan size"?
  • Where is the fixed amount of memory for all operators/iterators reserved, is it with the "Cached plan size" (32Kb in my example), or anywhere else?

I know they're different DMVs with different functions, but they are related. The compiled (cached) plans in sys.dm_exec_cached_plans joins sys.dm_os_memory_objects on memory_object_address column. The reason I post the questions here, is that I'm asking for help on this, understanding how to interpret the DMVs and their columns.

If size_in_bytes is the cached plan size, why does SQL Server say another value in actual execution plan?

New query, new numbers:

  • Actual plan
    • Cached Plan Size 16KB
    • CompileMemory 96KB
  • DMVs:
    • sys.dm_exec_cached_plans.size_in_bytes 24KB
    • sys.dm_os_memory_objects.pages_in_bytes, .max_pages_in_bytes 16KB.

Also, note that this query doesn't require any additional memory grants for sorts and hash operations.

Microsoft SQL Server 2012 - 11.0.5343.0 (X64)

2 Answers 2


The reason that the size_in_bytes field of the sys.dm_exec_cached_plans DMV, at least in terms of "Compiled Plans", is larger than the CachedPlanSize attribute of the QueryPlan node in the XML plan is because a Compiled Plan is not the same thing as a Query Plan. A Compiled Plan is comprised of multiple Memory Objects, the combined size of which equates to the size_in_bytes field. So, the description of "Number of bytes consumed by the cache object" that you found in the documentation is accurate; it's just that it is easy to misinterpret what is meant by "cache object" given the name of the DMV and that the term "plan" has multiple meanings.

A Compiled Plan is a container that holds various pieces of information related to the query batch (i.e. not just a single statement), one (or more) of those pieces being the query plan(s). Compiled Plans have a top-level Memory Object of MEMOBJ_COMPILE_ADHOC which is the row in sys.dm_os_memory_objects that is linked via the memory_object_address field in both DMVs. This Memory Object contains the symbol table, parameter collection, links to related objects, accessor cache, TDS metadata cache, and possibly some other items. Compiled Plans are shared amongst Sessions/Users that are executing the same batch with the same Session settings. However, some related objects are not shared between Sessions/Users.

Compiled Plans also have one or more dependent objects that can be found by passing the plan_handle (in sys.dm_exec_cached_plans) into the sys.dm_exec_cached_plan_dependent_objects DMF. There are two types of dependent objects: Executable Plan (Memory Object = MEMOBJ_EXECUTE) and Cursor (Memory Object = MEMOBJ_CURSOREXEC). There will be 0 or more Cursor objects, one per each cursor. There will also be one or more Executable Plan objects, one per each User executing that same batch, hence Executable Plans are not shared between Users. Executable Plans contain run-time parameter and local variable info, run-time state such as the currently executing statement, object ids for objects created at run-time (I assume this refers to Table Variables, Temporary Tables, Temporary Stored Procedures, etc), and possibly other items.

Each statement within a multi-statement batch is contained within a Compiled Statement (Memory Object = MEMOBJ_STATEMENT). The size of each Compiled Statement (i.e. pages_in_bytes) divided by 1024 should match the CachedPlanSize="xx" values of the <QueryPlan> nodes in the XML plan. Compiled Statements will often have one (possibly more?) associated runtime Query Plans (Memory Object = MEMOBJ_XSTMT). Finally, for each runtime Query Plan that is a query, there should be an associated Query Execution Context (Memory Object = MEMOBJ_QUERYEXECCNTXTFORSE).

With respect to Compiled Statements, single-statement batches do not have separate Compiled Statement (i.e. MEMOBJ_STATEMENT) or separate runtime Query Plan (i.e. MEMOBJ_XSTMT) objects. The value for each of those objects will be stored in the main Compiled Plan object (i.e. MEMOBJ_COMPILE_ADHOC), and in that case, the pages_in_bytes value for that main object divided by 1024 should match the CachedPlanSize size in the <QueryPlan> node of the XML plan. Those values will not equate, however, in multi-statement batches.

The size_in_bytes value can be derived by summing the entries in the sys.dm_os_memory_objects DMV (the items noted above in bold), all related by dm_os_memory_objects.page_allocator_address for that Compiled Plan. The trick to getting the correct value is to first get the memory_object_address from sys.dm_exec_cached_plans for a particular Compiled Plan, then use that to get the corresponding MEMOBJ_COMPILE_ADHOC row from sys.dm_os_memory_objects based on its memory_object_address field. Then, grab the page_allocator_address value from sys.dm_os_memory_objects for that row, and use it to grab all rows from sys.dm_os_memory_objects that have the same page_allocator_address value. (Please note that this technique does not work for the other Cached Object types: Parse Tree, Extended Proc, CLR Compiled Proc, and CLR Compiled Func.)

Using the memory_object_address value obtained from sys.dm_exec_cached_plans, you can see all of the components of the Compiled Plan via the following query:

DECLARE @CompiledPlanAddress VARBINARY(8) = 0x00000001DC4A4060;

SELECT obj.memory_object_address, obj.pages_in_bytes, obj.type
FROM   sys.dm_os_memory_objects obj
WHERE  obj.page_allocator_address = (
                               SELECT planobj.page_allocator_address
                               FROM   sys.dm_os_memory_objects planobj
                               WHERE  planobj.memory_object_address = @CompiledPlanAddress
ORDER BY obj.[type], obj.pages_in_bytes;

The query below lists all of the Compiled Plans in sys.dm_exec_cached_plans along with the Query Plan and statements for each batch. The query directly above is incorporated into the query below via XML as the MemoryObjects field:

SELECT cplan.bucketid,
       '---' AS [---],
       '---' AS [---],
       planobj.pages_in_bytes / 1024 AS [BaseSingleStatementPlanKB],
       '===' AS [===],
       cplan.size_in_bytes AS [TotalPlanBytes],
       (SELECT CONVERT(VARCHAR(30), obj.memory_object_address, 1)
               AS [memory_object_address], obj.pages_in_bytes, obj.[type]
        FROM   sys.dm_os_memory_objects obj
        WHERE  obj.page_allocator_address = planobj.page_allocator_address
        FOR XML RAW(N'object'), ROOT(N'memory_objects'), TYPE) AS [MemoryObjects]
FROM   sys.dm_exec_cached_plans cplan
OUTER APPLY sys.dm_exec_sql_text(cplan.[plan_handle]) sqltxt
OUTER APPLY sys.dm_exec_query_plan(cplan.[plan_handle]) qrypln
INNER JOIN sys.dm_os_memory_objects planobj
        ON planobj.memory_object_address = cplan.memory_object_address
OUTER APPLY (SELECT SUM(domo.[pages_in_bytes]) AS [AllocatedBytes]
             FROM   sys.dm_os_memory_objects domo
             WHERE  domo.page_allocator_address = planobj.page_allocator_address) bytes
WHERE  cplan.parent_plan_handle IS NULL
AND    cplan.cacheobjtype IN (N'Compiled Plan', N'Compiled Plan Stub')
--AND cplan.plan_handle = 0x06000D0031CD572910529CE001000000xxxxxxxx
ORDER BY cplan.objtype, cplan.plan_handle;

Please note that:

  • the TotalPlanBytes field is just a re-statement of the sys.dm_exec_cached_plans.size_in_bytes field,
  • the AllocatedBytes field is the SUM of the related memory objects that typically matches TotalPlanBytes (i.e. size_in_bytes)
  • the AllocatedBytes field will occasionally be greater than TotalPlanBytes (i.e. size_in_bytes) due to the memory consumption increasing during execution. This seems to happen mostly due to recompilation (which should be evident with the usecounts field showing 1)
  • the BaseSingleStatementPlanKB field should match the CachedPlanSize attribute of the QueryPlan node in the XML, but only when using a single query batch.
  • for batches with multiple queries, there should be rows marked as MEMOBJ_STATEMENT in sys.dm_os_memory_objects, one for each query. The pages_in_bytes field for these rows should match the individual <QueryPlan> nodes of the XML plan.



Pro SQL Server Internals has examined this issue.

dbcc freeproccache
@SQL nvarchar (MAX)
,@I INT = 0
while @I < 1000
    SELECT @SQL = N'declare @C int;select @C=ID from dbo.Employees where ID='
           + CONVERT (nvarchar (10), @I);

    EXEC (@SQL);
    SELECT @I += 1;

p.usecounts, p.cacheobjtype, p.objtype, p.size_in_bytes, t.[text]
sys.dm_exec_cached_plans p CROSS apply
sys.dm_exec_sql_text (p.plan_handle) t
p.cacheobjtype LIKE 'Compiled Plan%' AND
t.[text] LIKE '%Employees%'
p.objtype DESC;

there are 1,000 plans cached, each of which uses 32 KB of memory, or 32 MB total. As you can guess, ad-hoc queries in busy systems can lead to excessive plan cache memory usage.

exec sys.sp_configure N'optimize for ad hoc workloads', N'1';
reconfigure with override;

with the Optimize for ad-hoc workloads setting enabled, you would see the plan cache content. As you can see, it now uses just 272 KB of memory rather than the 32 MB it used to before

enter image description here

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.