Database
 sql >> Base de données >  >> RDS >> Database

Plans multiples pour une requête identique

Je vois souvent des gens se débattre avec SQL Server lorsqu'ils voient deux plans d'exécution différents pour ce qu'ils pensent être la même requête. Habituellement, cela est découvert après d'autres observations, telles que des temps d'exécution très différents. Je dis qu'ils pensent qu'il s'agit de la même requête parce que parfois c'est le cas, et parfois non.

L'un des cas les plus courants est lorsqu'ils testent une requête dans SSMS et obtiennent un plan différent de celui qu'ils obtiennent de leur application. Il y a potentiellement deux facteurs en jeu ici (qui pourraient également être pertinents lorsque la comparaison n'est PAS entre l'application et SSMS) :

  1. L'application a presque toujours un SET différent paramètres que SSMS (ce sont des choses comme ARITHABORT , ANSI_NULLS et QUOTED_IDENTIFIER ). Cela force SQL Server à stocker les deux plans séparément; Erland Sommarskog a traité cela en détail dans son article, Slow in the Application, Fast in SSMS ?
  2. Les paramètres utilisés par l'application lorsque sa copie du plan a été compilée pour la première fois auraient pu être très différents, et conduire à un plan différent, de ceux utilisés la première fois que la requête a été exécutée à partir de SSMS - c'est ce qu'on appelle le reniflage de paramètres . Erland en parle aussi en profondeur, et je ne vais pas régurgiter ses recommandations, mais résumer en vous rappelant que tester la requête de l'application dans SSMS n'est pas toujours utile, car il est peu probable qu'il s'agisse d'un test de pommes à pommes.

Il y a quelques autres scénarios un peu plus obscurs que j'évoque dans mon exposé sur les mauvaises habitudes et les meilleures pratiques. Ce sont des cas où les plans ne sont pas différents, mais il existe plusieurs copies du même plan qui gonflent le cache du plan. J'ai pensé que je devais les mentionner ici car ils prennent toujours tant de gens par surprise.

la casse et les espaces blancs sont importants

SQL Server hache le texte de la requête dans un format binaire, ce qui signifie que chaque caractère du texte de la requête est crucial. Prenons les requêtes simples suivantes :

USE AdventureWorks2014;
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
GO
SELECT StoreID FROM Sales.Customer;
GO -- original query
GO
SELECT  StoreID FROM Sales.Customer;
GO ----^---- extra space
GO
SELECT storeid FROM sales.customer;
GO ---- lower case names
GO
select StoreID from Sales.Customer;
GO ---- lower case keywords
GO

Ceux-ci génèrent exactement les mêmes résultats, évidemment, et génèrent exactement le même plan. Cependant, si nous regardons ce que nous avons dans le cache du plan :

SELECT t.[text], p.size_in_bytes, p.usecounts
 FROM sys.dm_exec_cached_plans AS p
 CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
 WHERE LOWER(t.[text]) LIKE N'%sales'+'.'+'customer%';

Les résultats sont malheureux :

Donc, dans ce cas, il est clair que la casse et les espaces sont très importants. J'en ai parlé beaucoup plus en détail en mai dernier.

Les références de schéma sont importantes

J'ai déjà blogué sur l'importance de spécifier le préfixe de schéma lors du référencement d'un objet, mais à l'époque, je n'étais pas pleinement conscient qu'il avait également des implications sur le cache du plan.

Examinons un cas très simple où nous avons deux utilisateurs avec des schémas par défaut différents, et ils exécutent exactement le même texte de requête, sans référencer l'objet par son schéma :

USE AdventureWorks2014;
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
GO
 
CREATE USER SQLPerf1 WITHOUT LOGIN WITH DEFAULT_SCHEMA = Sales;
CREATE USER SQLPerf2 WITHOUT LOGIN WITH DEFAULT_SCHEMA = Person;
GO
 
CREATE TABLE dbo.AnErrorLog(id INT);
GRANT SELECT ON dbo.AnErrorLog TO SQLPerf1, SQLPerf2;
GO
 
EXECUTE AS USER = N'SQLPerf1';
GO
SELECT id FROM AnErrorLog;
GO
REVERT;
GO
EXECUTE AS USER = N'SQLPerf2';
GO
SELECT id FROM AnErrorLog;
GO
REVERT;
GO

Maintenant, si nous jetons un coup d'œil au cache du plan, nous pouvons extraire sys.dm_exec_plan_attributes pour voir exactement pourquoi nous obtenons deux forfaits différents pour des requêtes identiques :

SELECT t.[text], p.size_in_bytes, p.usecounts, 
  [schema_id] = pa.value, 
  [schema] = s.name
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
CROSS APPLY sys.dm_exec_plan_attributes(p.plan_handle) AS pa
INNER JOIN sys.schemas AS s ON s.[schema_id] = pa.value
WHERE t.[text] LIKE N'%AnError'+'Log%' 
AND pa.attribute = N'user_id';

Résultats :

Et si vous relancez tout mais ajoutez le dbo. préfixe aux deux requêtes, vous verrez qu'il n'y a qu'un seul plan qui est utilisé deux fois. Cela devient un argument très convaincant pour toujours référencer entièrement les objets.

RÉGLAGE des paramètres redux

En remarque, vous pouvez utiliser une approche similaire pour déterminer si SET les paramètres sont différents pour deux versions ou plus de la même requête. Dans ce cas, nous étudions les requêtes impliquées dans plusieurs plans générés par différents appels à la même procédure stockée, mais vous pouvez également les identifier par le texte de la requête ou le hachage de la requête.

SELECT p.plan_handle, p.usecounts, p.size_in_bytes, 
  set_options = MAX(a.value)
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
CROSS APPLY sys.dm_exec_plan_attributes(p.plan_handle) AS a
WHERE t.objectid = OBJECT_ID(N'dbo.procedure_name')
AND a.attribute = N'set_options'
GROUP BY p.plan_handle, p.usecounts, p.size_in_bytes;

Si vous avez plusieurs résultats ici, vous devriez voir différentes valeurs pour set_options (qui est un masque de bits). Ce n'est que le début; Je vais me débrouiller ici et vous dire que vous pouvez déterminer quel ensemble d'options est activé pour chaque plan en déballant la valeur conformément à la section "Évaluer les options d'ensemble" ici. Oui, je suis si paresseux.

Conclusion

Il existe plusieurs raisons pour lesquelles vous pouvez voir différents plans pour la même requête (ou ce que vous pensez être la même requête). Dans la plupart des cas, vous pouvez isoler la cause assez facilement. le défi est souvent de savoir le chercher en premier lieu. Dans mon prochain article, je parlerai d'un sujet légèrement différent :pourquoi une base de données restaurée sur un serveur "identique" peut donner des plans différents pour la même requête.