SQL Server không tối ưu hóa kết hợp song song trên hai bảng được phân vùng tương đương


21

Xin lỗi trước cho câu hỏi rất chi tiết. Tôi đã bao gồm các truy vấn để tạo một bộ dữ liệu đầy đủ để tái tạo sự cố và tôi đang chạy SQL Server 2012 trên máy 32 lõi. Tuy nhiên, tôi không nghĩ rằng điều này là dành riêng cho SQL Server 2012 và tôi đã buộc MAXDOP là 10 cho ví dụ cụ thể này.

Tôi có hai bảng được phân vùng bằng cách sử dụng cùng một sơ đồ phân vùng. Khi kết hợp chúng lại với nhau trên cột được sử dụng để phân vùng, tôi nhận thấy rằng SQL Server không thể tối ưu hóa kết hợp song song nhiều như mong đợi và do đó chọn sử dụng HASH THAM GIA thay thế. Trong trường hợp cụ thể này, tôi có thể mô phỏng thủ công MERGE THAM GIA song song tối ưu hơn nhiều bằng cách chia truy vấn thành 10 phạm vi tách rời dựa trên chức năng phân vùng và chạy đồng thời từng truy vấn đó trong SSMS. Sử dụng WAITFOR để chạy tất cả chúng cùng một lúc chính xác, kết quả là tất cả các truy vấn hoàn thành trong ~ 40% tổng thời gian được sử dụng bởi HASH THAM GIA song song ban đầu.

Có cách nào để SQL Server tự thực hiện tối ưu hóa này trong trường hợp các bảng được phân vùng tương đương không? Tôi hiểu rằng SQL Server thường có thể phải chịu rất nhiều chi phí để tạo song song MERGE THAM GIA, nhưng có vẻ như có một phương pháp shending rất tự nhiên với chi phí tối thiểu trong trường hợp này. Có lẽ đó chỉ là một trường hợp chuyên biệt mà trình tối ưu hóa chưa đủ thông minh để nhận ra?

Đây là SQL để thiết lập một tập dữ liệu đơn giản hóa để tái tạo vấn đề này:

/* 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)

Bây giờ chúng tôi cuối cùng đã sẵn sàng để tái tạo truy vấn phụ tối ưu!

/* 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)

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

Tuy nhiên, sử dụng một luồng duy nhất để xử lý từng phân vùng (ví dụ cho phân vùng đầu tiên bên dưới) sẽ dẫn đến một kế hoạch hiệu quả hơn nhiều. Tôi đã kiểm tra điều này bằng cách chạy một truy vấn như câu hỏi dưới đây cho mỗi trong số 10 phân vùng cùng một lúc và tất cả 10 kết thúc chỉ sau hơn 1 giây:

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)

nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây

Câu trả lời:


18

Bạn đúng rằng trình tối ưu hóa Máy chủ SQL không muốn tạo các MERGEkế hoạch tham gia song song (chi phí thay thế này khá cao). Song song MERGEluôn yêu cầu trao đổi phân vùng trên cả hai đầu vào tham gia và quan trọng hơn, nó yêu cầu thứ tự hàng phải được bảo toàn trên các trao đổi đó.

Song song là hiệu quả nhất khi mỗi luồng có thể chạy độc lập; bảo quản đơn hàng thường dẫn đến sự chờ đợi đồng bộ hóa thường xuyên và cuối cùng có thể khiến các trao đổi tràn ra tempdbđể giải quyết tình trạng bế tắc trong truy vấn.

Các vấn đề này có thể được khắc phục bằng cách chạy nhiều phiên bản của toàn bộ truy vấn trên một luồng, với mỗi luồng xử lý một phạm vi dữ liệu độc quyền. Tuy nhiên, đây không phải là một chiến lược mà trình tối ưu hóa xem xét nguyên bản. Như vậy, mô hình SQL Server ban đầu cho tính song song phá vỡ truy vấn tại các trao đổi và chạy các phân đoạn kế hoạch được hình thành bởi các phân chia trên nhiều luồng.

Có nhiều cách để đạt được toàn bộ kế hoạch truy vấn trên nhiều luồng trên phạm vi tập dữ liệu độc quyền, nhưng chúng yêu cầu mánh khóe mà không phải ai cũng hài lòng (và sẽ không được Microsoft hỗ trợ hoặc đảm bảo sẽ hoạt động trong tương lai). Một cách tiếp cận như vậy là lặp lại các phân vùng của một bảng được phân đoạn và cung cấp cho mỗi luồng nhiệm vụ tạo ra một tổng phụ. Kết quả là SUMsố lượng hàng được trả về bởi mỗi luồng độc lập:

Lấy số phân vùng đủ dễ dàng từ siêu dữ liệu:

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;

Sau đó, chúng tôi sử dụng các số này để điều khiển một phép nối ( APPLY) tương quan và $PARTITIONhàm để giới hạn mỗi luồng trong số phân vùng hiện tại:

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;

Kế hoạch truy vấn cho thấy một phép MERGEnối được thực hiện cho mỗi hàng trong bảng @P. Các thuộc tính quét chỉ mục được nhóm xác nhận rằng chỉ một phân vùng duy nhất được xử lý trên mỗi lần lặp:

Áp dụng kế hoạch nối tiếp

Thật không may, điều này chỉ dẫn đến việc xử lý nối tiếp các phân vùng. Trên tập dữ liệu bạn cung cấp, máy tính xách tay 4 lõi (siêu phân luồng đến 8) của tôi trả về kết quả chính xác trong 7 giây với tất cả dữ liệu trong bộ nhớ.

Để có được các MERGEkế hoạch con chạy đồng thời, chúng ta cần một kế hoạch song song trong đó các id phân vùng được phân phối trên các luồng có sẵn ( MAXDOP) và mỗi MERGEkế hoạch con chạy trên một luồng sử dụng dữ liệu trong một phân vùng. Thật không may, trình tối ưu hóa thường quyết định chống lại song song MERGEtrên cơ sở chi phí và không có cách nào được lập thành tài liệu để buộc một kế hoạch song song. Có một cách không có giấy tờ (và không được hỗ trợ), sử dụng cờ theo dõi 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);

Bây giờ kế hoạch truy vấn cho thấy số phân vùng không @Pđược phân phối giữa các luồng trên cơ sở luân chuyển. Mỗi luồng chạy phía bên trong của các vòng lặp lồng nhau tham gia cho một phân vùng duy nhất, đạt được mục tiêu xử lý đồng thời dữ liệu của chúng tôi. Kết quả tương tự bây giờ được trả về sau 3 giây trên 8 lõi của tôi, với tất cả tám với mức sử dụng 100%.

ÁP DỤNG song song

Tôi không khuyên bạn nên sử dụng kỹ thuật này nhất thiết - xem các cảnh báo trước đó của tôi - nhưng nó giải quyết câu hỏi của bạn.

Xem bài viết của tôi Cải thiện Bảng phân vùng Tham gia hiệu suất để biết thêm chi tiết.

Nhà kho

Xem như bạn đang sử dụng SQL Server 2012 (và giả sử đó là Enterprise), bạn cũng có tùy chọn sử dụng chỉ mục cột. Điều này cho thấy tiềm năng của hàm băm chế độ hàng loạt tham gia khi có đủ bộ nhớ:

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

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

Với các chỉ mục này, truy vấn ...

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

... Kết quả trong kế hoạch thực hiện sau từ trình tối ưu hóa mà không có bất kỳ mánh khóe nào:

Kế hoạch cột 1

Kết quả chính xác trong 2 giây , nhưng loại bỏ xử lý chế độ hàng cho tổng hợp vô hướng sẽ giúp nhiều hơn:

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;

Cửa hàng cột được tối ưu hóa

Truy vấn lưu trữ cột được tối ưu hóa chạy trong 851ms .

Geoff Patterson đã tạo báo cáo lỗi Phân vùng Wise Joins nhưng nó đã bị đóng dưới dạng Không sửa.


5
Kinh nghiệm học tập tuyệt vời ở đây. cảm ơn bạn. +1
Edward tổng hợp

1
Cảm ơn Paul! Thông tin tuyệt vời ở đây, và nó chắc chắn giải quyết các câu hỏi chi tiết.
Geoff Patterson

2
Cảm ơn Paul! Thông tin tuyệt vời ở đây, và nó chắc chắn giải quyết các câu hỏi chi tiết. Chúng tôi đang ở trong một môi trường SQL 2008/2012 hỗn hợp, nhưng tôi sẽ xem xét việc khám phá các cửa hàng cột hơn nữa cho tương lai. Tất nhiên, tôi vẫn mong muốn SQL Server có thể tận dụng hiệu quả sự kết hợp song song - và các yêu cầu bộ nhớ thấp hơn nhiều - trong trường hợp sử dụng của tôi :) Tôi đã gửi vấn đề Kết nối sau đây trong trường hợp bất kỳ ai quan tâm và xem xét hoặc bỏ phiếu cho nó: connect.microsoft.com/QueryServer/feedback/details/759266/ mẹo
Geoff Patterson

0

Cách để làm cho trình tối ưu hóa hoạt động theo cách bạn nghĩ tốt hơn là thông qua gợi ý truy vấn.

Trong trường hợp này, OPTION (MERGE JOIN)

Hoặc bạn có thể đi cả con lợn và sử dụng USE PLAN


Tôi sẽ không làm điều này một cách cá nhân: gợi ý sẽ chỉ hữu ích cho khối lượng và phân phối dữ liệu hiện tại.
gbn

Điều thú vị là việc sử dụng TÙY CHỌN (MERGE THAM GIA) dẫn đến một kế hoạch tồi tệ hơn nhiều. Trình tối ưu hóa không đủ thông minh để nhận ra rằng MERGE THAM GIA có thể bị loại bỏ bởi chức năng phân vùng và việc áp dụng gợi ý này khiến cho truy vấn mất ~ 46 giây. Rất bực bội!

@gbn có lẽ là lý do tại sao trình tối ưu hóa sẽ cho phép băm tham gia ở vị trí đầu tiên?

@gpatterson Thật khó chịu! :)

Điều gì xảy ra nếu bạn buộc phân vùng thủ công thông qua liên kết (nghĩa là: truy vấn ngắn của bạn kết hợp với các truy vấn tương tự khác)?
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.