it-swarm-eu.dev

Proč je agregační dotaz výrazně rychlejší s klauzulí GROUP BY než bez jednoho?

Jsem jen zvědavý, proč agregovaný dotaz běží mnohem rychleji s klauzulí GROUP BY Než bez něj.

Tento dotaz například trvá téměř 10 sekund

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1

Zatímco tento trvá méně než sekundu

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
GROUP BY CreatedDate

V tomto případě je pouze jeden CreatedDate, takže seskupený dotaz vrací stejné výsledky jako neseskupený.

Všiml jsem si, že plány provádění obou dotazů jsou odlišné - Druhý dotaz používá paralelismus, zatímco první dotaz ne.

Query1 Execution PlanQuery2 Execution Plan

Je normální, že server SQL hodnotí agregovaný dotaz jinak, pokud nemá klauzuli GROUP BY? A existuje něco, co mohu udělat pro zlepšení výkonu prvního dotazu bez použití klauzule GROUP BY?

Upravit

Právě jsem se naučil, že mohu použít OPTION(querytraceon 8649) k nastavení režijních nákladů na paralelismus na 0, což způsobí, že dotaz použije nějaký paralelismus a sníží dobu běhu na 2 sekundy, i když nevím, jestli existují nějaké nevýhody pomocí této nápovědy k dotazu.

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
OPTION(querytraceon 8649)

enter image description here

Pořád bych dával přednost kratší době běhu, protože dotaz má naplnit hodnotu při výběru uživatele, takže by měl být v ideálním případě okamžitý jako seskupený dotaz. Právě teď balím svůj dotaz, ale vím, že to opravdu není ideální řešení.

SELECT Min(CreatedDate)
FROM
(
    SELECT Min(CreatedDate) as CreatedDate
    FROM MyTable WITH (NOLOCK) 
    WHERE SomeIndexedValue = 1
    GROUP BY CreatedDate
) as T

Upravit # 2

Reakce na Martinova žádost o další informace :

CreatedDate a SomeIndexedValue mají na sobě samostatný neexkluzivní index bez klastrů. SomeIndexedValue je ve skutečnosti pole varchar (7), i když ukládá číselnou hodnotu, která ukazuje na PK (int) jiné tabulky. Vztah mezi oběma tabulkami není v databázi definován. Neměl bych vůbec měnit databázi a mohu psát pouze dotazy na data dotazu.

MyTable obsahuje více než 3 miliony záznamů a každému záznamu je přiřazena skupina, do které patří (SomeIndexedValue). Skupiny mohou být od 1 do 200 000 záznamů

12
Rachel

Vypadá to, že pravděpodobně sleduje index na CreatedDate v pořadí od nejnižší k nejvyšší a provádí vyhledávání k vyhodnocení SomeIndexedValue = 1 predikát.

Když najde první odpovídající řádek, je hotovo, ale může to udělat mnohem více vyhledávání, než očekává, než takový řádek najde (předpokládá, že řádky odpovídající predikátu jsou náhodně rozděleny podle data.)

Viz moje odpověď zde pro podobný problém

Ideálním indexem pro tento dotaz by byl index na SomeIndexedValue, CreatedDate. Za předpokladu, že jej nemůžete přidat nebo alespoň vytvořit svůj existující index na SomeIndexedValue cover CreatedDate jako zahrnutý sloupec, můžete zkusit přepsat dotaz následujícím způsobem

SELECT MIN(DATEADD(DAY, 0, CreatedDate)) AS CreatedDate
FROM MyTable
WHERE SomeIndexedValue = 1

zabránit tomu, aby tento konkrétní plán použil.

8
Martin Smith

Můžeme ovládat MAXDOP a vybrat známou tabulku, např. AdventureWorks.Production.TransactionHistory?

Když opakuji vaše nastavení pomocí

--#1
SELECT MIN(TransactionDate) 
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
OPTION( MAXDOP 1) ;

--#2
SELECT MIN(TransactionDate) 
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO 

náklady jsou stejné.

Kromě toho bych očekával (aby se to stalo) hledání indexu vaší indexované hodnoty; v opačném případě pravděpodobně uvidíte shody hash namísto agregátů proudu. Výkon můžete vylepšit pomocí neskupovaných indexů, které zahrnují hodnoty, které agregujete, nebo můžete vytvořit indexované zobrazení, které definuje agregáty jako sloupce. Pak byste zasáhli seskupený index, který obsahuje vaše agregace, pomocí indexovaného ID. V SQL Standard můžete jednoduše vytvořit pohled a použít nápovědu WITH (NOEXPAND).

Příklad (nepoužívám MIN, protože to nefunguje v indexovaných pohledech):

USE AdventureWorks ;
GO

-- Covering Index with Include
CREATE INDEX IX_CoverAndInclude
ON Production.TransactionHistory(TransactionDate) 
INCLUDE (Quantity) ;
GO

-- Indexed View
CREATE VIEW dbo.SumofQtyByTransDate
    WITH SCHEMABINDING
AS
SELECT 
      TransactionDate 
    , COUNT_BIG(*) AS NumberOfTransactions
    , SUM(Quantity) AS TotalTransactions
FROM Production.TransactionHistory
GROUP BY TransactionDate ;
GO

CREATE UNIQUE CLUSTERED INDEX SumofAllChargesIndex 
    ON dbo.SumofQtyByTransDate (TransactionDate) ;  
GO


--#1
SELECT SUM(Quantity) 
FROM AdventureWorks.Production.TransactionHistory 
WITH (INDEX(0))
WHERE TransactionID = 100001 
OPTION( MAXDOP 1) ;

--#2
SELECT SUM(Quantity)  
FROM AdventureWorks.Production.TransactionHistory 
WITH (INDEX(IX_CoverAndInclude))
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO 

--#3
SELECT SUM(Quantity)  
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO
2
ooutwire

Dle mého názoru je příčinou problému to, že optimalizátor serveru SQL nevyhledává NEJLEPŠÍ plán, spíše hledá dobrý plán, jak je patrné ze skutečnosti, že po vynucení paralelizace byl dotaz proveden mnohem rychleji, něco, co měl optimalizátor neudělal to sám.

Viděl jsem také mnoho situací, kdy přepsání dotazu v jiném formátu bylo rozdílem mezi paralelizací (například ačkoli většina článků o SQL doporučuje parametrizaci, zjistil jsem, že způsobuje paralelní paralelní noy, i když jsou čichané parametry stejné jako jiné - paralelizovaný jeden nebo kombinace dvou dotazů s UNION ALL může někdy eliminovat paralelizaci).

Správným řešením by tedy mohlo být vyzkoušení různých způsobů psaní dotazu, například vyzkoušení dočasných tabulek, proměnných tabulek, cte, odvozených tabulek, parametrizace atd. A také hraní s indexy, indexovanými pohledy nebo filtrovanými indexy v aby získal nejlepší plán.

0
yoel halb