it-swarm-eu.dev

Optimalizace plánů pomocí čteček XML

Provedení dotaz odtud pro vytažení událostí zablokování z výchozí relace rozšířených událostí

SELECT CAST (
    REPLACE (
        REPLACE (
            XEventData.XEvent.value ('(data/value)[1]', 'varchar(max)'),
            '<victim-list>', '<deadlock><victim-list>'),
        '<process-list>', '</victim-list><process-list>')
    AS XML) AS DeadlockGraph
FROM (SELECT CAST (target_data AS XML) AS TargetData
    FROM sys.dm_xe_session_targets st
    JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address
    WHERE [name] = 'system_health') AS Data
CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
    WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report';

dokončení mého stroje trvá asi 20 minut. Hlášené statistiky jsou

Table 'Worktable'. Scan count 0, logical reads 68121, physical reads 0, read-ahead reads 0, 
         lob logical reads 25674576, lob physical reads 0, lob read-ahead reads 4332386.

 SQL Server Execution Times:
   CPU time = 1241269 ms,  elapsed time = 1244082 ms.

XML s pomalým plánem

Parallel

Pokud odstraním klauzuli WHERE, vyplní se za méně než sekundu a vrací 3 782 řádků.

Podobně, když přidám OPTION (MAXDOP 1) k původnímu dotazu, který také věci zrychlí, protože statistiky nyní vykazují výrazně méně lobů.

Table 'Worktable'. Scan count 0, logical reads 15, physical reads 0, read-ahead reads 0,
                lob logical reads 6767, lob physical reads 0, lob read-ahead reads 6076.

 SQL Server Execution Times:
   CPU time = 639 ms,  elapsed time = 693 ms.

Rychlejší plán XML

Serial

Takže moje otázka zní

Může mi někdo vysvětlit, co se děje? Proč je původní plán tak katastroficky horší a existuje spolehlivý způsob, jak se tomuto problému vyhnout?

Sčítání:

Zjistil jsem také, že změnou dotazu na INNER HASH JOIN Se věci do jisté míry zlepší (ale stále to trvá> 3 minuty), protože výsledky DMV jsou tak malé, pochybuji, že je zodpovědný samotný typ spojení a něco předpokládám jinak se muselo změnit. Statistiky za to

Table 'Worktable'. Scan count 0, logical reads 30294, physical reads 0, read-ahead reads 0, 
          lob logical reads 10741863, lob physical reads 0, lob read-ahead reads 4361042.

 SQL Server Execution Times:
   CPU time = 200914 ms,  elapsed time = 203614 ms.

(A plán)

Po vyplnění rozšířené události ring ring buffer (DATALENGTH z XML byl 4 880 045 bajtů a obsahoval 1 448 událostí.) A testování zkrácené verze původního dotazu s a bez MAXDOP nápověda.

SELECT COUNT(*)
FROM   (SELECT CAST (target_data AS XML) AS TargetData
        FROM   sys.dm_xe_session_targets st
               JOIN sys.dm_xe_sessions s
                 ON s.address = st.event_session_address
        WHERE  [name] = 'system_health') AS Data
       CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
WHERE  XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report'

SELECT*
FROM   sys.dm_db_task_space_usage
WHERE  session_id = @@SPID 

Následující výsledky

+-------------------------------------+------+----------+
|                                     | Fast |   Slow   |
+-------------------------------------+------+----------+
| internal_objects_alloc_page_count   |  616 |  1761272 |
| internal_objects_dealloc_page_count |  616 |  1761272 |
| elapsed time (ms)                   |  428 |   398481 |
| lob logical reads                   | 8390 | 12784196 |
+-------------------------------------+------+----------+

Existuje jasný rozdíl v alokacích tempdb s rychlejším zobrazením stránek 616, Které byly přiděleny a rozdány. Toto je stejné množství stránek, které se používá, když se XML vloží také do proměnné.

U pomalého plánu jsou tyto počty alokací stránek v milionech. Dotazování dm_db_task_space_usage Během dotazu ukazuje, že se zdá, že neustále přiřazuje a rozdává stránky v tempdb, kdykoli je přiděleno kdykoli mezi 1 800 a 3 000 stránek.

35
Martin Smith

Důvod rozdílu ve výkonu spočívá v tom, jak jsou ve výkonném modulu zpracovávány skalární výrazy. V tomto případě je vyjádření zájmu:

[Expr1000] = CONVERT(xml,DM_XE_SESSION_TARGETS.[target_data],0)

Tento výraz výrazu je definován operátorem Compute Scalar (uzel 11 v sériovém plánu, uzel 13 v paralelním plánu). Výpočetní skalární operátory se liší od ostatních operátorů (SQL Server 2005 a dále) v tom, že výrazy, které definují, jsou nemusí být nutně vyhodnoceny v poloze, ve které se objeví ve viditelném plánu provádění; vyhodnocení může být odloženo, dokud nebude výsledek výpočtu vyžadován pozdějším operátorem.

V tomto dotazu target_data string je obvykle velký, takže je převod z řetězce na XML drahý. U pomalých plánů je převod na řetězec XML prováděn pokaždé později, když operátor vyžaduje výsledek Expr1000 je odskočit.

K opětovnému navázání dochází na vnitřní straně spojení vnořených smyček, když se změní korelovaný parametr (vnější reference). Expr1000 je vnější reference pro většinu vnořených smyčkových spojení v tomto plánu provádění. Na výraz se několikrát odkazuje několik čteček XML, agregátů Stream a spouštěcího filtru. V závislosti na velikosti XML, kolikrát je řetězec převeden na XML může snadno počítat v milionech.

Zásobníky hovorů níže ukazují příklady target_data řetězec převáděný na XML (ConvertStringToXMLForES - kde ES je expresní služba ):

Start-up Filter

Start-up Filter call stack

XML Reader (interně TVF Stream)

TVF Stream call stack

Agregát proudu

Stream Aggregate call stack

Převod řetězce na XML pokaždé, když některý z těchto operátorů rebind vysvětlí vysvětluje rozdíl výkonu pozorovaný u plánů vnořených smyček. To je bez ohledu na to, zda je paralelismus používán či nikoliv. Stává se tak, že optimalizátor vybere spojení hash, když MAXDOP 1 nápověda je specifikována. Pokud MAXDOP 1, LOOP JOIN je specifikováno, výkon je slabý stejně jako u výchozího paralelního plánu (kde optimalizátor vybere vnořené smyčky).

Jak se zvyšuje výkon s připojením hashů, závisí na tom, zda Expr1000 se objeví na straně sestavení nebo sondy operátora. Následující dotaz vyhledá výraz na straně sondy:

SELECT CAST (
    REPLACE (
        REPLACE (
            XEventData.XEvent.value ('(data/value)[1]', 'varchar(max)'),
            '<victim-list>', '<deadlock><victim-list>'),
        '<process-list>', '</victim-list><process-list>')
    AS XML) AS DeadlockGraph
FROM (SELECT CAST (target_data AS XML) AS TargetData
    FROM sys.dm_xe_sessions s
    INNER HASH JOIN sys.dm_xe_session_targets st ON s.address = st.event_session_address
    WHERE [name] = 'system_health') AS Data
CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report';

Vrátil jsem písemné pořadí spojení od verze zobrazené v otázce, protože se připojuji k radám (INNER HASH JOIN výše) vynutí také pořadí celého dotazu, jako by FORCE ORDER bylo uvedeno. Obrácení je nutné k zajištění Expr1000 se objeví na straně sondy. Zajímavou součástí realizačního plánu je:

hint 1

S výrazem definovaným na straně sondy je hodnota uložena do mezipaměti:

Hash Cache

Vyhodnocení Expr1000 je stále odloženo, dokud první operátor nepotřebuje hodnotu (spouštěcí filtr ve stopě zásobníku výše), ale vypočítaná hodnota je uložena do mezipaměti (CValHashCachedSwitch) a znovu použita pro pozdější volání čtečkami XML a agregacemi streamů . Sledování zásobníku níže ukazuje příklad hodnoty mezipaměti, která je znovu použita XML čtečkou.

Cache reuse

Když je pořadí spojení vynuceno tak, že definice Expr1000 nastává na straně buildu hash join, situace je jiná:

SELECT CAST (
    REPLACE (
        REPLACE (
            XEventData.XEvent.value ('(data/value)[1]', 'varchar(max)'),
            '<victim-list>', '<deadlock><victim-list>'),
        '<process-list>', '</victim-list><process-list>')
    AS XML) AS DeadlockGraph
FROM (SELECT CAST (target_data AS XML) AS TargetData
    FROM sys.dm_xe_session_targets st 
    INNER HASH JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address
    WHERE [name] = 'system_health') AS Data
CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report'

Hash 2

Spojení hash čte jeho vstup sestavení kompletně postavit hash tabulku před tím, než začne zkoumat shody. Výsledkem je, že musíme uložit všechny hodnoty, nejen ty, na které se zpracovává vlákno ze strany sondy plánu. Spojení hash proto používá pracovní tabulku tempdb k uložení dat XML a každý přístup k výsledku Expr1000 novějšími operátory vyžaduje nákladnou cestu do tempdb:

Slow access

Následující obrázek zobrazuje další podrobnosti o cestě pomalého přístupu:

Slow details

Pokud je sloučení sloučení vynuceno, jsou vstupní řádky tříděny (operace blokování, stejně jako vstup sestavení do spojení hash), což má za následek podobné uspořádání, kde je vyžadován pomalý přístup prostřednictvím pracovního stolu tempdb kvůli optimalizaci řazení velikost dat.

Plány, které manipulují s velkými datovými položkami, mohou být problematické ze všech důvodů, které nejsou zřejmé z plánu provádění. Použití hash join (s výrazem na správném vstupu) není dobrým řešením. Spoléhá se na nezdokumentované vnitřní chování bez záruky, že bude fungovat příští týden stejným způsobem, nebo na trochu odlišný dotaz.

Zpráva je, že manipulace XML může být dnes pro optimalizaci komplikovaná. Zápis XML do proměnné nebo dočasné tabulky před skartováním je mnohem spolehlivějším řešením než cokoli výše uvedeného. Jedním ze způsobů, jak toho dosáhnout, je:

DECLARE @data xml =
        CONVERT
        (
            xml,
            (
            SELECT TOP (1)
                dxst.target_data
            FROM sys.dm_xe_sessions AS dxs 
            JOIN sys.dm_xe_session_targets AS dxst ON
                dxst.event_session_address = dxs.[address]
            WHERE 
                dxs.name = N'system_health'
                AND dxst.target_name = N'ring_buffer'
            )
        )

SELECT XEventData.XEvent.value('(data/value)[1]', 'varchar(max)')
FROM @data.nodes ('./RingBufferTarget/event[@name eq "xml_deadlock_report"]') AS XEventData (XEvent)
WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report';

Nakonec chci jen přidat Martinovu velmi pěknou grafiku z komentářů níže:

Martin's graphic

37
Paul White 9

To je kód z mého článku původně zveřejněného zde:

http://www.sqlservercentral.com/articles/deadlock/65658/

Pokud si přečtete komentáře, najdete několik alternativ, které nemají problémy s výkonem, se kterými se setkáváte, jeden s použitím modifikace původního dotazu a druhý s použitím proměnné k držení XML před zpracováním, které vyjde lepší. (viz moje komentáře k Strana 2) XML z DMV může být pomalé zpracování, stejně jako může analyzovat XML z DMF pro cílový soubor, což je často lépe dosáhnout načtením dat do dočasné tabulky a následným zpracováním. XML v SQL je pomalé ve srovnání s použitím věcí jako .NET nebo SQLCLR.

10
Jonathan Kehayias