SQL Server ไม่ปรับการรวมการผสานแบบขนานบนตารางที่แบ่งพาร์ติชันอย่างเท่าเทียมกันสองตาราง


21

ขออภัยล่วงหน้าสำหรับคำถามที่ละเอียดมาก ฉันได้รวมคิวรี่เพื่อสร้างชุดข้อมูลแบบเต็มสำหรับการทำซ้ำปัญหาและฉันใช้ SQL Server 2012 บนเครื่อง 32-core อย่างไรก็ตามฉันไม่คิดว่านี่เป็นเฉพาะของ SQL Server 2012 และฉันได้บังคับ MAXDOP เป็น 10 สำหรับตัวอย่างนี้โดยเฉพาะ

ฉันมีสองตารางที่แบ่งพาร์ติชันโดยใช้ชุดรูปแบบพาร์ติชันเดียวกัน เมื่อรวมพวกเขาเข้าด้วยกันในคอลัมน์ที่ใช้สำหรับการแบ่งพาร์ติชันฉันสังเกตว่า SQL Server ไม่สามารถเพิ่มประสิทธิภาพการรวมแบบขนานได้มากเท่าที่คาดไว้และเลือกที่จะใช้ HASH JOIN แทน ในกรณีพิเศษนี้ฉันสามารถจำลอง MERGE JOIN ที่เหมาะสมกว่าด้วยตนเองโดยแบ่งแบบสอบถามออกเป็น 10 ช่วงแยกจากกันตามฟังก์ชันพาร์ติชันและเรียกใช้แบบสอบถามแต่ละชุดใน SSMS พร้อมกัน การใช้ WAITFOR เพื่อเรียกใช้ทั้งหมดในเวลาเดียวกันอย่างแม่นยำผลลัพธ์คือแบบสอบถามทั้งหมดทำให้เสร็จสมบูรณ์ใน ~ 40% ของเวลาทั้งหมดที่ใช้โดย HASH JOIN ขนานแบบขนานเดิม

มีวิธีการรับ 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)

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

คำตอบ:


18

คุณถูกต้องที่เครื่องมือเพิ่มประสิทธิภาพ SQL Server ไม่ต้องการสร้างMERGEแผนการเข้าร่วมแบบขนาน(มีค่าใช้จ่ายทางเลือกนี้ค่อนข้างสูง) การขนานMERGEต้องมีการแบ่งพาร์ติชั่นการแลกเปลี่ยนใหม่ทั้งอินพุตเข้าร่วมและที่สำคัญกว่านั้นต้องการให้ลำดับแถวถูกเก็บรักษาข้ามการแลกเปลี่ยนเหล่านั้น

Parallelism มีประสิทธิภาพมากที่สุดเมื่อแต่ละเธรดสามารถรันได้อย่างอิสระ การเก็บรักษาคำสั่งมักจะนำไปสู่การประสานบ่อยครั้งและในที่สุดอาจทำให้การแลกเปลี่ยนหกtempdbเพื่อแก้ไขเงื่อนไขการหยุดชะงักภายในแบบสอบถาม

ปัญหาเหล่านี้สามารถหลีกเลี่ยงได้โดยการเรียกใช้แบบสอบถามทั้งหมดหลายอินสแตนซ์ในแต่ละเธรดแต่ละเธรดแต่ละเธรดที่ประมวลผลช่วงข้อมูลพิเศษ นี่ไม่ใช่กลยุทธ์ที่เครื่องมือเพิ่มประสิทธิภาพพิจารณาโดยกำเนิด ตามที่เป็นอยู่ SQL Server รุ่นดั้งเดิมสำหรับ parallelism แบ่งแบบสอบถามที่การแลกเปลี่ยนและเรียกใช้เซ็กเมนต์แผนที่เกิดขึ้นโดยแยกเหล่านั้นบนหลายเธรด

มีวิธีในการดำเนินการตามแผนแบบสอบถามทั้งหมดในหลายเธรดในช่วงชุดข้อมูลพิเศษ แต่ต้องใช้เล่ห์เหลี่ยมที่ทุกคนไม่พอใจ (และ Microsoft จะไม่สนับสนุนหรือรับประกันว่าจะทำงานในอนาคต) วิธีหนึ่งดังกล่าวคือการวนซ้ำพาร์ติชันของตารางที่แบ่งพาร์ติชันและให้แต่ละเธรดทำงานการสร้างผลรวมย่อย ผลลัพธ์คือSUMจำนวนแถวที่ส่งคืนโดยแต่ละเธรดอิสระ:

การรับหมายเลขพาร์ติชันนั้นง่ายพอจากเมตาดาต้า:

DECLARE @P AS TABLE
(
    partition_number integer PRIMARY KEY
);

INSERT @P (partition_number)
SELECT
    p.partition_number
FROM sys.partitions AS p 
WHERE 
    p.[object_id] = OBJECT_ID(N'test_transaction_properties', N'U')
    AND p.index_id = 1;

จากนั้นเราจะใช้ตัวเลขเหล่านี้เพื่อเพิ่มความสัมพันธ์เข้าร่วม ( APPLY) และ$PARTITIONฟังก์ชั่นเพื่อ จำกัด แต่ละเธรดให้เป็นจำนวนพาร์ติชันปัจจุบัน:

SELECT
    row_count = SUM(Subtotals.cnt)
FROM @P AS p
CROSS APPLY
(
    SELECT
        cnt = COUNT_BIG(*)
    FROM dbo.test_transaction_item_detail AS i
    JOIN dbo.test_transaction_properties AS t ON
        t.transactionID = i.transactionID
    WHERE 
        $PARTITION.pf_test_transactionId(t.transactionID) = p.partition_number
        AND $PARTITION.pf_test_transactionId(i.transactionID) = p.partition_number
) AS SubTotals;

แสดงให้เห็นว่าแผนการสอบถามเข้าร่วมการดำเนินการสำหรับแต่ละแถวในตารางMERGE @Pคุณสมบัติการสแกนดัชนีแบบคลัสเตอร์ยืนยันว่ามีการประมวลผลพาร์ติชันเดียวเท่านั้นในการทำซ้ำแต่ละครั้ง:

ใช้แผนอนุกรม

น่าเสียดายที่ผลลัพธ์นี้ส่งผลให้เกิดการประมวลผลพาร์ติชันอนุกรมตามลำดับเท่านั้น ในชุดข้อมูลที่คุณระบุแล็ปท็อปแบบ 4-core (hyperthreaded ถึง 8) ของฉันจะส่งคืนผลลัพธ์ที่ถูกต้องภายใน7 วินาทีด้วยข้อมูลทั้งหมดในหน่วยความจำ

ในการรับMERGEแผนย่อยให้ทำงานพร้อมกันเราจำเป็นต้องใช้แผนคู่ขนานที่รหัสพาร์ติชันจะถูกกระจายไปทั่วเธรดที่มีอยู่ ( MAXDOP) และแต่ละMERGEแผนย่อยจะรันบนเธรดเดี่ยวโดยใช้ข้อมูลในพาร์ติชันเดียว น่าเสียดายที่เครื่องมือเพิ่มประสิทธิภาพมักจะตัดสินใจเทียบเคียงMERGEกับต้นทุนและไม่มีวิธีที่ชัดเจนในการบังคับแผนขนาน มีวิธีที่ไม่มีเอกสาร (และไม่สนับสนุน) โดยใช้ค่าสถานะการติดตาม 8649 :

SELECT
    row_count = SUM(Subtotals.cnt)
FROM @P AS p
CROSS APPLY
(
    SELECT
        cnt = COUNT_BIG(*)
    FROM dbo.test_transaction_item_detail AS i
    JOIN dbo.test_transaction_properties AS t ON
        t.transactionID = i.transactionID
    WHERE 
        $PARTITION.pf_test_transactionId(t.transactionID) = p.partition_number
        AND $PARTITION.pf_test_transactionId(i.transactionID) = p.partition_number
) AS SubTotals
OPTION (QUERYTRACEON 8649);

ตอนนี้แผนคิวรีแสดงหมายเลขพาร์ติชันจาก@Pการกระจายระหว่างเธรดบนพื้นฐานการปัดเศษ แต่ละเธรดรันด้านในของลูปซ้อนกันสำหรับพาร์ติชันเดียวบรรลุเป้าหมายของเราในการประมวลผลข้อมูลที่แยกจากกันในเวลาเดียวกัน ตอนนี้ผลลัพธ์เดียวกันกลับมาภายใน3 วินาทีในไฮเปอร์คอร์ 8 คอร์ของฉันซึ่งมีการใช้งานทั้งแปดที่ 100%

ใช้คู่ขนาน

ฉันไม่แนะนำให้คุณใช้เทคนิคนี้อย่างจำเป็น - ดูคำเตือนก่อนหน้าของฉัน - แต่มันตอบคำถามของคุณ

ดูบทความของฉันการปรับปรุงตารางพาร์ทิชันเข้าร่วมประสิทธิภาพสำหรับรายละเอียดเพิ่มเติม

columnstore

เห็นว่าคุณใช้ SQL Server 2012 (และสมมติว่าเป็น Enterprise) คุณยังมีตัวเลือกในการใช้ดัชนีแบบคอลัมน์ สิ่งนี้แสดงให้เห็นถึงความเป็นไปได้ของการแฮชโหมดแบตช์ที่มีหน่วยความจำเพียงพอ

CREATE NONCLUSTERED COLUMNSTORE INDEX cs 
ON dbo.test_transaction_properties (transactionID);

CREATE NONCLUSTERED COLUMNSTORE INDEX cs 
ON dbo.test_transaction_item_detail (transactionID);

ด้วยดัชนีเหล่านี้ในสถานที่แบบสอบถาม ...

SELECT
    COUNT_BIG(*)
FROM dbo.test_transaction_properties AS ttp
JOIN dbo.test_transaction_item_detail AS ttid ON
    ttid.transactionID = ttp.transactionID;

... ส่งผลให้แผนการดำเนินการต่อไปนี้จากเครื่องมือเพิ่มประสิทธิภาพโดยไม่มีการใช้เล่ห์เหลี่ยมใด ๆ :

แผนของคอลัมน์ 1

แก้ไขผลลัพธ์ใน2 วินาทีแต่การกำจัดการประมวลผลแถวโหมดสำหรับการรวมสเกลาร์ช่วยได้มากขึ้น:

SELECT
    COUNT_BIG(*)
FROM dbo.test_transaction_properties AS ttp
JOIN dbo.test_transaction_item_detail AS ttid ON
    ttid.transactionID = ttp.transactionID
GROUP BY
    ttp.transactionID % 1;

การเพิ่มประสิทธิภาพของคอลัมน์

แบบสอบถามคอลัมน์เก็บเพิ่มประสิทธิภาพทำงานใน851ms

Geoff Patterson สร้างรายงานบั๊กPartition Wise Joinsแต่ถูกปิดเนื่องจากไม่สามารถแก้ไขได้


5
ประสบการณ์การเรียนรู้ที่ยอดเยี่ยมที่นี่ ขอขอบคุณ. +1
Edward Dortland

1
ขอบคุณพอล! ข้อมูลที่ยอดเยี่ยมที่นี่และแน่นอนตอบคำถามในรายละเอียด
Geoff Patterson

2
ขอบคุณพอล! ข้อมูลที่ยอดเยี่ยมที่นี่และแน่นอนตอบคำถามในรายละเอียด เราอยู่ในสภาพแวดล้อม SQL 2008/2012 แบบผสม แต่ฉันจะพิจารณาสำรวจร้านค้าคอลัมน์เพิ่มเติมสำหรับอนาคต แน่นอนว่าฉันยังต้องการให้ SQL Server สามารถใช้ประโยชน์จากการผสานแบบขนานได้อย่างมีประสิทธิภาพและความต้องการหน่วยความจำที่ต่ำกว่ามากในกรณีที่ใช้งานของฉัน :) ฉันได้ยื่นปัญหาการเชื่อมต่อต่อไปนี้ในกรณีที่ใครก็ตาม หรือโหวตให้กับมัน: connect.microsoft.com/SQLServer/feedback/details/759266/…
Geoff Patterson

0

วิธีที่จะทำให้เครื่องมือเพิ่มประสิทธิภาพทำงานในแบบที่คุณคิดว่าดีกว่าคือผ่านคำแนะนำการสืบค้น

ในกรณีนี้, OPTION (MERGE JOIN)

หรือคุณสามารถใช้ทั้งหมูและใช้ USE PLAN


ฉันจะไม่ทำสิ่งนี้เป็นการส่วนตัว: คำใบ้นั้นจะมีประโยชน์สำหรับปริมาณข้อมูลและการแจกจ่ายในปัจจุบันเท่านั้น
gbn

สิ่งที่น่าสนใจคือการใช้ OPTION (MERGE JOIN) ทำให้แผนการแย่ลงกว่าเดิม เครื่องมือเพิ่มประสิทธิภาพไม่ฉลาดพอที่จะตระหนักได้ว่าการรวมเข้าด้วยกันสามารถใช้งานร่วมกับฟังก์ชันพาร์ติชันได้และการใช้คำใบ้นี้จะทำให้คิวรีใช้เวลาประมาณ 46 วินาที น่าผิดหวังมาก!

@gbn ซึ่งน่าจะเป็นเหตุผลว่าทำไมเครื่องมือเพิ่มประสิทธิภาพจะเกิดขึ้นสำหรับการเข้าร่วมแฮชในครั้งแรก?

@gpatterson ช่างน่ารำคาญจริงๆ! :)

จะเกิดอะไรขึ้นถ้าคุณบังคับให้การแบ่งพาร์ติชันด้วยตนเองผ่านสหภาพ (เช่น: คิวรีสั้นของคุณรวมกับคิวรีอื่น ๆ ที่คล้ายกัน)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.