Tại sao một bảng tạm thời là một giải pháp hiệu quả hơn cho Vấn đề Halloween hơn là một ống chỉ háo hức?


14

Hãy xem xét truy vấn sau đây chỉ chèn các hàng từ bảng nguồn nếu chúng chưa có trong bảng đích:

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
OPTION (MAXDOP 1, QUERYTRACEON 7470);

Một hình dạng kế hoạch có thể bao gồm một liên kết hợp nhất và một ống chỉ háo hức. Toán tử spool háo hức có mặt để giải quyết vấn đề Halloween :

kế hoạch đầu tiên

Trên máy của tôi, đoạn mã trên thực thi trong khoảng 6900 ms. Mã repro để tạo các bảng được bao gồm ở dưới cùng của câu hỏi. Nếu tôi không hài lòng với hiệu suất, tôi có thể thử tải các hàng được chèn vào bảng tạm thời thay vì dựa vào bộ đệm háo hức. Đây là một triển khai có thể:

DROP TABLE IF EXISTS #CONSULTANT_RECOMMENDED_TEMP_TABLE;
CREATE TABLE #CONSULTANT_RECOMMENDED_TEMP_TABLE (
    ID BIGINT,
    PRIMARY KEY (ID)
);

INSERT INTO #CONSULTANT_RECOMMENDED_TEMP_TABLE WITH (TABLOCK)
SELECT maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
OPTION (MAXDOP 1, QUERYTRACEON 7470);

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT new_rows.ID
FROM #CONSULTANT_RECOMMENDED_TEMP_TABLE new_rows
OPTION (MAXDOP 1);

Mã mới thực thi trong khoảng 4400 ms. Tôi có thể nhận các kế hoạch thực tế và sử dụng Thống kê thời gian thực tế ™ để kiểm tra xem thời gian được sử dụng ở cấp độ nhà điều hành. Lưu ý rằng việc yêu cầu một kế hoạch thực tế sẽ thêm chi phí đáng kể cho các truy vấn này để tổng số sẽ không khớp với kết quả trước đó.

╔═════════════╦═════════════╦══════════════╗
  operator    first query  second query 
╠═════════════╬═════════════╬══════════════╣
 big scan     1771         1744         
 little scan  163          166          
 sort         531          530          
 merge join   709          669          
 spool        3202         N/A          
 temp insert  N/A          422          
 temp scan    N/A          187          
 insert       3122         1545         
╚═════════════╩═════════════╩══════════════╝

Kế hoạch truy vấn với bộ đệm háo hức dường như dành nhiều thời gian hơn cho các toán tử chèn và bộ đệm so với kế hoạch sử dụng bảng tạm thời.

Tại sao kế hoạch với bảng tạm thời hiệu quả hơn? Không phải là một spool háo hức chủ yếu chỉ là một bảng tạm thời nội bộ? Tôi tin rằng tôi đang tìm kiếm câu trả lời tập trung vào nội bộ. Tôi có thể thấy các ngăn xếp cuộc gọi khác nhau như thế nào nhưng không thể tìm ra bức tranh lớn.

Tôi đang dùng SQL Server 2017 CU 11 trong trường hợp ai đó muốn biết. Đây là mã để điền vào các bảng được sử dụng trong các truy vấn trên:

DROP TABLE IF EXISTS dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR;

CREATE TABLE dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR (
ID BIGINT NOT NULL,
PRIMARY KEY (ID)
);

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT TOP (20000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
CROSS JOIN master..spt_values t3
OPTION (MAXDOP 1);


DROP TABLE IF EXISTS dbo.A_HEAP_OF_MOSTLY_NEW_ROWS;

CREATE TABLE dbo.A_HEAP_OF_MOSTLY_NEW_ROWS (
ID BIGINT NOT NULL
);

INSERT INTO dbo.A_HEAP_OF_MOSTLY_NEW_ROWS WITH (TABLOCK)
SELECT TOP (1900000) 19999999 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

Câu trả lời:


14

Đây là những gì tôi gọi là Bảo vệ Halloween bằng tay .

Bạn có thể tìm thấy một ví dụ về nó đang được sử dụng với một tuyên bố cập nhật trong bài viết của tôi Tối ưu hóa các truy vấn cập nhật . Người ta phải cẩn thận một chút để bảo tồn cùng một ngữ nghĩa, ví dụ bằng cách khóa bảng đích chống lại tất cả các sửa đổi đồng thời trong khi các truy vấn riêng biệt thực thi, nếu điều đó có liên quan trong kịch bản của bạn.

Tại sao kế hoạch với bảng tạm thời hiệu quả hơn? Không phải là một spool háo hức chủ yếu chỉ là một bảng tạm thời nội bộ?

Một ống chỉ có một số đặc điểm của một bảng tạm thời, nhưng hai cái này không tương đương chính xác. Cụ thể, một ống chỉ về cơ bản là một hàng được sắp xếp theo thứ tự hàng vào cấu trúc cây b . Nó có lợi từ việc khóa và tối ưu hóa ghi nhật ký, nhưng không hỗ trợ tối ưu hóa tải hàng loạt .

Do đó, người ta thường có thể có hiệu suất tốt hơn bằng cách chia truy vấn theo cách tự nhiên: Tải hàng loạt các hàng mới vào một bảng hoặc biến tạm thời, sau đó thực hiện chèn tối ưu hóa (không có Bảo vệ Halloween rõ ràng) từ đối tượng tạm thời.

Tạo sự tách biệt này cũng cho phép bạn có thêm tự do để điều chỉnh các phần đọc và viết của câu lệnh gốc.

Là một lưu ý phụ, thật thú vị khi nghĩ về vấn đề Halloween có thể được giải quyết bằng các phiên bản hàng. Có lẽ phiên bản tương lai của SQL Server sẽ cung cấp tính năng đó trong các trường hợp phù hợp.


Như Michael Kutz đã đề cập trong một bình luận, bạn cũng có thể khám phá khả năng khai thác tối ưu hóa lấp lỗ hổng để tránh HP rõ ràng. Một cách để đạt được điều này cho bản demo là tạo một chỉ mục duy nhất (được nhóm nếu bạn muốn) trên IDcột của A_HEAP_OF_MOSTLY_NEW_ROWS.

CREATE UNIQUE INDEX i ON dbo.A_HEAP_OF_MOSTLY_NEW_ROWS (ID);

Với sự đảm bảo đó, trình tối ưu hóa có thể sử dụng tính năng điền lỗ và chia sẻ hàng:

MERGE dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (SERIALIZABLE) AS HICETY
USING dbo.A_HEAP_OF_MOSTLY_NEW_ROWS AS AHOMNR
    ON AHOMNR.ID = HICETY.ID
WHEN NOT MATCHED BY TARGET
THEN INSERT (ID) VALUES (AHOMNR.ID);

Kế hoạch MERGE

Mặc dù thú vị, bạn vẫn có thể đạt được hiệu suất tốt hơn trong nhiều trường hợp bằng cách sử dụng Bảo vệ Halloween bằng tay được triển khai cẩn thận.


5

Để mở rộng câu trả lời của Paul một chút, một phần của sự khác biệt về thời gian trôi qua giữa các cách tiếp cận bảng chỉ và bảng tạm thời dường như là do thiếu sự hỗ trợ cho DML Request Sorttùy chọn trong kế hoạch bộ đệm. Với cờ theo dõi không có giấy tờ 8795, thời gian trôi qua cho cách tiếp cận bảng tạm thời nhảy từ 4400 ms đến 5600 ms.

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT new_rows.ID
FROM #CONSULTANT_RECOMMENDED_TEMP_TABLE new_rows
OPTION (MAXDOP 1, QUERYTRACEON 8795);

Lưu ý rằng điều này không chính xác tương đương với phần chèn được thực hiện bởi gói spool. Truy vấn này ghi nhiều dữ liệu hơn vào nhật ký giao dịch.

Hiệu ứng tương tự có thể được nhìn thấy ngược lại với một số mánh khóe. Có thể khuyến khích SQL Server sử dụng một loại thay vì một bộ đệm cho Bảo vệ Halloween. Một cách thực hiện:

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT TOP (987654321) 
maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
ORDER BY maybe_new_rows.ID, maybe_new_rows.ID + 1
OPTION (MAXDOP 1, QUERYTRACEON 7470, MERGE JOIN);

Bây giờ kế hoạch có một toán tử TOP N Sort thay cho ống chỉ. Sắp xếp là một toán tử chặn, vì vậy bộ đệm không còn cần thiết:

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

Quan trọng hơn, chúng tôi hiện có hỗ trợ cho DML Request Sorttùy chọn này. Nhìn lại Thống kê thời gian thực tế một lần nữa, toán tử chèn giờ chỉ mất 1623 ms. Toàn bộ kế hoạch mất khoảng 5400 ms để thực hiện mà không yêu cầu một kế hoạch thực tế.

Như Hugo giải thích , toán tử Eager Spool không giữ trật tự. Điều đó có thể dễ dàng được nhìn thấy nhất với một TOP PERCENTkế hoạch. Thật không may là truy vấn ban đầu với bộ đệm không thể tận dụng tốt hơn tính chất được sắp xếp của dữ liệu trong bộ đệm.

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.