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절을 제거하면 3,782 행을 반환하는 1 초 안에 완료됩니다.

마찬가지로 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 JOINDMV 결과가 너무 작기 때문에 쿼리를 변경 하면 상황이 어느 정도 향상되지만 여전히 3 분 이상 걸리는 것으로 나타났습니다 .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 |
+-------------------------------------+------+----------+

616페이지가 빠르게 할당되고 할당이 해제되는 것을 보여주는 tempdb 할당에는 분명한 차이가 있습니다. XML도 변수에 넣을 때 사용되는 페이지와 동일한 양입니다.

느린 계획의 경우 이러한 페이지 할당 수는 수백만입니다. dm_db_task_space_usage쿼리가 실행되는 동안 폴링 은 tempdb한 번에 1,800 ~ 3,000 페이지 사이의 페이지를 지속적으로 할당하고 할당을 해제하는 것으로 보입니다 .


WHERE절을 XQuery 표현식으로 이동할 수 있습니다 . 로직을 빨리 제거하기 위해 제거 할 필요는 없습니다 TargetData.nodes ('RingBufferTarget[1]/event[@name = "xml_deadlock_report"]'). 즉, 나는 당신이 제기 한 질문에 대답하기에 충분히 XML 내부를 모른다.
Jon Seigel

Martin을 위해 @SQLPoolBoy를 페이징하는 중 ... 그는 더 효율적인 제안이있는 곳 의 의견을 통해 제안했습니다 ( 위의 코드 에 대한 소스 기사를 기반으로 함 ).
Aaron Bertrand

답변:


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는 Expression Service 임).

시작 필터

시작 필터 호출 스택

XML 리더 (내부 TVF 스트림)

TVF 스트림 호출 스택

스트림 집계

스트림 집계 호출 스택

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

마지막으로 아래 주석에서 Martin의 멋진 그래픽을 추가하고 싶습니다.

마틴의 그래픽


좋은 설명 감사합니다. 나는 계산 스칼라에 대한 기사를 읽었지만 여기에 둘과 둘을 함께 넣지 않았습니다.
Martin Smith

3
어제 프로파일 링을 시도하면서 무언가를 엉망으로 만들었을 것입니다. 나는 오늘 그것을 다시하고 물론 그것은 당신이 이미 말한 것을 보여줍니다.
Martin Smith

2
예. 스크린 샷은 Visual Studio 2012 프로파일 러 의 호출 트리보기 보고서입니다 . 메서드 이름은 출력과 같은 신비한 문자열이 없지만 출력에서 ​​훨씬 명확하게 보인다고 생각합니다 @@IEAAXPEA_K.
Martin Smith

10

그것은 내 기사의 코드가 원래 여기에 게시되어 있습니다.

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

주석을 읽으면 발생하는 성능 문제가없는 몇 가지 대안, 원래 쿼리의 수정을 사용하는 다른 하나 및 변수를 사용하여 XML을 처리하기 전에 XML을 보유하는 대안을 찾을 수 있습니다. 보다 나은. (2 페이지에 대한 나의 의견 참조) DMV의 XML은 처리 속도가 느릴 수 있습니다. 파일 대상에 대해 DMF에서 XML을 구문 분석하여 데이터를 임시 테이블로 먼저 읽은 다음 처리하는 것이 더 좋습니다. .NET 또는 SQLCLR과 같은 것을 사용하는 것에 비해 SQL의 XML이 느립니다.


1
감사! 그 트릭을했다. 600ms와 6341을 사용하는 변수가없는 변수는 변수 303 ms3249 lob reads. 2012 년 and target_name='ring_buffer'에는 현재 두 개의 대상이있는 것처럼 해당 버전에 추가 해야했습니다. 나는 아직도 20 분 버전에서 정확히 무엇을하고 있는지 정신 이미지를 얻으려고 노력하고 있습니다.
Martin Smith
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.