Số hàng 'thực tế' không chính xác trong kế hoạch song song


17

Đây là một câu hỏi hoàn toàn mang tính học thuật, đến mức nó không gây ra vấn đề gì và tôi chỉ muốn nghe bất kỳ lời giải thích nào cho hành vi.

Lấy một vấn đề tiêu chuẩn Bảng kiểm đếm CTE của Itzik Ben-Gan:

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

Đưa ra một truy vấn sẽ tạo bảng số 1 triệu hàng:

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

Hãy xem kế hoạch thực hiện song song cho truy vấn này:

Kế hoạch thực hiện song song

Lưu ý số hàng 'thực tế' trước toán tử luồng thu thập là 1.004.588. Sau khi tập hợp toán tử luồng, số hàng là 1.000.000. Người lạ vẫn vậy, giá trị không nhất quán và sẽ thay đổi từ chạy sang chạy. Kết quả của COUNT luôn luôn đúng.

Phát hành truy vấn một lần nữa, buộc kế hoạch không song song:

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

Lần này tất cả các nhà khai thác hiển thị số lượng hàng 'thực tế' chính xác.

Kế hoạch thực hiện không song song

Tôi đã thử điều này trên 2005SP3 và 2008R2 cho đến nay, kết quả tương tự trên cả hai. Bất kỳ suy nghĩ về những gì có thể gây ra điều này?

Câu trả lời:


12

Các hàng được truyền qua các trao đổi nội bộ từ nhà sản xuất đến luồng tiêu dùng trong các gói (do đó CXPACKET - gói trao đổi lớp), chứ không phải là một hàng tại một thời điểm. Có một số lượng nhất định của bộ đệm trong trao đổi. Ngoài ra, lệnh gọi tắt đường ống từ phía người tiêu dùng của Luồng tập hợp phải được chuyển trong gói điều khiển trở lại các luồng của nhà sản xuất. Lập kế hoạch và cân nhắc nội bộ khác có nghĩa là các kế hoạch song song luôn có một 'khoảng cách dừng' nhất định.

Kết quả là, bạn sẽ thường thấy loại chênh lệch đếm hàng này trong đó ít hơn toàn bộ hàng tiềm năng của một cây con thực sự là bắt buộc. Trong trường hợp này, TOP đưa việc thực thi đến 'kết thúc sớm'.

Thêm thông tin:


10

Tôi nghĩ rằng tôi có thể có một lời giải thích một phần cho điều này nhưng xin vui lòng bắn hạ nó hoặc đăng bất kỳ lựa chọn thay thế. @MartinSmith chắc chắn sẽ làm được điều gì đó bằng cách làm nổi bật hiệu quả của TOP trong kế hoạch thực hiện.

Nói một cách đơn giản, 'Đếm hàng thực tế' không phải là tổng số hàng mà toán tử xử lý, nó là số lần phương thức GetNext () của toán tử được gọi.

Lấy từ BOL :

Các toán tử vật lý khởi tạo, thu thập dữ liệu và đóng. Cụ thể, toán tử vật lý có thể trả lời ba cuộc gọi phương thức sau:

  • Init (): Phương thức init () làm cho một toán tử vật lý tự khởi tạo và thiết lập bất kỳ cấu trúc dữ liệu cần thiết nào. Toán tử vật lý có thể nhận được nhiều cuộc gọi init (), mặc dù thông thường, một toán tử vật lý chỉ nhận được một cuộc gọi.
  • GetNext (): Phương thức GetNext () khiến toán tử vật lý nhận được hàng dữ liệu đầu tiên hoặc tiếp theo. Toán tử vật lý có thể nhận được 0 hoặc nhiều lệnh gọi GetNext ().
  • Đóng (): Phương thức Đóng () khiến toán tử vật lý thực hiện một số thao tác dọn dẹp và tự tắt. Một toán tử vật lý chỉ nhận được một cuộc gọi Đóng ().

Phương thức GetNext () trả về một hàng dữ liệu và số lần được gọi là ActualRows trong đầu ra Showplan được tạo bằng cách sử dụng SET STATISTICS PROFILE ON hoặc SET STATISTICS XML ON.

Để hoàn thiện, một chút nền tảng về các toán tử song song là hữu ích. Công việc được phân phối cho nhiều luồng trong một kế hoạch song song bởi luồng phân vùng lại hoặc phân phối các toán tử luồng. Chúng phân phối các hàng hoặc trang giữa các luồng bằng một trong bốn cơ chế:

  • Hash phân phối các hàng dựa trên hàm băm của các cột trong hàng
  • Vòng tròn phân phối các hàng bằng cách lặp qua danh sách các chuỗi trong một vòng lặp
  • Broadcast phân phối tất cả các trang hoặc hàng cho tất cả các chủ đề
  • Phân vùng nhu cầu chỉ được sử dụng để quét. Chủ đề quay lên, yêu cầu một trang dữ liệu từ toán tử, xử lý nó và yêu cầu một trang tiếp theo khi hoàn thành.

Toán tử phân phối luồng đầu tiên (hầu hết trong kế hoạch) sử dụng phân vùng nhu cầu trên các hàng có nguồn gốc từ quá trình quét liên tục. Có ba luồng gọi GetNext () 6, 4 và 0 lần với tổng số 10 'Hàng thực tế':

<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>

Tại toán tử phân phối tiếp theo, chúng ta có ba luồng một lần nữa, lần này với các lệnh gọi 50, 50 và 0 đến GetNext () với tổng số 100:

<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>

Chính tại toán tử song song tiếp theo, nguyên nhân và lời giải thích có thể xuất hiện.

<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>

Vì vậy, bây giờ chúng tôi có 11 cuộc gọi đến GetNext (), nơi chúng tôi đang mong đợi để xem 10 cuộc gọi.

Chỉnh sửa: 2011-11-13

Bị mắc kẹt tại thời điểm này, tôi đã đi tìm câu trả lời với các lỗ hổng trong chỉ mục được nhóm và @MikeWalsh vui lòng chỉ dẫn @QueryKiwi tại đây .


7

1,004,588 là một con số tăng lên rất nhiều trong thử nghiệm của tôi.

Tôi cũng thấy điều này cho kế hoạch có phần đơn giản hơn dưới đây.

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

Kế hoạch

Các số liệu quan tâm khác trong kế hoạch thực hiện là

+----------------------------------+--------------+--------------+-----------------+
|                                  | 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 |
+----------------------------------+--------------+--------------+-----------------+

Tôi đoán chỉ là bởi vì các tác vụ đang được xử lý song song một tác vụ nằm ở các hàng xử lý giữa chuyến bay khi một nhiệm vụ khác chuyển hàng thứ một triệu cho toán tử luồng thu thập nên các hàng bổ sung đang được xử lý. Ngoài ra, từ bài viết này, các hàng được đệm và phân phối theo đợt cho trình lặp này, do đó có vẻ như số lượng hàng đang được xử lý sẽ vượt quá chứ không phải chính xác đạt TOPđặc tả trong bất kỳ sự kiện nào.

Biên tập

Chỉ cần nhìn vào điều này một chút chi tiết. Tôi nhận thấy tôi đã trở nên đa dạng hơn chỉ là 1,004,588số lượng hàng được trích dẫn ở trên, vì vậy đã chạy truy vấn ở trên trong một vòng lặp cho 1.000 lần lặp và nắm bắt các kế hoạch thực hiện thực tế. Loại bỏ 81 kết quả mà mức độ song song bằng 0 đã cho các số liệu sau.

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

Có thể thấy rằng 1.004.588 là kết quả phổ biến nhất nhưng trong 3 lần, trường hợp xấu nhất có thể xảy ra và 100.000.000 hàng đã được xử lý. Trường hợp tốt nhất quan sát được là 1.000.496 hàng, xảy ra 19 lần.

Kịch bản đầy đủ để tái tạo nằm ở cuối phiên bản 2 của câu trả lời này (nó sẽ cần điều chỉnh nếu chạy trên một hệ thống có nhiều hơn 2 bộ xử lý).


1

Tôi tin rằng vấn đề xuất phát từ thực tế là nhiều luồng có thể xử lý cùng một hàng tùy thuộc vào cách các hàng được khắc lên giữa các luồng.

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.