sql >> Databasteknik >  >> RDS >> Database

Varför Optimizern inte använder Buffer Pool Knowledge

SQL Server har en kostnadsbaserad optimerare som använder kunskap om de olika tabellerna som är inblandade i en fråga för att producera vad den bestämmer är den mest optimala planen under den tid som är tillgänglig för den under kompileringen. Denna kunskap inkluderar vilka index som än finns och deras storlekar och vilken kolumnstatistik som än finns. En del av det som går till att hitta den optimala frågeplanen är att försöka minimera antalet fysiska läsningar som behövs under planens exekvering.

En sak som jag har blivit tillfrågad några gånger är varför optimeraren inte tar hänsyn till vad som finns i SQL Server-buffertpoolen när man kompilerar en frågeplan, eftersom det säkert kan få en fråga att köras snabbare. I det här inlägget ska jag förklara varför.

Ta reda på innehållet i buffertpoolen

Den första anledningen till att optimeraren ignorerar buffertpoolen är att det är ett icke-trivialt problem att ta reda på vad som finns i buffertpoolen på grund av hur buffertpoolen är organiserad. Datafilsidor styrs i buffertpoolen av små datastrukturer som kallas buffertar, som spårar saker som (icke uttömmande lista):

  • Sidans ID (filnummer:sidnummer-i-fil)
  • Förra gången sidan refererades (används av den lata skribenten för att implementera den minst nyligen använda algoritmen som skapar ledigt utrymme vid behov)
  • Minnesplatsen för 8KB-sidan i buffertpoolen
  • Oavsett om sidan är smutsig eller inte (en smutsig sida har ändringar på sig som ännu inte har skrivits tillbaka till hållbar lagring)
  • Den allokeringsenhet sidan tillhör (förklaras här) och allokeringsenhets-ID kan användas för att ta reda på vilken tabell och vilken index sidan är en del av

För varje databas som har sidor i buffertpoolen finns det en hashlista med sidor, i sid-ID-ordning, som snabbt är sökbar för att avgöra om en sida redan finns i minnet eller om en fysisk läsning måste utföras. Men ingenting gör det lätt för SQL Server att avgöra vilken procentandel av bladnivån för varje index i en tabell som redan finns i minnet. Koden skulle behöva skanna hela listan med buffertar för databasen, leta efter buffertar som mappar sidor för tilldelningsenheten i fråga. Och ju fler sidor i minnet för en databas, desto längre tid skulle skanningen ta. Det skulle vara oöverkomligt dyrt att göra som en del av frågekompileringen.

Om du är intresserad skrev jag ett inlägg för ett tag sedan med lite T-SQL-kod som skannar buffertpoolen och ger lite mätvärden, med hjälp av DMV sys.dm_os_buffer_descriptors .

Varför skulle det vara farligt att använda buffertpoolens innehåll

Låt oss låtsas att det *finns* en mycket effektiv mekanism för att fastställa buffertpoolens innehåll som optimeraren kan använda för att hjälpa den att välja vilket index som ska användas i en frågeplan. Hypotesen jag ska undersöka är om optimeraren vet att tillräckligt med ett mindre effektivt (större) index redan finns i minnet, jämfört med det mest effektiva (mindre) indexet att använda, bör den välja in-memory-indexet eftersom det kommer att minska antalet fysiska läsningar som krävs och frågan kommer att köras snabbare.

Scenariot jag kommer att använda är följande:en tabell BigTable har två icke-klustrade index, Index_A och Index_B, som båda helt täcker en viss fråga. Frågan kräver en fullständig genomsökning av indexets bladnivå för att hämta frågeresultaten. Tabellen har 1 miljon rader. Index_A har 200 000 sidor på bladnivå och Index_B har 1 miljon sidor på bladnivå, så en fullständig genomsökning av Index_B kräver bearbetning av fem gånger fler sidor.

Jag skapade detta konstruerade exempel på en bärbar dator som kör SQL Server 2019 med 8 processorkärnor, 32 GB minne och solid-state-diskar. Koden är följande:

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

Och sedan tog jag tid på de konstruerade frågorna:

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

Du kan se när inget index finns i minnet, Index_A är lätt det mest effektiva indexet att använda, med en förfluten frågetid på 764 ms mot 2 761 ms med Index_B, och detsamma gäller när båda indexen finns i minnet. Men om Index_B finns i minnet och Index_A inte är det, om frågan använder Index_B (149ms) kommer den att köras snabbare än om den använder Index_A (764ms).

Låt oss nu låta optimeraren basera planvalet på vad som finns i buffertpoolen...

Om Index_A för det mesta inte finns i minnet och Index_B mestadels i minnet, skulle det vara mer effektivt att kompilera frågeplanen för att använda Index_B, för en fråga som körs i det ögonblicket. Även om Index_B är större och skulle behöva fler CPU-cykler att skanna igenom, är fysiska läsningar mycket långsammare än de extra CPU-cyklerna så en effektivare frågeplan minimerar antalet fysiska läsningar.

Detta argument gäller bara, och en "använd Index_B"-frågeplan är bara mer effektiv än en "använd Index_A"-frågeplan, om Index_B förblir mestadels i minnet och Index_A förblir oftast inte i minnet. Så fort det mesta av Index_A finns i minnet skulle frågeplanen "använd Index_A" vara effektivare, och frågeplanen "använd Index_B" är fel val.

Situationerna när den sammanställda "använd Index_B"-planen är mindre effektiv än den kostnadsbaserade "använd Index_A"-planen är (generaliserande):

  • Index_A och Index_B finns båda i minnet:den kompilerade planen kommer att ta nästan tre gånger längre tid
  • Inget av indexen är minnesrelaterat:den kompilerade planen tar över 3,5 gånger längre
  • Index_A är minnesresident och Index_B är det inte:alla fysiska läsningar som utförs av planen är ovidkommande, OCH det kommer att ta hela 53 gånger längre tid

Sammanfattning

Även om optimeraren i vår tankeövning kan använda buffertpoolkunskap för att kompilera den mest effektiva frågan i ett enda ögonblick, skulle det vara ett farligt sätt att driva plankompilering på grund av den potentiella volatiliteten hos buffertpoolens innehåll, vilket gör den framtida effektiviteten av den cachade planen mycket opålitlig.

Kom ihåg att optimerarens jobb är att snabbt hitta en bra plan, inte nödvändigtvis den enskilt bästa planen för 100 % av alla situationer. Enligt min åsikt gör SQL Server-optimeraren det rätta genom att ignorera det faktiska innehållet i SQL Server-buffertpoolen och förlitar sig istället på de olika kostnadsreglerna istället för att skapa en frågeplan som sannolikt är den mest effektiva för det mesta .


  1. Vad är SQL Server-ekvivalenten för ELT() i MySQL?

  2. Tips för att hantera PostgreSQL på distans

  3. Hur man tar bort ett lösenord från en databas i Access 2016

  4. Skapa en offentlig standardprofil för databaspost i SQL Server (T-SQL)