Выполнение запроса отсюда, чтобы извлечь тупиковые события из сеанса расширенных событий по умолчанию
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.
Если я удаляю 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.
Итак, мой вопрос
Кто-нибудь может объяснить, что происходит? Почему первоначальный план настолько катастрофически хуже и есть надежный способ избежать проблемы?
Дополнение:
Я также обнаружил, что изменение запроса до 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
из них XML
было 4 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, чтобы ответить на поставленный вами вопрос.Ответы:
Причина разницы в производительности заключается в том, как скалярные выражения обрабатываются в механизме выполнения. В этом случае выражение интереса:
Эта метка выражения определяется оператором Compute Scalar (узел 11 в последовательном плане, узел 13 в параллельном плане). Скалярные операторы Compute отличаются от других операторов (начиная с SQL Server 2005) тем, что определяемые ими выражения не обязательно оцениваются в той позиции, в которой они появляются в видимом плане выполнения; оценка может быть отложена до тех пор, пока последующий оператор не потребует результата вычисления.
В данном запросе
target_data
строка обычно велика, что делает преобразование из строки вXML
дорогое. В медленных планах строка дляXML
преобразования выполняется каждый раз, когда более поздний оператор, которому требуется результат,Expr1000
является отскоком.Перепривязка происходит на внутренней стороне соединения вложенных циклов, когда изменяется коррелированный параметр (внешняя ссылка).
Expr1000
является внешней ссылкой для большинства объединений вложенных циклов в этом плане выполнения. На выражение многократно ссылаются несколько XML-ридеров, как потоковых агрегатов, так и запускающий фильтр. В зависимости от размераXML
число, в которое преобразуется строка,XML
может легко исчисляться миллионами.Стеки вызовов ниже показывают примеры
target_data
преобразования строки вXML
(ConvertStringToXMLForES
- где ES - Служба выражений ):Фильтр запуска
XML Reader (внутренний поток TVF)
Поток Агрегат
Преобразование строки в
XML
каждый раз, когда любой из этих операторов выполняет повторное связывание, объясняет разницу в производительности, наблюдаемую с планами вложенных циклов. Это независимо от того, используется параллелизм или нет. Так уж получилось, что оптимизатор выбирает хеш-соединение, когдаMAXDOP 1
указана подсказка. ЕслиMAXDOP 1, LOOP JOIN
указано, производительность низкая, как и в случае параллельного плана по умолчанию (где оптимизатор выбирает вложенные циклы).Насколько производительность увеличивается при хэш-соединении, зависит от того,
Expr1000
появляется ли оператор на сборке или на стороне зонда. Следующий запрос находит выражение на стороне зонда:Я перевернул письменный порядок объединений по сравнению с версией, показанной в вопросе, потому что подсказки объединений (
INNER HASH JOIN
выше) также принудительно устанавливают порядок для всего запроса, как если быFORCE ORDER
он был указан. Реверс необходим для обеспеченияExpr1000
появления на стороне зонда. Интересная часть плана выполнения:С помощью выражения, определенного на стороне зонда, значение кэшируется:
Оценка по-
Expr1000
прежнему откладывается до тех пор, пока значение не понадобится первому оператору (фильтр запуска в приведенной выше трассировке стека), но вычисленное значение кэшируется (CValHashCachedSwitch
) и повторно используется для последующих вызовов программами чтения XML и потоковыми агрегатами. Трассировка стека ниже показывает пример повторного использования кэшированного значения XML Reader.Когда порядок соединения принудителен так, что определение
Expr1000
происходит на стороне компоновки хеш-соединения, ситуация другая:Хеш-соединение полностью считывает входные данные сборки, чтобы создать хеш-таблицу, прежде чем оно начнет проверять совпадения. В результате мы должны хранить все значения, а не только одно значение для каждого потока, над которым ведется работа со стороны зонда плана. Поэтому в хеш-соединении
tempdb
для храненияXML
данных используется рабочая таблица , и каждый доступ к результатамExpr1000
более поздних операторов требует дорогостоящей поездкиtempdb
:Ниже приведены дополнительные сведения о пути медленного доступа:
Если объединение слиянием принудительно, входные строки сортируются (операция блокировки, точно так же, как сборка ввода для хеш-объединения), что приводит к аналогичному расположению, где
tempdb
требуется медленный доступ через оптимизированную по сортировке рабочую таблицу из-за размера данных.Планы, которые манипулируют большими элементами данных, могут быть проблематичными по разным причинам, которые не очевидны из плана выполнения. Использование хеш-соединения (с выражением на правильном входе) не является хорошим решением. Он опирается на недокументированное внутреннее поведение без каких-либо гарантий, что он будет работать таким же образом на следующей неделе, или по немного другому запросу.
Сообщение состоит в том, что
XML
манипуляции могут быть хитрыми вещами, чтобы оптимизировать сегодня. Запись вXML
переменную или временную таблицу перед уничтожением является гораздо более надежным решением, чем все, что показано выше. Один из способов сделать это:Наконец, я просто хочу добавить очень приятную графику Мартина из комментариев ниже:
источник
@@IEAAXPEA_K
появление.Вот код из моей статьи, первоначально размещенной здесь:
http://www.sqlservercentral.com/articles/deadlock/65658/
Если вы прочтете комментарии, вы найдете пару альтернатив, у которых нет проблем с производительностью, с которыми вы столкнулись, одна использует модификацию этого исходного запроса, а другая - переменную для хранения XML перед его обработкой, которая сработает. лучше. (см. мои комментарии на стр. 2) XML из DMV может быть медленным для обработки, так же как и анализ XML из DMF для целевого файла, который часто лучше выполнить, сначала прочитав данные во временную таблицу, а затем обработав ее. XML в SQL медленнее по сравнению с использованием таких вещей, как .NET или SQLCLR.
источник
303 ms
и3249 lob reads
. В 2012 году мне нужно было также добавитьand target_name='ring_buffer'
эту версию, так как сейчас есть две цели. Я все еще пытаюсь понять, что именно он делает в 20-минутной версии.