it-swarm-eu.dev

Proč tento dotaz nepoužívá můj neotevřený index a jak jej mohu vytvořit?

V návaznosti na tato otázka o zvýšení výkonu dotazu bych rád věděl, zda existuje způsob, jak můj index použít jako výchozí.

Tento dotaz trvá přibližně 2,5 sekundy:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

Tento běží přibližně za 33 ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 
ORDER BY [DateEntered], [DeviceID];

V poli [ID] (pk) je klastrovaný index a na [DateEntered], [DeviceID] je klastrovaný index. První dotaz používá seskupený index, druhý dotaz používá můj neseskupený index. Moje otázka je dvě části:

  • Proč, protože oba dotazy mají klauzuli WHERE v poli [DateEntered], používá server klastrovaný index na prvním, ale ne na druhém?
  • Jak mohu nastavit, aby byl tento clusterový index ve výchozím stavu použit v tomto dotazu i bez uspořádání? (Nebo proč bych to chování nechtěl?)
12
Nate

první dotaz provede skenování tabulky na základě prahu, který jsem dříve vysvětlil v: Je možné zvýšit výkon dotazu na úzké tabulce s miliony řádků?

(s největší pravděpodobností váš dotaz bez TOP 1000 klauze vrátí více než 46 000 řádků. nebo něco mezi 35 a 46 tis. (šedá oblast ;-))

druhý dotaz, musí být objednán. Vzhledem k tomu, že jste NC index je uspořádán v požadovaném pořadí, je pro levnějšího použití optimalizátoru tento index levnější a poté pro vyhledávání záložek v seskupeném indexu, aby chybějící sloupce byly porovnány s provedením seskupeného indexového skenování a poté, co je potřeba nařídit to.

obrátit pořadí sloupců v ORDER BY Klauzule a jste zpět do seskupeného indexového skenování, protože NC INDEX je pak zbytečný.

editace zapomněla odpověď na vaši druhou otázku, proč to nechcete

Použití neklastrovaného nekryjícího indexu znamená, že se v NC indexu vyhledá rowID a pak chybějící sloupce se musí vyhledat v seskupeném indexu (seskupený index obsahuje všechny sloupce tabulky). IO prohledávání chybějících sloupců v seskupeném indexu jsou Náhodné IO.

Klíčem je zde NÁHODNÉ. protože pro každý řádek nalezený v NC indexu musí přístupové metody vyhledat novou stránku v seskupeném indexu. To je náhodné, a proto velmi drahé.

Na druhou stranu by mohl optimalizátor také jít na seskupené indexové skenování. Může použít mapy přidělení k vyhledávání rozsahů skenování a právě začít číst index clusterů ve velkých blocích. To je postupné a mnohem levnější. (pokud vaše tabulka není roztříštěná :-)) Nevýhodou je, že je třeba si přečíst WHOLE clusterový index. To je špatné pro váš buffer a potenciálně velké množství IO. ale přesto, sekvenční IO.

Ve vašem případě se optimalizátor rozhodne někde mezi řádky 35 až 46 000, je pro skenování s úplným seskupeným indexem levnější. Jo, je to špatně. A v mnoha případech s úzkými neslastovanými indexy s tím, že k tomu nebudou selektivní klauzule WHERE nebo velká tabulka, se to pokazí. (Váš stůl je horší, protože je to také velmi úzký stůl.)

Nyní přidáte ORDER BY zkomplikuje prohledávání celého seskupeného indexu a následné objednání výsledků. Místo toho optimalizátor předpokládá, že je levnější používat již objednaný NC index a poté zaplatit náhodně IO penalizace za vyhledávání záložek).

Vaše objednávka je tedy perfektním řešením typu „dotaz nápověda“. Ale v určitém okamžiku, jakmile budou výsledky vašeho dotazu tak velké, bude penalizace za náhodný IO při vyhledávání záložek tak velká, že bude pomalejší. Předpokládám, že optimalizátor změní plány zpět na skenování seskupeného indexu před tímto bodem, ale nikdy to nevíte jistě.

Ve vašem případě, pokud jsou vaše přílohy seřazeny podle data, jak je uvedeno v chatu a předchozí otázka (viz odkaz), je lepší vytvořit seskupený index ve sloupci EnterDate.

9
Edward Dortland

Vyjádření dotazu pomocí jiné syntaxe může někdy pomoci sdělit vašemu přání použít klastrovaný index optimalizátoru. Měli byste najít níže uvedený formulář, který vám poskytne požadovaný plán:

SELECT
    [ID],
    [DeviceID],
    [IsPUp],
    [IsWebUp],
    [IsPingUp],
    [DateEntered]
FROM [dbo].[Heartbeats]
WHERE
    [ID] IN
(
    -- Keys
    SELECT TOP (1000)
        [ID]
    FROM [dbo].[Heartbeats]
    WHERE 
        [DateEntered] >= CONVERT(datetime, '2011-08-30', 121)
        AND [DateEntered]  < CONVERT(datetime, '2011-08-31', 121)
);

Query Plan

Porovnejte tento plán s plánem vytvořeným, když je neseskupený index vynucen pomocí nápovědy:

SELECT TOP (1000) 
    * 
FROM [dbo].[Heartbeats] WITH (INDEX(CommonQueryIndex))
WHERE 
    [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

Forced Index Hint Plan

Plány jsou v zásadě stejné (Key Lookup není nic jiného než hledání v seskupeném indexu). Obě plánové formuláře budou provádět pouze jedno hledání na neskupovaném indexu a maximálně 1000 vyhledávání do seskupeného indexu.

Důležitý rozdíl je v poloze Top operátora. Nahoře umístěné mezi dvěma vyhledáváními optimalizátor neumožňuje, aby optimalizátor nahradil dvě operace vyhledávání logicky ekvivalentním skenováním seskupeného indexu. Optimalizátor pracuje tak, že nahrazuje části logického plánu ekvivalentními relačními operacemi. Nahoru není relační operátor, takže přepis zabraňuje transformaci na skenování seskupeného indexu. Pokud by optimalizátor dokázal přemístit hlavní operátor, stále by upřednostňoval skenování před vyhledáváním + hledat kvůli způsobu odhadu nákladů.

Náklady na skenování a vyhledávání

Na velmi vysoké úrovni je nákladový model optimalizátoru pro skenování a vyhledávání poměrně jednoduchý: odhaduje, že 320 náhodných vyhledávání náklady stejné jako čtení 1350 stránek při skenování. To pravděpodobně nemá malou podobnost s hardwarovými schopnostmi jakéhokoli konkrétního moderního I/O systému, ale funguje přiměřeně dobře jako praktický model.

Model také vytváří řadu zjednodušujících předpokladů, přičemž hlavní je, že se předpokládá, že každý dotaz začíná bez dat nebo indexových stránek již v mezipaměti. Důsledkem je, že každý I/O bude mít za následek fyzický I/O - i když v praxi to tak bude jen zřídka. I při studené mezipaměti předběžné načítání a čtení předem znamená, že potřebné stránky jsou ve skutečnosti pravděpodobně v paměti v době, kdy je procesor dotazu potřebuje.

Další úvahou je, že první požadavek na řádek, který není v paměti, způsobí, že celá stránka bude načtena z disku. Následným požadavkům na řádky na stejné stránce pravděpodobně nevznikne fyzický vstup/výstup. Model kalkulace obsahuje logiku, aby bylo možné vzít v úvahu určité účinky, jako je tento, ale není dokonalý.

Všechny tyto věci (a další) znamenají, že optimalizátor má sklon přepnout na skenování dříve, než by pravděpodobně mělo. Náhodné I/O jsou pouze „mnohem dražší“ než „sekvenční“ I/O, pokud dojde k fyzické operaci - přístup k stránkám v paměti je opravdu velmi rychlý. Dokonce i tam, kde je vyžadováno fyzické čtení, nemusí skenování v důsledku fragmentace vůbec vyústit v sekvenční čtení a vyhledávání může být uspořádáno tak, že vzorek je v podstatě sekvenční. K tomu přidejte měnící se výkonovou charakteristiku moderních I/O systémů (zejména polovodičových) a celá věc začíná vypadat velmi nejistě.

Řádkové cíle

Přítomnost top operátora v plánu modifikuje přístup k kalkulaci. Optimalizátor je dostatečně chytrý, aby věděl, že nalezení 1000 řádků pomocí kontroly pravděpodobně nebude vyžadovat prohledávání celého seskupeného indexu - může se zastavit, jakmile bude nalezeno 1000 řádků. Nastavuje „řádkový cíl“ 1000 řádků u nejvyššího operátora a používá statistické informace, aby odtamtud odhadl, kolik řádků očekává od zdroje řádků (v tomto případě skenování). O podrobnostech tohoto výpočtu jsem psal zde .

Obrázky v této odpovědi byly vytvořeny pomocí SQL Sentry Plan Explorer .

20
Paul White 9