Giải pháp cho lỗi chống bán tham gia


7

Tôi đã xây dựng truy vấn SQL Server sau đây, nhưng nó gặp phải lỗi chống bán tham gia trong SQL Server 2005 dẫn đến ước tính số lượng thẻ không chính xác (1 - urgh!) Và chạy mãi mãi. Vì đây là một SQL Server sản xuất lâu đời nên tôi không thể dễ dàng đề xuất nâng cấp các phiên bản và vì vậy tôi không thể buộc gợi ý dấu vết 4199 trên truy vấn cụ thể này.

Tôi đang gặp khó khăn trong việc tái cấu trúc WHERE AND NOT IN (SELECT). Bất cứ ai có thể quan tâm để giúp đỡ? Tôi đã chắc chắn thử và sử dụng các phép nối tốt nhất dựa trên các cặp khóa được nhóm.

SELECT TOP 5000 d.doc2_id
    ,d.direction_cd
    ,a.address_type_cd
    ,d.external_identification
    ,s.hash_value
    ,d.publishdate
    ,d.sender_address_id AS [D2 Sender_Address_id]
    ,a.address_id AS [A Address_ID]
    ,d.message_size
    ,d.subject
    ,emi.employee_id
FROM assentor.emcsdbuser.doc2 d(NOLOCK)
INNER JOIN assentor.emcsdbuser.employee_msg_index emi(NOLOCK)
    ON d.processdate = emi.processdate
    AND d.doc2_id = emi.doc2_id
INNER LOOP JOIN assentor.emcsdbuser.doc2_address a(NOLOCK)
    ON emi.doc2_id = a.doc2_id
    AND emi.address_type_cd = a.address_type_cd
    AND emi.address_id = a.address_id
INNER JOIN sis.dbo.sis s(NOLOCK) ON d.external_identification = s.external_identification
WHERE d.publishdate > '2008-01-01'
    **AND d.doc2_id NOT IN (
        SELECT doc2_id
        FROM assentor.emcsdbuser.doc2_address d2a(NOLOCK)
        WHERE d.doc2_id = d2a.doc2_id
            AND d2a.address_type_cd = 'FRM'
        )**
OPTION (FAST 10)

Lưu ý rằng Employee_MSG_Indexbảng là 500m hàng, doc2là 1,5b hàng, SISlà ~ 500m hàng.

Bất kỳ trợ giúp sẽ được đánh giá cao!

Câu trả lời:


13

Vì nó là một SQL Server sản xuất lâu đời nên tôi không thể dễ dàng đề xuất nâng cấp các phiên bản

Lỗi ước tính cardinality chống bán tham gia có thể được sao chép trên tất cả các phiên bản của SQL Server từ năm 2005 đến 2012 . Tất cả đều yêu cầu cờ theo dõi 4199 để cho phép sửa lỗi, vì vậy việc nâng cấp sẽ không giải quyết được vấn đề của bạn mà không kích hoạt 4199 (mặc dù có nhiều lý do tốt khác để nâng cấp từ năm 2005).

... Như vậy tôi không thể buộc gợi ý dấu vết 4199 trên truy vấn cụ thể này.

Nếu đó chỉ là một truy vấn cụ thể bị ảnh hưởng, bạn có thể sử dụng OPTION (QUERYTRACEON 4199)để bật cờ theo dõi cho truy vấn đó. Gợi ý truy vấn này được ghi lại và hỗ trợ để sử dụng với 4199 và áp dụng từ SQL Server 2005 Service Pack 2 trở đi.

Gợi ý này có hiệu quả chạy DBCC TRACEON (4199)DBCC TRACEOFF (4199)xung quanh truy vấn và kết quả là cần có sự cho phép của sysadmin . Nếu đó là một vấn đề, thêm gợi ý bằng cách sử dụng một hướng dẫn kế hoạch .

Bạn cũng nên xem xét việc kiểm tra toàn bộ hệ thống của mình với 4199 phiên bản được kích hoạt . Hồi quy kế hoạch là có thể, nhưng nhìn chung bạn có thể thấy rằng các bản sửa lỗi tối ưu hóa khác nhau được kích hoạt bởi cờ này rất đáng giá. Tất cả các bản sửa lỗi bộ xử lý truy vấn ảnh hưởng đến kế hoạch trong tương lai đều yêu cầu cờ này để kích hoạt.

Tất cả đã nói ...

Như đã đề cập trong câu trả lời của ypercube , lỗi này yêu cầu hai hoặc nhiều cột tham gia để hiển thị (trong số nhiều chi tiết). Sự dư thừa trong NOT INmệnh đề của bạn làm cho trình tối ưu hóa nhìn thấy hai so sánh cột (mặc dù về mặt logic chỉ có một), do đó phơi bày lỗi.

Loại bỏ sự dư thừa này sẽ 'giải quyết' vấn đề cho truy vấn cụ thể này, mặc dù các truy vấn khác thực sự có nhiều hơn một biến vị ngữ tham gia vẫn sẽ dễ bị tổn thương .

Thí dụ

Để minh họa, đây là một ví dụ dựa trên bài đăng blog CSS được liên kết trong câu hỏi (nhưng với một tập lệnh hoàn chỉnh!):

CREATE TABLE dbo.tst_TAB1
(
    c1 integer NOT NULL, 
    c2 integer NOT NULL, 
    c3 integer NOT NULL
);

CREATE TABLE dbo.tst_TAB2
(
    c1 integer NOT NULL, 
    c2 integer NOT NULL, 
    c3 integer NOT NULL
);

CREATE INDEX i ON dbo.tst_TAB1 (c1, c2);
CREATE INDEX i ON dbo.tst_TAB2 (c1, c2);

Dữ liệu mẫu:

INSERT dbo.tst_TAB1
    (c1, c2, c3)
SELECT 
    number, number, number
FROM master.dbo.spt_values
WHERE 
    [type] = N'P' 
    AND number BETWEEN 1 AND 2047;

INSERT dbo.tst_TAB2 (c1, c2, c3)
VALUES (1, 1, 1);

Kiểm tra truy vấn bằng cách sử dụng NOT INvị ngữ dự phòng:

SELECT
    T1.c1
FROM tst_TAB1 AS t1 
WHERE
    t1.c1 NOT IN 
    (
        SELECT 
            t2.c1 
        FROM tst_TAB2 AS t2
        -- This is redundant!
        WHERE
            t2.c1 = t1.c1
    );

Kế hoạch thực hiện ước tính cho thấy ước tính 1 hàng sau khi chống bán tham gia:

Ước tính 1 hàng

Lưu ý bên lề: Trên thực tế đây là một ví dụ về một lỗi (hiếm) khác. Viết WHEREmệnh đề t1.c1 = t2.c1thay vì t2.c1 = t1.c1cho phép trình tối ưu hóa thấy rằng hai biến vị ngữ nối trên thực tế là giống nhau và lỗi không biểu hiện.

Truy vấn tương tự với OPTION (QUERYTRACEON 4199):

SELECT
    T1.c1
FROM tst_TAB1 AS t1 
WHERE
    t1.c1 NOT IN 
    (
        SELECT 
            t2.c1 
        FROM tst_TAB2 AS t2
        WHERE
            t2.c1 = t1.c1
    )
OPTION (QUERYTRACEON 4199);

Kế hoạch thực hiện ước tính hiện hiển thị ước tính 2046 hàng , điều này hoàn toàn chính xác:

Lập kế hoạch với TF 4199

Chúng ta cũng có thể loại bỏ vị từ thừa:

SELECT
    T1.c1
FROM tst_TAB1 AS t1 
WHERE
    t1.c1 NOT IN 
    (
        SELECT 
            t2.c1 
        FROM tst_TAB2 AS t2
    );

Kế hoạch thực hiện tình cờ sử dụng một tối ưu hóa không liên quan bổ sung (Tổng hợp luồng), nhưng điểm quan trọng là ước tính sau khi tham gia là chính xác mà không phải bật 4199:

Không có vị ngữ dự phòng

Nhiều cột chống bán tham gia

Có thể biểu thị một liên kết chống bán trên nhiều cột bằng NOT INcú pháp. Những trường hợp này sẽ yêu cầu 4199. Ví dụ: truy vấn tiếp theo tham gia vào c1c2:

SELECT
    T1.c1
FROM tst_TAB1 AS t1 
WHERE
    t1.c1 NOT IN 
    (
        SELECT 
            t2.c1 
        FROM tst_TAB2 AS t2
        WHERE
            t2.c2 = t1.c2
    );

Kế hoạch thực hiện cho thấy ước tính 1 hàng sai lầm:

Nhiều cột tham gia

Với 4199, vấn đề được giải quyết:

SELECT
    T1.c1
FROM tst_TAB1 AS t1 
WHERE
    t1.c1 NOT IN 
    (
        SELECT 
            t2.c1 
        FROM tst_TAB2 AS t2
        WHERE
            t2.c2 = t1.c2
    )
OPTION (QUERYTRACEON 4199);

Nhiều cột tham gia với 4199

Các cú pháp khác

Sử dụng NOT INtheo cách này là tốt nhất nên tránh, nhất là vì những lý do được đề cập trong Sách trực tuyến:

Cảnh báo BOL

Vấn đề đó với NOT INNULLsđã được viết về nhiều lần. Có nhiều cú pháp thay thế có sẵn, trong đó NOT EXISTSlà sở thích cá nhân của tôi. Lưu ý rằng việc thay đổi cú pháp sẽ không tránh được lỗi ước tính cardinality:

SELECT
    T1.c1
FROM dbo.tst_TAB1 AS t1 
WHERE 
    NOT EXISTS 
    ( 
        SELECT 1 
        FROM dbo.tst_TAB2 AS t2 
        WHERE 
            t2.c1 = t1.c1 
            AND t2.c2 = t1.c2
    );

Hai cột chống bán tham gia đó tạo ra ước tính 1 hàng và yêu cầu 4199 để sửa nó. Các kế hoạch thực hiện giống hệt như đã thấy trước đây, vì vậy tôi sẽ không lặp lại chúng. Các NOT EXISTScú pháp không tránh được những NULLsvấn đề với NOT IN.

Những quan sát khác

Tôi đồng ý với những quan sát khác của ypercube.

  • Rắc NOLOCKgợi ý trên mỗi bảng trong một truy vấn là một mùi mã xấu. Nếu truy vấn có thể thực sự chấp nhận READ UNCOMMITTEDngữ nghĩa giao dịch, hãy đặt mức cô lập rõ ràng.

  • TOPkhông có ORDER BYlà một dấu hiệu khác của mã kém. TOPđòi hỏi một ORDER BYmệnh đề để định TOPnghĩa nghĩa là gì Không bao giờ dựa vào hành vi được quan sát, sử dụng mức cao nhất rõ ràng ORDER BYđể có được sự đảm bảo.

  • INNER LOOP JOINvà tham gia gợi ý nói chung, ngụ ý một FORCE ORDERgợi ý truy vấn. Điều này hạn chế nghiêm trọng sự tự do của trình tối ưu hóa, và thường bị hiểu lầm và áp dụng sai. Không bao giờ sử dụng gợi ý mà bạn không hiểu đầy đủ.


6

Liên kết bạn cung cấp nói rằng lỗi chỉ ảnh hưởng đến việc tham gia với nhiều hơn một cột:

Lưu ý rằng bạn chỉ gặp phải sự cố này khi nhiều cột tham gia được tham gia vào phép nối như ví dụ trên.

Và tôi không thể hiểu tại sao bạn lại viết NOT INtheo cách này (thêm d.doc2_id = d2a.doc2_idđiều kiện trong truy vấn con.) Nó không cần thiết (trừ khi các cột được tham gia là nullable - chúng có phải không?), Vì vậy bạn có thể viết NOT INnhư sau:

AND d.doc2_id NOT IN (
    SELECT d2a.doc2_id
    FROM assentor.emcsdbuser.doc2_address d2a
    WHERE d2a.address_type_cd = 'FRM'
    )

hoặc với NOT EXISTS:

AND NOT EXISTS (
    SELECT 1
    FROM assentor.emcsdbuser.doc2_address d2a
    WHERE d.doc2_id = d2a.doc2_id
        AND d2a.address_type_cd = 'FRM'
    )

Hãy thử cả hai và kiểm tra xem vấn đề ước tính cardinality đã được chữa khỏi.

Ghi chú khác:

  • Bạn có một chỉ số trên address_type_cd?
  • Tại sao sử dụng nhiều NOLOCK?
  • TOPmà không ORDER BYcó thể cung cấp cho bạn kết quả khác nhau cho mỗi lần thực hiện.
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.