Lors de l'exécution d'une requête incluant le plan d'exécution réel, l'opérateur racine (SELECT
) m'indique que taille du plan mis en cache est de 32 Ko.
Une requête qui rejoint sys.dm_exec_cached_plans
et sys.dm_os_memory_objects
, en regardant le plan en question, dit que les valeurs de pages_in_bytes
et max_pages_in_bytes
sont 32 768 (32 Ko), ce qui correspond à la taille du plan mis en cache.
Ce que je ne comprends pas, c'est la valeur de sys.dm_exec_cached_plans.size_in_bytes
, qui représente 49152 (48 Ko). J'ai lu BOL sur toutes ces colonnes, et surtout size_in_bytes
qui dit:
"Nombre d'octets consommés par l'objet cache."
Je ne peux pas mettre en place ce dernier morceau du puzzle, pour comprendre ce que cela signifie vraiment.
Je sais que tous les opérateurs (sans parler de l'allocation de mémoire supplémentaire utilisée pour les tris et les hachages) nécessitent une certaine quantité de mémoire fixe, pour stocker l'état, faire des calculs, etc., qui est stocké avec le plan optimisé dans le cache, mais où?
Donc, mes questions sont:
size_in_bytes
Vraiment méchantJe sais que ce sont des DMV différents avec des fonctions différentes, mais ils sont liés. Les plans compilés (mis en cache) dans sys.dm_exec_cached_plans
rejoint sys.dm_os_memory_objects
sur memory_object_address
colonne. La raison pour laquelle je poste les questions ici, c'est que je demande de l'aide à ce sujet, en comprenant comment interpréter les DMV et leurs colonnes.
Si size_in_bytes
est la taille du plan mis en cache, pourquoi SQL Server indique-t-il une autre valeur dans le plan d'exécution réel?
Nouvelle requête, nouveaux numéros:
sys.dm_exec_cached_plans.size_in_bytes
24 Kosys.dm_os_memory_objects.pages_in_bytes, .max_pages_in_bytes
16 Ko.Notez également que cette requête ne nécessite aucune allocation de mémoire supplémentaire pour les opérations de tri et de hachage.
Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
La raison pour laquelle le size_in_bytes
champ du sys.dm_exec_cached_plans
DMV, au moins en termes de "plans compilés", est plus grand que l'attribut CachedPlanSize
du nœud QueryPlan
dans le plan XML parce qu'un plan compilé n'est pas la même chose qu'un Plan de requête. Un plan compilé est composé de plusieurs objets mémoire, dont la taille combinée équivaut à size_in_bytes
champ. Ainsi, la description de " Nombre d'octets consommés par l'objet cache" que vous avez trouvée dans la documentation est exacte; c'est juste qu'il est facile de mal interpréter ce que l'on entend par "objet cache" étant donné le nom du DMV et que le terme "plan" a plusieurs significations.
Un plan compilé est un conteneur qui contient diverses informations relatives à la requête batch (c'est-à-dire pas seulement une seule instruction), une (ou plusieurs) de ces pièces étant le ou les plans de requête . Les plans compilés ont un objet mémoire de niveau supérieur de MEMOBJ_COMPILE_ADHOC qui est la ligne dans sys.dm_os_memory_objects
qui est lié via le memory_object_address
champ dans les deux DMV. Cet objet mémoire contient la table des symboles, la collection de paramètres, les liens vers les objets associés, le cache d'accesseur, le cache de métadonnées TDS et éventuellement d'autres éléments. Les plans compilés sont partagés entre les sessions/utilisateurs qui exécutent le même batch avec les mêmes paramètres de session. Cependant, certains objets associés sont pas partagés entre les sessions/utilisateurs.
Les plans compilés ont également un ou plusieurs objets dépendants qui peuvent être trouvés en passant le plan_handle
(dans sys.dm_exec_cached_plans
) dans le sys.dm_exec_cached_plan_dependent_objects
DMF. Il existe deux types d'objets dépendants: Plan exécutable (objet mémoire = MEMOBJ_EXECUTE ) et curseur (objet mémoire = MEMOBJ_CURSOREXEC ). Il y aura 0 ou plusieurs objets Cursor, un pour chaque curseur. Il y aura également un ou plusieurs objets Plan exécutable, un pour chaque utilisateur exécutant le même batch, donc les plans exécutables sont pas partagés entre les utilisateurs. Les plans exécutables contiennent des paramètres d'exécution et des informations sur les variables locales, l'état d'exécution tel que l'instruction en cours d'exécution, les ID d'objet pour les objets créés au moment de l'exécution (je suppose que cela fait référence aux variables de table, aux tables temporaires, aux procédures stockées temporaires, etc.) , et éventuellement d'autres éléments.
Chaque instruction dans un lot multi-instructions est contenue dans une instruction compilée (objet mémoire = MEMOBJ_STATEMENT ). La taille de chaque instruction compilée (c'est-à-dire pages_in_bytes
) divisé par 1024 devrait correspondre à CachedPlanSize="xx"
valeurs de <QueryPlan>
nœuds dans le plan XML. Les instructions compilées ont souvent un (éventuellement plus?) Plans d'exécution de requête associés (objet mémoire = MEMOBJ_XSTMT ). Enfin, pour chaque plan de requête d'exécution qui est une requête, il doit y avoir un contexte d'exécution de requête associé (objet mémoire = MEMOBJ_QUERYEXECCNTXTFORSE ).
En ce qui concerne les instructions compilées, les lots d'instructions uniques n'ont pas d'instruction compilée distincte (c.-à-d. MEMOBJ_STATEMENT ) ou un plan de requête d'exécution distinct (c.-à-d. MEMOBJ_XSTMT ) objets. La valeur de chacun de ces objets sera stockée dans l'objet principal du plan compilé (c'est-à-dire MEMOBJ_COMPILE_ADHOC ), et dans ce cas, le pages_in_bytes
la valeur de cet objet principal divisée par 1024 doit correspondre à la taille CachedPlanSize
dans le <QueryPlan>
noeud du plan XML. Cependant, ces valeurs ne seront pas égales dans les lots à instructions multiples.
Le size_in_bytes
la valeur peut être dérivée en additionnant les entrées dans le sys.dm_os_memory_objects
DMV (les éléments indiqués ci-dessus en gras), tous liés par dm_os_memory_objects.page_allocator_address
pour ce plan compilé. L'astuce pour obtenir la valeur correcte consiste à obtenir d'abord le memory_object_address
de sys.dm_exec_cached_plans
pour un plan compilé particulier, puis utilisez-le pour obtenir la ligne correspondante MEMOBJ_COMPILE_ADHOC de sys.dm_os_memory_objects
en fonction de son memory_object_address
champ. Ensuite, prenez le page_allocator_address
valeur de sys.dm_os_memory_objects
pour cette ligne, et utilisez-la pour récupérer toutes les lignes de sys.dm_os_memory_objects
qui ont le même page_allocator_address
valeur. (Veuillez noter que cette technique ne fonctionne pas pour les autres types d'objets mis en cache: Parse Tree , Extended Proc , CLR Compiled Proc , et CLR Compiled Func .)
En utilisant le memory_object_address
valeur obtenue de sys.dm_exec_cached_plans
, vous pouvez voir tous les composants du plan compilé via la requête suivante:
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;
La requête ci-dessous répertorie tous les plans compilés dans sys.dm_exec_cached_plans
avec le plan de requête et les instructions pour chaque lot. La requête directement ci-dessus est incorporée dans la requête ci-dessous via XML en tant que champ MemoryObjects
:
SELECT cplan.bucketid,
cplan.pool_id,
cplan.refcounts,
cplan.usecounts,
cplan.size_in_bytes,
cplan.memory_object_address,
cplan.cacheobjtype,
cplan.objtype,
cplan.plan_handle,
'---' AS [---],
qrypln.[query_plan],
sqltxt.[text],
'---' AS [---],
planobj.pages_in_bytes,
planobj.pages_in_bytes / 1024 AS [BaseSingleStatementPlanKB],
'===' AS [===],
cplan.size_in_bytes AS [TotalPlanBytes],
bytes.AllocatedBytes,
(SELECT CONVERT(VARCHAR(30), obj.memory_object_address, 1)
AS [memory_object_address], obj.pages_in_bytes, obj.[type]
--,obj.page_size_in_bytes
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;
Veuillez noter que:
TotalPlanBytes
n'est qu'une ré-déclaration du sys.dm_exec_cached_plans.size_in_bytes
champ,AllocatedBytes
est la somme des objets mémoire associés qui correspond généralement à TotalPlanBytes
(c'est-à-dire size_in_bytes
)AllocatedBytes
sera parfois supérieur à TotalPlanBytes
(c'est-à-dire size_in_bytes
) en raison de l'augmentation de la consommation de mémoire pendant l'exécution. Cela semble se produire principalement en raison de la recompilation (ce qui devrait être évident avec le champ usecounts
affichant 1
)BaseSingleStatementPlanKB
devrait correspond à l'attribut CachedPlanSize
du nœud QueryPlan
dans le XML, mais seulement = lors de l'utilisation d'un seul lot de requêtes.MEMOBJ_STATEMENT
dans sys.dm_os_memory_objects
, un pour chaque requête. Le pages_in_bytes
le champ de ces lignes doit correspondre à l'individu <QueryPlan>
nœuds du plan XML.Ressources:
Pro SQL Server Internals a examiné ce problème.
dbcc freeproccache
GO
DECLARE
@SQL nvarchar (MAX)
,@I INT = 0
while @I < 1000
BEGIN
SELECT @SQL = N'declare @C int;select @C=ID from dbo.Employees where ID='
+ CONVERT (nvarchar (10), @I);
EXEC (@SQL);
SELECT @I += 1;
END
GO
SELECT
p.usecounts, p.cacheobjtype, p.objtype, p.size_in_bytes, t.[text]
FROM
sys.dm_exec_cached_plans p CROSS apply
sys.dm_exec_sql_text (p.plan_handle) t
WHERE
p.cacheobjtype LIKE 'Compiled Plan%' AND
t.[text] LIKE '%Employees%'
ORDER BY
p.objtype DESC;
1 000 plans sont mis en cache, chacun utilisant 32 Ko de mémoire ou 32 Mo au total. Comme vous pouvez le deviner , les requêtes ad hoc dans les systèmes occupés peuvent entraîner une utilisation excessive de la mémoire cache du plan.
exec sys.sp_configure N'optimize for ad hoc workloads', N'1';
reconfigure with override;
avec le paramètre Optimiser pour les charges de travail ad-hoc activé, vous verriez le contenu du cache du plan. Comme vous pouvez le voir, il utilise désormais seulement 272 Ko de mémoire au lieu des 32 Mo qu'il utilisait auparavant