병렬 계획에서 부정확 한 '실제'행 수


17

이것은 순전히 학문적 인 질문이므로 문제를 일으키지 않으며 행동에 대한 설명을 듣고 싶습니다.

Itzik Ben-Gan 교차 결합 CTE 탈리 테이블 표준 문제를 살펴보십시오.

USE [master]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE FUNCTION [dbo].[TallyTable] 
(   
    @N INT
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN 
(
    WITH 
    E1(N) AS 
    (
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
    )                                       -- 1*10^1 or 10 rows
    , E2(N) AS (SELECT 1 FROM E1 a, E1 b)   -- 1*10^2 or 100 rows
    , E4(N) AS (SELECT 1 FROM E2 a, E2 b)   -- 1*10^4 or 10,000 rows
    , E8(N) AS (SELECT 1 FROM E4 a, E4 b)   -- 1*10^8 or 100,000,000 rows

    SELECT TOP (@N) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS N FROM E8 
)
GO

백만 개의 행 번호 테이블을 작성하는 조회를 발행하십시오.

SELECT
    COUNT(N)
FROM
    dbo.TallyTable(1000000) tt

이 쿼리에 대한 병렬 실행 계획을 살펴보십시오.

병렬 실행 계획

스트림 수집 연산자 이전의 '실제'행 수는 1,004,588입니다. 수집 스트림 연산자 후에 행 수는 1,000,000으로 예상됩니다. 낯선 사람은 여전히 ​​값이 일정하지 않으며 실행마다 다릅니다. COUNT의 결과는 항상 정확합니다.

병렬이 아닌 계획을 강제로 쿼리를 다시 발행하십시오.

SELECT
    COUNT(N)
FROM
    dbo.TallyTable(1000000) tt
OPTION (MAXDOP 1)

이번에는 모든 연산자가 올바른 '실제'행 수를 표시합니다.

비 병렬 실행 계획

나는 지금까지 2005SP3와 2008R2에서 이것을 시도했지만 두 결과 모두 동일합니다. 무엇이 이것을 일으킬 수 있는지에 대한 생각이 있습니까?

답변:


12

행은 한 번에 한 행이 아닌 생산자에서 소비자 스레드로 패킷 (CXPACKET-클래스 교환 패킷)의 내부 교환으로 전달됩니다. 교환 내부에는 일정한 양의 버퍼링이 있습니다. 또한 Gather Streams의 소비자 측에서 파이프 라인을 종료하라는 요청은 제어 패킷을 통해 생산자 스레드로 다시 전달되어야합니다. 예약 및 기타 내부 고려 사항은 병렬 계획에 항상 특정 '정지 거리'가 있음을 의미합니다.

결과적으로 하위 트리의 전체 잠재적 행 집합보다 실제로 필요한 행 수 차이가 적은 경우가 종종 있습니다. 이 경우 TOP은 실행을 '초기'에 가져옵니다.

추가 정보:


10

나는 이것에 대한 부분적인 설명을 가지고 있다고 생각하지만, 그것을 쓰러 뜨리거나 대안을 게시하십시오. @MartinSmith는 실행 계획에서 TOP의 효과를 강조함으로써 확실히 무언가에 있습니다.

간단히 말해 '실제 행 개수'는 연산자가 처리하는 행의 개수가 아니라 연산자의 GetNext () 메서드가 호출 된 횟수입니다.

BOL 에서 가져온 것 :

물리적 연산자는 초기화하고 데이터를 수집 한 후 닫습니다. 특히 물리 연산자는 다음 세 가지 메서드 호출에 응답 할 수 있습니다.

  • Init () : Init () 메소드는 물리 연산자가 스스로 초기화하고 필요한 데이터 구조를 설정하도록합니다. 물리적 연산자는 많은 Init () 호출을받을 수 있지만 일반적으로 물리적 연산자는 하나만받습니다.
  • GetNext () : GetNext () 메서드는 물리 연산자가 첫 번째 또는 그 이후의 데이터 행을 가져 오도록합니다. 실제 연산자는 0 개 또는 많은 GetNext () 호출을 수신 할 수 있습니다.
  • Close () : Close () 메서드는 물리 연산자가 일부 정리 작업을 수행하고 자체 종료되도록합니다. 실제 연산자는 하나의 Close () 호출 만받습니다.

GetNext () 메서드는 한 행의 데이터를 반환하고 호출 횟수는 SET STATISTICS PROFILE ON 또는 SET STATISTICS XML ON을 사용하여 생성 된 실행 계획 출력에서 ​​ActualRows로 나타납니다.

완전성을 위해 병렬 연산자에 대한 약간의 배경이 유용합니다. 작업은 재 파티션 스트림 또는 분배 스트림 연산자에 의해 병렬 계획으로 여러 스트림에 분배됩니다. 다음 네 가지 메커니즘 중 하나를 사용하여 스레드간에 행 또는 페이지를 분배합니다.

  • 해시 는 행의 열 해시를 기반으로 행을 분산시킵니다.
  • 라운드 로빈 은 루프의 스레드 목록을 반복하여 행을 분배합니다.
  • 브로드 캐스트 는 모든 페이지 또는 행을 모든 스레드에 분배
  • 수요 분할은 스캔에만 사용됩니다. 스레드가 스핀 업하고 운영자에게 데이터 페이지를 요청하고 처리 한 후 완료되면 추가 페이지를 요청합니다.

첫 번째 분배 스트림 연산자 (계획에서 가장 오른쪽에 있음)는 일정한 스캔에서 시작된 행에서 수요 분할을 사용합니다. 총 10 개의 '실제 행'에 대해 GetNext ()를 6, 4 및 0 번 호출하는 세 개의 스레드가 있습니다.

<RunTimeInformation>
       <RunTimeCountersPerThread Thread="2" ActualRows="6" ActualEndOfScans="1" ActualExecutions="1" />
       <RunTimeCountersPerThread Thread="1" ActualRows="4" ActualEndOfScans="1" ActualExecutions="1" />
       <RunTimeCountersPerThread Thread="0" ActualRows="0" ActualEndOfScans="0" ActualExecutions="0" />
 </RunTimeInformation>

다음 배포 연산자에는 다시 세 개의 스레드가 있으며 이번에는 총 100 개의 GetNext ()를 50, 50 및 0으로 호출합니다.

<RunTimeInformation>
    <RunTimeCountersPerThread Thread="2" ActualRows="50" ActualEndOfScans="1" ActualExecutions="1" />
    <RunTimeCountersPerThread Thread="1" ActualRows="50" ActualEndOfScans="1" ActualExecutions="1" />
    <RunTimeCountersPerThread Thread="0" ActualRows="0" ActualEndOfScans="0" ActualExecutions="0" />
</RunTimeInformation>

다음 병렬 연산자에서 원인과 설명이 나타날 수 있습니다.

<RunTimeInformation>
    <RunTimeCountersPerThread Thread="2" ActualRows="1" ActualEndOfScans="0" ActualExecutions="1" />
    <RunTimeCountersPerThread Thread="1" ActualRows="10" ActualEndOfScans="0" ActualExecutions="1" />
    <RunTimeCountersPerThread Thread="0" ActualRows="0" ActualEndOfScans="0" ActualExecutions="0" />
</RunTimeInformation>

이제 GetNext ()에 대한 11 번의 호출이 있는데 10 번이 나올 것으로 예상됩니다.

편집 : 2011-11-13

이 시점에서 갇혀, 나는에서 챕 답변을 습격했다 클러스터 된 인덱스 와 @MikeWalsh 친절 감독 @SQLKiwi 여기 .


7

1,004,588 내 테스트에서도 많이 자란 인물입니다.

나는 또한 아래의 다소 간단한 계획에 대해서도 이것을 본다.

WITH 
E1(N) AS 
(
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)                                       -- 1*10^1 or 10 rows
, E2(N) AS (SELECT 1 FROM E1 a, E1 b)   -- 1*10^2 or 100 rows
, E4(N) AS (SELECT 1 FROM E2 a, E2 b)   -- 1*10^4 or 10,000 rows
SELECT * INTO #E4 FROM E4;

WITH E8(N) AS (SELECT 1 FROM #E4 a, #E4 b),
Nums(N) AS (SELECT  TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) FROM E8 )
SELECT COUNT(N) FROM Nums

DROP TABLE #E4

계획

실행 계획에 대한 다른 관심 수치는

+----------------------------------+--------------+--------------+-----------------+
|                                  | Table Scan A | Table Scan B | Row Count Spool |
+----------------------------------+--------------+--------------+-----------------+
| Number Of Executions             | 2            |            2 |             101 |
| Actual Number Of Rows - Total    | 101          |        20000 |         1004588 |
| Actual Number Of Rows - Thread 0 | -            |              |                 |
| Actual Number Of Rows - Thread 1 | 95           |        10000 |          945253 |
| Actual Number Of Rows - Thread 2 | 6            |        10000 |           59335 |
| Actual Rebinds                   | 0            |            0 |               2 |
| Actual Rewinds                   | 0            |            0 |              99 |
+----------------------------------+--------------+--------------+-----------------+

내 생각은 작업이 병렬로 처리되고 있기 때문에 다른 작업이 백만 번째 행을 수집 스트림 연산자에 전달하여 추가 행이 처리 될 때 한 행의 비행 처리 행에 있다는 것입니다. 또한 이 기사 에서 행은 버퍼링 되어이 반복자에 배치로 전달되므로 처리되는 행 수가 TOP이벤트 의 사양에 정확하게 일치하지 않고 초과 할 가능성이 높습니다 .

편집하다

좀 더 자세히 살펴보면됩니다. 1,004,588위의 인용 된 행 수 보다 더 다양 해졌으므로 1,000 회 반복 루프에서 위의 쿼리를 실행하고 실제 실행 계획을 캡처했습니다. 평행도가 0 인 81 개의 결과를 무시하면 다음과 같은 수치가 나타납니다.

count       Table Scan A: Total Actual Row Spool - Total Actual Rows
----------- ------------------------------ ------------------------------
352         101                            1004588
323         102                            1004588
72          101                            1003565
37          101                            1002542
35          102                            1003565
29          101                            1001519
18          101                            1000496
13          102                            1002542
5           9964                           99634323
5           102                            1001519
4           9963                           99628185
3           10000                          100000000
3           9965                           99642507
2           9964                           99633300
2           9966                           99658875
2           9965                           99641484
1           9984                           99837989
1           102                            1000496
1           9964                           99637392
1           9968                           99671151
1           9966                           99656829
1           9972                           99714117
1           9963                           99629208
1           9985                           99847196
1           9967                           99665013
1           9965                           99644553
1           9963                           99623626
1           9965                           99647622
1           9966                           99654783
1           9963                           99625116

1,004,588이 가장 일반적인 결과이지만 3 번의 경우 최악의 경우가 발생했으며 100,000,000 개의 행이 처리 된 것을 볼 수 있습니다. 관찰 된 가장 좋은 사례는 1,000,496 개의 행 수로 19 번 발생했습니다.

재현 할 전체 스크립트 는이 답변의 개정 2 하단에 있습니다 (프로세서가 2 개 이상인 시스템에서 실행하는 경우 조정이 필요함).


1

문제는 여러 스트림이 스트림 사이에 행이 조각되는 방식에 따라 동일한 행을 처리 할 수 ​​있다는 사실에서 비롯된 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.