sql >> Databáze >  >> RDS >> Database

Proč optimalizátor nepoužívá znalost zásobníku vyrovnávacích pamětí

SQL Server má nákladově orientovaný optimalizátor, který využívá znalosti o různých tabulkách zahrnutých v dotazu k vytvoření toho, co se podle něj považuje za nejoptimálnější plán v čase, který má během kompilace k dispozici. Tyto znalosti zahrnují jakékoli existující indexy a jejich velikosti a jakékoli existující statistiky sloupců. Součástí hledání optimálního plánu dotazů je snaha minimalizovat počet fyzických čtení potřebných během provádění plánu.

Jedna věc, kterou jsem byl několikrát dotázán, je, proč optimalizátor nezohledňuje, co je ve fondu vyrovnávacích pamětí serveru SQL Server při sestavování plánu dotazů, protože by to jistě mohlo urychlit provádění dotazu. V tomto příspěvku vysvětlím proč.

Zjištění obsahu zásobníku vyrovnávacích pamětí

Prvním důvodem, proč optimalizátor ignoruje fond vyrovnávacích pamětí, je to, že není triviální problém zjistit, co je ve fondu vyrovnávacích pamětí kvůli způsobu, jakým je fond vyrovnávacích pamětí organizován. Stránky datových souborů jsou řízeny ve fondu vyrovnávacích pamětí pomocí malých datových struktur nazývaných buffery, které sledují věci jako (neúplný seznam):

  • ID stránky (číslo souboru:číslo-stránky-v-souboru)
  • Poslední odkaz na stránku (používá jej líný autor k implementaci nejméně používaného algoritmu, který v případě potřeby vytváří volné místo)
  • Umístění paměti stránky o velikosti 8 kB ve fondu vyrovnávacích pamětí
  • Zda je stránka špinavá nebo ne (špinavá stránka obsahuje změny, které ještě nebyly zapsány zpět do trvalého úložiště)
  • Alokační jednotku, ke které stránka patří (vysvětleno zde) a ID alokační jednotky lze použít ke zjištění, které tabulky a indexu je stránka součástí

Pro každou databázi, která má stránky ve fondu vyrovnávacích pamětí, existuje hašovací seznam stránek v pořadí ID stránek, který lze rychle prohledávat a určit, zda je stránka již v paměti nebo zda je nutné provést fyzické čtení. Nic však SQL Serveru jednoduše neumožňuje určit, jaké procento úrovně listů pro každý index tabulky je již v paměti. Kód by musel prohledat celý seznam vyrovnávacích pamětí pro databázi a hledat vyrovnávací paměti, které mapují stránky pro příslušnou alokační jednotku. A čím více stránek v paměti databáze, tím déle by skenování trvalo. Bylo by to neúměrně drahé dělat to jako součást kompilace dotazů.

Pokud vás to zajímá, napsal jsem před chvílí příspěvek s nějakým kódem T-SQL, který skenuje zásobník vyrovnávacích pamětí a poskytuje nějaké metriky pomocí DMV sys.dm_os_buffer_descriptors .

Proč by bylo používání obsahu zásobníku vyrovnávacích pamětí nebezpečné

Předpokládejme, že *existuje* vysoce účinný mechanismus pro určení obsahu fondu vyrovnávacích pamětí, který může optimalizátor použít, aby mu pomohl vybrat, který index použít v plánu dotazů. Hypotéza, kterou se chystám prozkoumat, je, že pokud optimalizátor dostatečně ví, že méně účinný (větší) index je již v paměti, ve srovnání s nejúčinnějším (menším) indexem, který se má použít, by měl vybrat index v paměti, protože bude snížit počet požadovaných fyzických čtení a dotaz bude probíhat rychleji.

Scénář, který použiji, je následující:tabulka BigTable má dva neshlukované indexy, Index_A a Index_B, oba zcela pokrývají konkrétní dotaz. Dotaz vyžaduje úplné prohledání úrovně listů indexu, aby bylo možné načíst výsledky dotazu. Tabulka má 1 milion řádků. Index_A má na úrovni listu 200 000 stránek a Index_B má na úrovni listu 1 milion stránek, takže kompletní skenování Index_B vyžaduje zpracování pětkrát více stránek.

Tento vykonstruovaný příklad jsem vytvořil na notebooku se systémem SQL Server 2019 s 8 jádry procesoru, 32 GB paměti a SSD disky. Kód je následující:

CREATE TABLE BigTable ( c1 BIGINT IDENTITY, c2 AS (c1 * 2), c3 CHAR (1500) DEFAULT 'a', c4 CHAR (5000) VÝCHOZÍ 'b');PŘEJÍT VLOŽIT DO VÝCHOZÍ HODNOTY BigTable;GO 1000000 CREATE NEZAHRNUTÝ INDEX Index_A NA BigTable (c2) ZAHRNUJE (c3);-- 5 záznamů na stránku =200 000 stránek VYTVOŘIT NEZAHRNUTÝ INDEX Index_B NA BigTable (c2) ZAHRNUTÍ (c4);-- 1 záznam na stránku =1 milion stránek PŘEJÍT KONTROLNÍ BOD; /před> 

A pak jsem načasoval vykonstruované dotazy:

DBCC DROPCLEANBUFFERS;GO -- Index_A není v pamětiSELECT SUM (c2) FROM BigTable WITH (INDEX (Index_A));GO-- Čas CPU =796 ms, uplynulý čas =764 ms -- Index_A v pamětiSELECT SUM (c2) FROM BigTable WITH (INDEX (Index_A));GO-- čas CPU =312 ms, uplynulý čas =52 ms DBCC DROPCLEANBUFFERS;GO -- Index_B není v paměti VYBERTE SOUČET (c2) Z BigTable WITH (INDEX (Index_B));GO- - Čas CPU =2952 ms, uplynulý čas =2761 ms -- Index_B v paměti SELECT SUM (c2) FROM BigTable WITH (INDEX (Index_B));GO -- CPU čas =1219 ms, uplynulý čas =149 ms

Můžete vidět, že když žádný index není v paměti, Index_A je snadno nejefektivnějším indexem k použití, s uplynulým časem dotazu 764 ms oproti 2 761 ms při použití Index_B, a totéž platí, když jsou oba indexy v paměti. Pokud je však Index_B v paměti a Index_A nikoli, pokud dotaz používá Index_B (149 ms), poběží rychleji, než když používá Index_A (764 ms).

Nyní nechme optimalizátor založit výběr plánu na tom, co je ve fondu vyrovnávacích pamětí…

Pokud Index_A většinou není v paměti a Index_B je většinou v paměti, bylo by efektivnější sestavit plán dotazů pro použití Index_B pro dotaz spuštěný v tomto okamžiku. I když je Index_B větší a potřeboval by více cyklů CPU k prohledání, fyzické čtení je mnohem pomalejší než extra cykly CPU, takže efektivnější plán dotazů minimalizuje počet fyzických čtení.

Tento argument platí pouze a plán dotazů „použít Index_B“ je efektivnější než plán dotazů „použít Index_A“, pokud Index_B zůstává většinou v paměti a Index_A většinou není v paměti. Jakmile bude většina Index_A v paměti, plán dotazů „použít Index_A“ by byl efektivnější a plán dotazů „použít Index_B“ je špatnou volbou.

Situace, kdy je sestavený plán „použít Index_B“ méně efektivní než plán „použít Index_A“ založený na nákladech, jsou (zobecnění):

  • Index_A a Index_B jsou oba v paměti:sestavení plánu bude trvat téměř třikrát déle
  • Ani jeden index není rezidentní v paměti:zkompilovaný plán trvá 3,5krát déle
  • Index_A je rezidentní v paměti a Index_B nikoli:všechna fyzická čtení prováděná plánem jsou mimořádná A bude to trvat 53krát déle

Shrnutí

Ačkoli v našem myšlenkovém cvičení může optimalizátor využít znalosti fondu vyrovnávacích pamětí ke kompilaci nejúčinnějšího dotazu v jediném okamžiku, byl by to nebezpečný způsob, jak řídit kompilaci plánu kvůli potenciální nestálosti obsahu zásobníku vyrovnávacích pamětí, což by budoucí efektivitu plán uložený v mezipaměti je vysoce nespolehlivý.

Pamatujte, že úkolem optimalizátora je rychle najít dobrý plán, ne nutně ten jediný nejlepší plán pro 100 % všech situací. Podle mého názoru dělá optimalizátor SQL Server správnou věc tím, že ignoruje skutečný obsah zásobníku vyrovnávacích pamětí serveru SQL Server a místo toho se spoléhá na různá pravidla kalkulace, aby vytvořil plán dotazů, který bude pravděpodobně většinu času nejúčinnější. .


  1. Nastavení hodnot sloupců jako názvů sloupců ve výsledku dotazu SQL

  2. Jak spojit dvě tabulky mysql?

  3. Datový model rozvozu restaurace

  4. Vkládání MySQL z jedné databáze do druhé