Tại sao SQL Server không tối ưu hóa UNION?


7

Hãy xem xét các truy vấn này ( SQL Fiddle ):

Truy vấn 1:

SELECT * INTO #TMP1 FROM Foo
UNION
SELECT * FROM Boo
UNION
SELECT * FROM Koo;

Truy vấn 2:

SELECT * INTO #TMP2 FROM Foo
UNION
SELECT * FROM Boo
UNION ALL
SELECT * FROM Koo;

Lưu ý rằng Koo không trùng lặp với Boo / Foo, vì vậy kết quả cuối cùng là như nhau. Câu hỏi đặt ra là tại sao tổ hợp UNION / UNION đầu tiên không được hợp nhất thành một hoạt động SORT duy nhất?


2
MERGEtham gia được tối ưu hóa hơn SORT. Nó có độ phức tạp tuyến tính và không yêu cầu cấp bộ nhớ.
Martin Smith

Câu trả lời:


18

Trình tối ưu hóa truy vấn không có toán tử n-ary, mặc dù công cụ thực thi có khá ít. Để minh họa, tôi sẽ sử dụng một phiên bản đơn giản của các bảng của bạn - (SQL Fiddle) .

SELECT DISTINCT
    number
INTO foo
FROM master..spt_values
WHERE 
    number < 1000;

SELECT DISTINCT
    number
INTO boo
FROM master..spt_values
WHERE 
    number between 300 and 1005;

SELECT DISTINCT
    number
INTO koo
FROM master..spt_values
WHERE 
    number > 1006;

ALTER TABLE dbo.foo ADD PRIMARY KEY (number);
ALTER TABLE dbo.boo ADD PRIMARY KEY (number);
ALTER TABLE dbo.koo ADD PRIMARY KEY (number);

Đưa ra các bảng và dữ liệu đó, chúng ta hãy nhìn vào cây đầu vào cho UNIONtruy vấn ba chiều :

SELECT f.number FROM dbo.foo AS f
UNION
SELECT b.number FROM dbo.boo AS b
UNION
SELECT k.number FROM dbo.koo AS k
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8605);

LogOp_Union
    OUTPUT(COL: Union1006 )
    CHILD(QCOL: [f].number)
    CHILD(QCOL: [b].number)
    CHILD(QCOL: [k].number)
    LogOp_Project
        LogOp_Get TBL: dbo.foo(alias TBL: f)
        AncOp_PrjList 
    LogOp_Project
        LogOp_Get TBL: dbo.boo(alias TBL: b)
        AncOp_PrjList 
    LogOp_Project
        LogOp_Get TBL: dbo.koo(alias TBL: k)
        AncOp_PrjList 

Toán tử hợp nhất logic có một đầu ra và ba đầu vào con. Sau khi tối ưu hóa dựa trên chi phí, cây vật lý được chọn là hợp nhất với ba đầu vào:

SELECT f.number FROM dbo.foo AS f
UNION
SELECT b.number FROM dbo.boo AS b
UNION
SELECT k.number FROM dbo.koo AS k
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8607);

PhyOp_MergeUnion
    PhyOp_Range TBL: dbo.foo(alias TBL: f)(1) ASC
    PhyOp_Range TBL: dbo.boo(alias TBL: b)(1) ASC
    PhyOp_Range TBL: dbo.koo(alias TBL: k)(1) ASC

Đầu ra của trình tối ưu hóa được làm lại thành một hình thức mà công cụ thực thi (không có liên kết hợp nhất n-ary) có thể xử lý:

Hợp nhất kế hoạch công đoàn

Việc viết lại sau tối ưu hóa mở ra n-ary PhyOp_MergeUnionthành nhiều toán tử Merge Union. Lưu ý rằng tất cả các chi phí ước tính vẫn được liên kết với nhà điều hành công đoàn 'ban đầu' - những người khác có ước tính chi phí bằng không.

Rằng lý do tối ưu hóa về các công đoàn sử dụng toán tử n-ary cung cấp một lời giải thích cho lý do tại sao nó không xem xét việc viết lại ví dụ đầu tiên của bạn vào cùng một kế hoạch như ví dụ thứ hai (liên kết ba chiều là một nút cây).

Lý do thứ hai là không có ràng buộc nào để thực thi "thiếu chồng chéo". Trước khi có các ràng buộc, một liên kết giữa bookookhông thể được đảm bảo không tạo ra các bản sao, vì vậy chúng tôi có một kế hoạch loại bỏ trùng lặp (Liên minh hợp nhất trong trường hợp này):

SELECT b.number FROM dbo.boo AS b
UNION
SELECT k.number FROM dbo.koo AS k;

boo / koo không ràng buộc

Việc thêm các ràng buộc sau đây đảm bảo điều kiện không chồng lấp có thể bị vi phạm mà không làm mất hiệu lực các gói được lưu trong bộ nhớ cache cho truy vấn:

ALTER TABLE dbo.foo WITH CHECK ADD CHECK (number < 1000);
ALTER TABLE dbo.boo WITH CHECK ADD CHECK (number BETWEEN 300 AND 1005);
ALTER TABLE dbo.koo WITH CHECK ADD CHECK (number > 1006);

Bây giờ nó an toàn cho trình tối ưu hóa chỉ đơn giản là nối:

boo / koo với các ràng buộc

Tuy nhiên, ngay cả khi có các ràng buộc đó, truy vấn hợp ba chiều vẫn xuất hiện dưới dạng ba liên kết vì trình tối ưu hóa thường không xem xét việc tách các toán tử n-ary để khám phá các lựa chọn thay thế. Điều hành n-ary rất hữu ích trong việc kiểm soát không gian tìm kiếm; phá vỡ nó thường sẽ phản tác dụng với mục tiêu tối ưu hóa là tìm kiếm một kế hoạch tốt một cách nhanh chóng.

SELECT f.number FROM dbo.foo AS f
UNION
SELECT b.number FROM dbo.boo AS b
UNION
SELECT k.number FROM dbo.koo AS k;

Hợp nhất kế hoạch công đoàn với những hạn chế

Khi được viết dưới dạng a UNIONUNION ALL, một toán tử n-ary không còn có thể được sử dụng (các loại không khớp) để cây có các nút riêng biệt:

SELECT f.number FROM dbo.foo AS f
UNION
SELECT b.number FROM dbo.boo AS b
UNION ALL
SELECT k.number FROM dbo.koo AS k
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8605);

LogOp_UnionAll
    OUTPUT(COL: Union1007 )
    CHILD(COL: Union1004 )
    CHILD(QCOL: [k].number)

    LogOp_Union
        OUTPUT(COL: Union1004 )
        CHILD(QCOL: [f].number)
        CHILD(QCOL: [b].number)

        LogOp_Project
            LogOp_Get TBL: dbo.foo(alias TBL: f)
            AncOp_PrjList 

        LogOp_Project
            LogOp_Get TBL: dbo.boo(alias TBL: b)
            AncOp_PrjList 

    LogOp_Project
        LogOp_Get TBL: dbo.koo(alias TBL: k)
        AncOp_PrjList 

3

SQL Server không có hoạt động thiết lập 3 chiều; toán tử CONCATENATION chấp nhận n đầu vào. Cho, ví dụ, mười bảng:

CREATE TABLE Test01 (SomeKey INTEGER NOT NULL, SomeAttribute VARCHAR(80)); 
CREATE TABLE Test02 (SomeKey INTEGER NOT NULL, SomeAttribute VARCHAR(80));
CREATE TABLE Test03 (SomeKey INTEGER NOT NULL, SomeAttribute VARCHAR(80));
CREATE TABLE Test04 (SomeKey INTEGER NOT NULL, SomeAttribute VARCHAR(80));
CREATE TABLE Test05 (SomeKey INTEGER NOT NULL, SomeAttribute VARCHAR(80));
CREATE TABLE Test06 (SomeKey INTEGER NOT NULL, SomeAttribute VARCHAR(80));
CREATE TABLE Test07 (SomeKey INTEGER NOT NULL, SomeAttribute VARCHAR(80));
CREATE TABLE Test08 (SomeKey INTEGER NOT NULL, SomeAttribute VARCHAR(80));
CREATE TABLE Test09 (SomeKey INTEGER NOT NULL, SomeAttribute VARCHAR(80));
CREATE TABLE Test10 (SomeKey INTEGER NOT NULL, SomeAttribute VARCHAR(80));

và một truy vấn hợp nhất mọi thứ để tìm bất kỳ hàng nào trong mỗi bảng có cùng khóa:

SELECT * FROM
(
SELECT * FROM Test01 UNION ALL
SELECT * FROM Test02 UNION ALL
SELECT * FROM Test03 UNION ALL
SELECT * FROM Test04 UNION ALL
SELECT * FROM Test05 UNION ALL
SELECT * FROM Test06 UNION ALL
SELECT * FROM Test07 UNION ALL
SELECT * FROM Test08 UNION ALL
SELECT * FROM Test09 UNION ALL
SELECT * FROM Test10
) AS Bunch
WHERE SomeKey = 39;

Chúng ta sẽ thấy một kế hoạch truy vấn có các hàng khớp với nhau (với vị từ được đẩy xuống trong toán tử TABLE SCAN) sau đó nối tất cả các kết quả vào SELECTtoán tử.

Lý do bạn không nhận được một kế hoạch hợp nhất sau đó sắp xếp là vì nó sẽ rất chậm và loại không cần thiết để thực hiện UNIONthao tác. Trong các bảng BOO, FOO và KOO của bạn, bạn đã khai báo khóa chính. Khi bộ truy cập CLUSTERED INDEX SCAN liệt kê các hàng, chúng được sản xuất theo thứ tự của chỉ số cụm bên dưới - được đảm bảo. Ghép hai bộ sau đó sắp xếp kết quả chậm hơn nhiều so với sử dụng toán tử MERGE THAM GIA và toán tử MJ có thể được sử dụng rất dễ dàng vì cả hai bộ được sắp xếp và lập chỉ mụ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.