매우 상세한 질문에 대해 사과드립니다. 문제를 재현하기위한 전체 데이터 세트를 생성하는 쿼리를 포함 시켰으며 32 코어 컴퓨터에서 SQL Server 2012를 실행하고 있습니다. 그러나 이것이 SQL Server 2012에만 해당되는 것은 아니며이 특정 예제에 대해 MAXDOP를 10으로 설정했습니다.
동일한 파티션 구성표를 사용하여 파티션 된 두 개의 테이블이 있습니다. 파티셔닝에 사용되는 열에서 이들을 함께 결합 할 때 SQL Server가 예상대로 병렬 병합 조인을 최적화 할 수 없으므로 대신 HASH JOIN을 사용하도록 선택했습니다. 이 특별한 경우에는 쿼리를 분할 기능을 기반으로 10 개의 분리 된 범위로 분할하고 각 쿼리를 SSMS에서 동시에 실행하여 훨씬 더 최적의 병렬 MERGE JOIN을 수동으로 시뮬레이션 할 수 있습니다. WAITFOR를 사용하여 정확하게 동시에 실행하면 모든 쿼리가 원래 병렬 HASH JOIN에서 사용한 총 시간의 ~ 40 %에 완료됩니다.
동등하게 분할 된 테이블의 경우 SQL Server가이 최적화를 자체적으로 수행 할 수있는 방법이 있습니까? SQL Server는 MERGE JOIN을 병렬로 만들기 위해 일반적으로 많은 오버 헤드가 발생할 수 있지만이 경우 최소한의 오버 헤드로 매우 자연스러운 샤딩 방법이있는 것 같습니다. 아마도 옵티마이 저가 아직 인식하기에 충분하지 않은 특수한 경우일까요?
이 문제를 재현하기 위해 단순화 된 데이터 세트를 설정하는 SQL은 다음과 같습니다.
/* Create the first test data table */
CREATE TABLE test_transaction_properties
( transactionID INT NOT NULL IDENTITY(1,1)
, prop1 INT NULL
, prop2 FLOAT NULL
)
/* Populate table with pseudo-random data (the specific data doesn't matter too much for this example) */
;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
)
, E2(N) AS (SELECT 1 FROM E1 a CROSS JOIN E1 b)
, E4(N) AS (SELECT 1 FROM E2 a CROSS JOIN E2 b)
, E8(N) AS (SELECT 1 FROM E4 a CROSS JOIN E4 b)
INSERT INTO test_transaction_properties WITH (TABLOCK) (prop1, prop2)
SELECT TOP 10000000 (ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) % 5) + 1 AS prop1
, ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) * rand() AS prop2
FROM E8
/* Create the second test data table */
CREATE TABLE test_transaction_item_detail
( transactionID INT NOT NULL
, productID INT NOT NULL
, sales FLOAT NULL
, units INT NULL
)
/* Populate the second table such that each transaction has one or more items
(again, the specific data doesn't matter too much for this example) */
INSERT INTO test_transaction_item_detail WITH (TABLOCK) (transactionID, productID, sales, units)
SELECT t.transactionID, p.productID, 100 AS sales, 1 AS units
FROM test_transaction_properties t
JOIN (
SELECT 1 as productRank, 1 as productId
UNION ALL SELECT 2 as productRank, 12 as productId
UNION ALL SELECT 3 as productRank, 123 as productId
UNION ALL SELECT 4 as productRank, 1234 as productId
UNION ALL SELECT 5 as productRank, 12345 as productId
) p
ON p.productRank <= t.prop1
/* Divides the transactions evenly into 10 partitions */
CREATE PARTITION FUNCTION [pf_test_transactionId] (INT)
AS RANGE RIGHT
FOR VALUES
(1,1000001,2000001,3000001,4000001,5000001,6000001,7000001,8000001,9000001)
CREATE PARTITION SCHEME [ps_test_transactionId]
AS PARTITION [pf_test_transactionId]
ALL TO ( [PRIMARY] )
/* Apply the same partition scheme to both test data tables */
ALTER TABLE test_transaction_properties
ADD CONSTRAINT PK_test_transaction_properties
PRIMARY KEY (transactionID)
ON ps_test_transactionId (transactionID)
ALTER TABLE test_transaction_item_detail
ADD CONSTRAINT PK_test_transaction_item_detail
PRIMARY KEY (transactionID, productID)
ON ps_test_transactionId (transactionID)
이제 우리는 차선의 쿼리를 재현 할 준비가되었습니다!
/* This query produces a HASH JOIN using 20 threads without the MAXDOP hint,
and the same behavior holds in that case.
For simplicity here, I have limited it to 10 threads. */
SELECT COUNT(*)
FROM test_transaction_item_detail i
JOIN test_transaction_properties t
ON t.transactionID = i.transactionID
OPTION (MAXDOP 10)
그러나 단일 스레드를 사용하여 각 파티션을 처리하면 (아래의 첫 번째 파티션의 예) 훨씬 효율적인 계획으로 이어집니다. 정확히 같은 순간에 10 개의 파티션 각각에 대해 아래와 같은 쿼리를 실행하여이를 테스트했으며, 10 개 모두 1 초 만에 완료되었습니다.
SELECT COUNT(*)
FROM test_transaction_item_detail i
INNER MERGE JOIN test_transaction_properties t
ON t.transactionID = i.transactionID
WHERE t.transactionID BETWEEN 1 AND 1000000
OPTION (MAXDOP 1)