Database
 sql >> Datenbank >  >> RDS >> Database

Mehrere Pläne für eine identische Abfrage

Ich sehe oft, dass Leute mit SQL Server kämpfen, wenn sie zwei verschiedene Ausführungspläne für etwas sehen, von dem sie glauben, dass es dieselbe Abfrage ist. Normalerweise wird dies nach anderen Beobachtungen entdeckt, wie z. B. sehr unterschiedlichen Ausführungszeiten. Ich sage, dass sie glauben, dass es sich um dieselbe Abfrage handelt, weil es manchmal so ist und manchmal nicht.

Einer der häufigsten Fälle ist, wenn sie eine Abfrage in SSMS testen und einen anderen Plan erhalten als den, den sie von ihrer Anwendung erhalten. Hier spielen möglicherweise zwei Faktoren eine Rolle (die auch relevant sein könnten, wenn der Vergleich NICHT zwischen der Anwendung und SSMS erfolgt):

  1. Die Anwendung hat fast immer einen anderen SET Einstellungen als SSMS (das sind Sachen wie ARITHABORT , ANSI_NULLS und QUOTED_IDENTIFIER ). Dadurch wird SQL Server gezwungen, die beiden Pläne separat zu speichern; Erland Sommarskog hat dies in seinem Artikel „Langsam in der Anwendung, schnell in SSMS?“ ausführlich behandelt.
  2. Die Parameter, die von der Anwendung verwendet wurden, als ihre Kopie des Plans zum ersten Mal kompiliert wurde, könnten sehr unterschiedlich gewesen sein und zu einem anderen Plan geführt haben als diejenigen, die beim ersten Ausführen der Abfrage von SSMS verwendet wurden – dies wird als Parameter-Sniffing bezeichnet . Erland spricht auch darüber ausführlich, und ich werde seine Empfehlungen nicht wiederkäuen, sondern zusammenfassen, indem ich Sie daran erinnere, dass das Testen der Abfrage der Anwendung in SSMS nicht immer nützlich ist, da es ziemlich unwahrscheinlich ist, dass es sich um einen Apfel-zu-Äpfel-Test handelt.

Es gibt ein paar andere Szenarien, die etwas obskurer sind und die ich in meinem Vortrag über schlechte Gewohnheiten und Best Practices anspreche. Dies sind Fälle, in denen die Pläne nicht unterschiedlich sind, aber mehrere Kopien desselben Plans den Plan-Cache aufblähen. Ich dachte, ich sollte sie hier erwähnen, weil sie immer so viele Leute überraschen.

Groß- und Kleinschreibung und Leerzeichen sind wichtig

SQL Server hasht den Abfragetext in ein Binärformat, was bedeutet, dass jedes einzelne Zeichen im Abfragetext entscheidend ist. Nehmen wir die folgenden einfachen Abfragen:

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

Diese erzeugen natürlich genau die gleichen Ergebnisse und generieren genau den gleichen Plan. Wenn wir uns jedoch ansehen, was wir im Plan-Cache haben:

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%';

Die Ergebnisse sind bedauerlich:

In diesem Fall ist es also klar, dass Groß- und Kleinschreibung sehr wichtig sind. Ich habe letzten Mai ausführlicher darüber gesprochen.

Schemareferenzen sind wichtig

Ich habe schon früher über die Bedeutung der Angabe des Schema-Präfixes beim Verweisen auf ein beliebiges Objekt gebloggt, aber damals war mir nicht ganz bewusst, dass dies auch Auswirkungen auf den Plan-Cache hat.

Werfen wir einen Blick auf einen sehr einfachen Fall, in dem wir zwei Benutzer mit unterschiedlichen Standardschemas haben und sie denselben Abfragetext ausführen, ohne das Objekt über sein Schema zu referenzieren:

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

Wenn wir uns jetzt den Plan-Cache ansehen, können wir sys.dm_exec_plan_attributes abrufen um genau zu sehen, warum wir zwei verschiedene Pläne für identische Abfragen erhalten:

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';

Ergebnisse:

Und wenn Sie alles noch einmal ausführen, aber den dbo. hinzufügen Präfix für beide Abfragen, werden Sie sehen, dass es nur einen Plan gibt, der zweimal verwendet wird. Dies wird zu einem sehr überzeugenden Argument dafür, Objekte immer vollständig zu referenzieren.

SET-Einstellungen redux

Als Randnotiz können Sie einen ähnlichen Ansatz verwenden, um festzustellen, ob SET Die Einstellungen unterscheiden sich für zwei oder mehr Versionen derselben Abfrage. In diesem Fall untersuchen wir die Abfragen, die mit mehreren Plänen verbunden sind, die durch verschiedene Aufrufe derselben gespeicherten Prozedur generiert wurden, aber Sie können sie auch anhand des Abfragetexts oder des Abfragehashs identifizieren.

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;

Wenn Sie hier mehrere Ergebnisse haben, sollten Sie unterschiedliche Werte für set_options sehen (was eine Bitmaske ist). Das ist nur der Anfang; Ich werde hier abschließen und Ihnen sagen, dass Sie bestimmen können, welche Gruppe von Optionen für jeden Plan aktiviert sind, indem Sie den Wert gemäß dem Abschnitt "Evaluieren von Set-Optionen" hier entpacken. Ja, so faul bin ich.

Schlussfolgerung

Es gibt mehrere Gründe, warum Sie möglicherweise unterschiedliche Pläne für dieselbe Abfrage sehen (oder was Sie für dieselbe Abfrage halten). In den meisten Fällen können Sie die Ursache ziemlich einfach isolieren; Die Herausforderung besteht oft darin, überhaupt erst danach zu suchen. In meinem nächsten Beitrag werde ich über ein etwas anderes Thema sprechen:Warum eine Datenbank, die auf einem "identischen" Server wiederhergestellt wird, möglicherweise unterschiedliche Pläne für dieselbe Abfrage liefert.