Оптимізація планів за допомогою зчитувачів XML


34

Виконання запиту звідси, щоб витягнути події з глухого кута поза сеансом розширених подій за замовчуванням

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';

на моїй машині потрібно 20 хвилин. Звіт про статистику є

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

Паралельний

Якщо я видалю WHEREпункт, він завершиться менш ніж за секунду, повертаючи 3782 рядки.

Аналогічно, якщо я додаю OPTION (MAXDOP 1)до оригінального запиту, який також прискорює роботу зі статистикою, яка тепер показує значно меншу кількість читань лобу.

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.

Швидший план XML

Серійний

Отже, моє запитання

Хтось може пояснити, що відбувається? Чому оригінальний план настільки катастрофічно гірший і чи існує якийсь надійний спосіб уникнути проблеми?

Доповнення:

Я також виявив, що зміна запиту в INNER HASH JOINдеякій мірі покращує речі (але це все-таки займає> 3 хвилини), оскільки результати DMV настільки малі, я сумніваюся, що сам тип Join несе відповідальність, і припускаю, що щось інше повинно змінитися. Статистика для цього

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.

(І план)

Після заповнення розширеного буфера кільця подій ( DATALENGTHз XML4,880,045 байт і він містив 1448 подій.) Та тестування скороченої версії вихідного запиту з MAXDOPпідказкою і без неї .

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 

Дав такі результати

+-------------------------------------+------+----------+
|                                     | 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 |
+-------------------------------------+------+----------+

Існує чітка відмінність у розміщенні tempdb у більш швидкому 616розміщенні та розміщенні сторінок, що показують . Це стільки ж сторінок, що використовуються, коли XML також вводиться в змінну.

Для повільного плану, кількість цих сторінок розподіляється на мільйони. Опитування dm_db_task_space_usageпід час запуску запиту показує, що, здається, він постійно розподіляє та tempdbрозміщує сторінки на будь-яких місцях від 1800 до 3000 сторінок, виділених у будь-який час.


Ви можете перемістити WHEREпункт у вираз XQuery; логіка не повинна бути видалена для того , щоб йти швидко: TargetData.nodes ('RingBufferTarget[1]/event[@name = "xml_deadlock_report"]'). Це означає, що я не знаю внутрішніх даних XML досить добре, щоб відповісти на поставлене вами питання.
Джон Сейгель

Підказка @SQLPoolBoy для вас Мартін ... він запропонував переглянути коментарі тут, де він має більш ефективні пропозиції (вони базуються на статті джерела для коду вище ).
Аарон Бертран

Відповіді:


36

Причина різниці в продуктивності полягає в тому, як скаральні вирази обробляються в механізмі виконання. У цьому випадку прояв інтересу:

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

Ця мітка вираження визначається оператором Compute Scalar (вузол 11 в послідовному плані, вузол 13 в паралельному плані). Оператори Compute Scalar відрізняються від інших операторів (SQL Server 2005 і далі) тим, що вираження, які вони визначають, не обов'язково оцінюються в тому місці, яке вони відображаються у видимому плані виконання; Оцінка може бути відкладена до тих пір, поки результат обчислення не вимагає більш пізній оператор.

У цьому запиті target_dataрядок, як правило, великий, що робить перетворення з рядка в XMLдороге. У повільних планах рядок для XMLперетворення виконується щоразу, коли наступний оператор, який вимагає результату Expr1000, відновлює.

Відтворення відбувається на внутрішній стороні вкладеного вкладеного циклу, коли змінюється корельований параметр (зовнішня посилання). Expr1000є зовнішньою посиланням для більшості вкладених циклів, що приєднуються до цього плану виконання. Вираз посилається кілька разів на декілька читачів XML, як агрегати потоку, так і фільтр запуску. Залежно від розміру XML, кількість перетворень рядка XMLможе легко перераховуватися в мільйонах.

Нижче наведені приклади стеків викликів показують приклади target_dataрядка, який перетворюється в XML( ConvertStringToXMLForES- де ES - служба виразів ):

Фільтр пуску

Старт виклику стека

XML-зчитувач (передача TVF внутрішньо)

TVF Stream стек дзвінків

Потокова сукупність

Потік сукупного виклику стеків

Перетворення рядка XMLщоразу, коли будь-який з цих операторів відновлює, пояснює різницю продуктивності, що спостерігається з вкладеними планами циклів. Це незалежно від того, використовується паралелізм чи ні. Так буває, що оптимізатор вибирає хеш-з'єднання, коли MAXDOP 1вказується підказка. Якщо MAXDOP 1, LOOP JOINвказано, продуктивність погана, як і у паралельному плані за замовчуванням (де оптимізатор вибирає вкладені петлі).

Скільки збільшується продуктивність при хеш-з'єднанні, залежить від того, Expr1000відображається на збірці або зонді оператора. Наступний запит визначає вираз на стороні зонда:

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';

Я змінив письмове розпорядження приєднань до версії, показаної у запитанні, тому що підказки про приєднання ( INNER HASH JOINвище) також змушують замовити весь запит, як ніби FORCE ORDERбуло вказано. Поворот потрібен для того, щоб переконатися, що Expr1000з'являється на стороні зонда. Цікава частина плану виконання:

підказка 1

З виразом, визначеним на стороні зонда, значення кешується:

Хеш-кеш

Оцінювання Expr1000досі відкладається, поки першому оператору не знадобиться значення (фільтр запуску у сліді стека вище), але обчислене значення кешується ( CValHashCachedSwitch) і повторно використовується для пізніших викликів зчитувачів XML і потокових агрегатів. Слідок стека нижче показує приклад кешованого значення, яке повторно використовується XML-зчитувачем.

Повторне використання кешу

Коли замовлення на приєднання вимушене таким чином, що визначення існування Expr1000відбувається на стороні збірки хеш-з'єднання, ситуація відрізняється:

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'

Хеш 2

Хеш-з'єднання повністю читає свій вхід для складання, щоб створити хеш-таблицю, перш ніж вона починає зондування для відповідностей. Як результат, нам доводиться зберігати всі значення, а не лише одне на кожну нитку, над якою працює зондальний план. Тому хеш-з'єднання використовує tempdbробочу таблицю для зберігання XMLданих, і кожен доступ до результату Expr1000пізніших операторів вимагає дорогої поїздки до tempdb:

Повільний доступ

Далі показано більше деталей про повільний шлях доступу:

Повільні деталі

Якщо вимушене об'єднання об'єднань, рядки введення сортуються (операція блокування, подібно до введення збірки до хеш-з'єднання), що призводить до подібного розташування, коли необхідний повільний доступ через tempdbробочий стіл, оптимізований для сортування, через розмір даних.

Плани, що маніпулюють великими елементами даних, можуть бути проблематичними з різного роду причин, які не видно з плану виконання. Використання хеш-з'єднання (з виразом на правильному введенні) - не вдале рішення. Він покладається на незадокументовану внутрішню поведінку, не маючи гарантій, що буде працювати так само на наступному тижні або на дещо інший запит.

Повідомлення полягає в тому, що XMLманіпуляція може бути складними речами для оптимізації сьогодні. Запис у XMLзмінну чи тимчасову таблицю перед подрібненням - набагато більш твердий спосіб вирішення, ніж усе, що показано вище. Один із способів зробити це:

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';

Нарешті, я просто хочу додати дуже приємну графіку Мартіна з коментарів нижче:

Графіка Мартіна


Чудове пояснення, дякую. Я також читав вашу статтю про обчислення скалярів, але не ставлю два і два разом.
Мартін Сміт

3
Я, мабуть, щось зіпсував зі своєю спробою профілінгу вчора (можливо, заплутав повільні та швидкі сліди!). Я переробив це сьогодні і, звичайно, це просто показує те, що ви вже сказали.
Мартін Сміт

2
Так, скріншот - це звіт про перегляд дерева дзвінків від профілера Visual Studio 2012 . Я думаю, що назви методів виглядають набагато чіткішими у ваших результатах, хоча без таємничих рядків, таких як @@IEAAXPEA_Kз'явлення.
Мартін Сміт

10

Це код з моєї статті, спочатку розміщеної тут:

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

Якщо ви прочитаєте коментарі, ви знайдете пару альтернатив, які не мають проблем із продуктивністю, які у вас виникають: одна використовує модифікацію цього оригінального запиту, а друга використовує змінну для зберігання XML, перш ніж обробляти її, яка працює. краще. (див. мої коментарі на Сторінці 2) XML з DMV може дуже повільно оброблятись, як і розбір XML з DMF для цільового файлу, який часто краще досягти, прочитавши дані спочатку в таблиці темпів, а потім обробляючи їх. XML у SQL повільний порівняно з використанням таких речей, як .NET або SQLCLR.


1
Спасибі! Це зробило трюк. Той, що не має змінної, що займає 600 мс і 6341, читає і зі змінною 303 msі 3249 lob reads. У 2012 році мені потрібно було також додати and target_name='ring_buffer'цю версію, оскільки, схоже, зараз є дві цілі. Я все ще намагаюся скласти уявний образ того, що саме він робить у 20-хвилинній версії.
Мартін Сміт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.