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

Warum der Optimierer kein Pufferpool-Wissen verwendet

SQL Server verfügt über einen kostenbasierten Optimierer, der das Wissen über die verschiedenen Tabellen verwendet, die an einer Abfrage beteiligt sind, um den Plan zu erstellen, der seiner Meinung nach in der Zeit, die ihm während der Kompilierung zur Verfügung steht, der optimalste ist. Dieses Wissen umfasst alle vorhandenen Indizes und ihre Größen sowie alle vorhandenen Spaltenstatistiken. Ein Teil dessen, was zum Finden des optimalen Abfrageplans beiträgt, ist der Versuch, die Anzahl der physischen Lesevorgänge zu minimieren, die während der Planausführung erforderlich sind.

Eine Sache, die ich ein paar Mal gefragt wurde, ist, warum der Optimierer beim Kompilieren eines Abfrageplans nicht berücksichtigt, was sich im SQL Server-Pufferpool befindet, da dies sicherlich dazu führen könnte, dass eine Abfrage schneller ausgeführt wird. In diesem Beitrag erkläre ich warum.

Herausfinden des Pufferpoolinhalts

Der erste Grund, warum der Optimierer den Pufferpool ignoriert, ist, dass es aufgrund der Art und Weise, wie der Pufferpool organisiert ist, ein nicht triviales Problem ist, herauszufinden, was sich im Pufferpool befindet. Datendateiseiten werden im Pufferpool durch kleine Datenstrukturen namens Puffer gesteuert, die Dinge verfolgen wie (nicht erschöpfende Liste):

  • Die ID der Seite (Dateinummer:Seitennummer-in-Datei)
  • Der letzte Zeitpunkt, zu dem auf die Seite verwiesen wurde (wird vom faulen Schreiber verwendet, um den am wenigsten verwendeten Algorithmus zu implementieren, der bei Bedarf freien Speicherplatz schafft)
  • Der Speicherort der 8-KB-Seite im Pufferpool
  • Ob die Seite fehlerhaft ist oder nicht (auf einer fehlerhaften Seite befinden sich Änderungen, die noch nicht in den dauerhaften Speicher zurückgeschrieben wurden)
  • Die Zuordnungseinheit, zu der die Seite gehört (hier erklärt) und die Zuordnungseinheits-ID können verwendet werden, um herauszufinden, zu welcher Tabelle und zu welchem ​​Index die Seite gehört

Für jede Datenbank, die Seiten im Pufferpool enthält, gibt es eine Hash-Liste von Seiten in der Reihenfolge der Seiten-IDs, die schnell durchsucht werden kann, um festzustellen, ob sich eine Seite bereits im Speicher befindet oder ob ein physischer Lesevorgang durchgeführt werden muss. Allerdings kann SQL Server nicht ohne Weiteres bestimmen, wie viel Prozent der Blattebene für jeden Index einer Tabelle bereits im Arbeitsspeicher vorhanden sind. Der Code müsste die gesamte Pufferliste nach der Datenbank durchsuchen und nach Puffern suchen, die Seiten für die betreffende Zuordnungseinheit abbilden. Und je mehr Seiten für eine Datenbank im Speicher vorhanden sind, desto länger würde der Scan dauern. Es wäre unerschwinglich teuer, dies als Teil der Abfragekompilierung zu tun.

Falls Sie interessiert sind, ich habe vor einiger Zeit einen Beitrag mit T-SQL-Code geschrieben, der den Pufferpool scannt und einige Metriken liefert, wobei die DMV-sys.dm_os_buffer_descriptors verwendet werden .

Warum die Verwendung von Pufferpoolinhalten gefährlich wäre

Lassen Sie uns so tun, als gäbe es einen hocheffizienten Mechanismus, um den Inhalt des Pufferpools zu bestimmen, den der Optimierer verwenden kann, um ihm bei der Auswahl des in einem Abfrageplan zu verwendenden Index zu helfen. Die Hypothese, die ich untersuchen werde, ist, wenn der Optimierer weiß, dass sich ein weniger effizienter (größerer) Index bereits im Speicher befindet, im Vergleich zum effizientesten (kleineren) zu verwendenden Index, er den In-Memory-Index auswählen sollte, weil dies der Fall ist Reduzieren Sie die Anzahl der erforderlichen physischen Lesevorgänge und die Abfrage wird schneller ausgeführt.

Das Szenario, das ich verwenden werde, ist wie folgt:Eine BigTable-Tabelle hat zwei nicht gruppierte Indizes, Index_A und Index_B, die beide eine bestimmte Abfrage vollständig abdecken. Die Abfrage erfordert einen vollständigen Scan der Blattebene des Index, um die Abfrageergebnisse abzurufen. Die Tabelle hat 1 Million Zeilen. Index_A hat 200.000 Seiten auf seiner Blattebene und Index_B hat 1 Million Seiten auf seiner Blattebene, sodass ein vollständiger Scan von Index_B fünfmal mehr Seiten verarbeiten muss.

Ich habe dieses erfundene Beispiel auf einem Laptop erstellt, auf dem SQL Server 2019 mit 8 Prozessorkernen, 32 GB Arbeitsspeicher und Solid-State-Festplatten ausgeführt wird. Der Code lautet wie folgt:

CREATE TABLE BigTable (
  	c1 BIGINT IDENTITY,
  	c2 AS (c1 * 2),
  	c3 CHAR (1500) DEFAULT 'a',
  	c4 CHAR (5000) DEFAULT 'b'
);
GO
 
INSERT INTO BigTable DEFAULT VALUES;
GO 1000000
 
CREATE NONCLUSTERED INDEX Index_A ON BigTable (c2) INCLUDE (c3);
-- 5 records per page = 200,000 pages
GO
 
CREATE NONCLUSTERED INDEX Index_B ON BigTable (c2) INCLUDE (c4);
-- 1 record per page = 1 million pages
GO
 
CHECKPOINT;
GO

Und dann habe ich die erfundenen Abfragen zeitlich festgelegt:

DBCC DROPCLEANBUFFERS;
GO
 
-- Index_A not in memory
SELECT SUM (c2) FROM BigTable WITH (INDEX (Index_A));
GO
-- CPU time = 796 ms, elapsed time = 764 ms
 
-- Index_A in memory
SELECT SUM (c2) FROM BigTable WITH (INDEX (Index_A));
GO
-- CPU time = 312 ms, elapsed time = 52 ms
 
DBCC DROPCLEANBUFFERS;
GO
 
-- Index_B not in memory
SELECT SUM (c2) FROM BigTable WITH (INDEX (Index_B));
GO
-- CPU time = 2952 ms, elapsed time = 2761 ms
 
-- Index_B in memory
SELECT SUM (c2) FROM BigTable WITH (INDEX (Index_B));
GO
-- CPU time = 1219 ms, elapsed time = 149 ms

Sie können sehen, wenn sich keiner der Indizes im Arbeitsspeicher befindet, ist Index_A mit einer verstrichenen Abfragezeit von 764 ms gegenüber 2.761 ms bei Verwendung von Index_B mit Abstand der effizienteste Index, und das Gleiche gilt, wenn sich beide Indizes im Arbeitsspeicher befinden. Wenn sich Index_B jedoch im Speicher befindet und Index_A nicht, wird die Abfrage bei Verwendung von Index_B (149 ms) schneller ausgeführt als bei Verwendung von Index_A (764 ms).

Lassen Sie uns nun dem Optimierer erlauben, die Planauswahl auf dem zu basieren, was sich im Pufferpool befindet …

Wenn sich Index_A größtenteils nicht im Speicher und Index_B hauptsächlich im Speicher befindet, wäre es effizienter, den Abfrageplan so zu kompilieren, dass Index_B für eine zu diesem Zeitpunkt ausgeführte Abfrage verwendet wird. Obwohl Index_B größer ist und mehr CPU-Zyklen zum Durchsuchen benötigen würde, sind physische Lesevorgänge viel langsamer als die zusätzlichen CPU-Zyklen, sodass ein effizienterer Abfrageplan die Anzahl der physischen Lesevorgänge minimiert.

Dieses Argument gilt nur, und ein Abfrageplan „Index_B verwenden“ ist nur dann effizienter als ein Abfrageplan „Index_A verwenden“, wenn Index_B hauptsächlich im Speicher verbleibt und Index_A hauptsächlich nicht im Speicher verbleibt. Sobald der größte Teil von Index_A im Speicher ist, wäre der Abfrageplan „Index_A verwenden“ effizienter, und der Abfrageplan „Index_B verwenden“ ist die falsche Wahl.

Die Situationen, in denen der zusammengestellte Plan „Index_B verwenden“ weniger effizient ist als der kostenbasierte Plan „Index_A verwenden“, sind (allgemein):

  • Index_A und Index_B befinden sich beide im Speicher:Der kompilierte Plan dauert fast dreimal so lange
  • Keiner der Indizes ist speicherresident:Der kompilierte Plan dauert 3,5-mal länger
  • Index_A ist speicherresident und Index_B nicht:Alle vom Plan durchgeführten physischen Lesevorgänge sind irrelevant UND es dauert satte 53-mal länger

Zusammenfassung

Obwohl der Optimierer in unserer Gedankenübung das Pufferpool-Wissen verwenden kann, um die effizienteste Abfrage zu einem einzigen Zeitpunkt zu kompilieren, wäre es aufgrund der potenziellen Volatilität des Pufferpoolinhalts eine gefährliche Methode, die Plankompilierung voranzutreiben, was die zukünftige Effizienz beeinträchtigt Der zwischengespeicherte Plan ist höchst unzuverlässig.

Denken Sie daran, dass es die Aufgabe des Optimierers ist, schnell einen guten Plan zu finden, nicht unbedingt den besten Plan für 100 % aller Situationen. Meiner Meinung nach tut der SQL Server-Optimierer das Richtige, indem er den tatsächlichen Inhalt des SQL Server-Pufferpools ignoriert und sich stattdessen auf die verschiedenen Kostenregeln verlässt, um einen Abfrageplan zu erstellen, der wahrscheinlich die meiste Zeit am effizientesten ist .