非常に詳細な質問をおApびします。問題を再現するための完全なデータセットを生成するクエリを含め、32コアマシンでSQL Server 2012を実行しています。ただし、これはSQL Server 2012に固有のものではないと思い、この特定の例ではMAXDOPを10に強制しました。
同じパーティション構成を使用してパーティション化された2つのテーブルがあります。パーティショニングに使用される列でそれらを結合すると、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)