Tại sao việc thêm một TOP 1 làm giảm đáng kể hiệu suất?


39

Tôi có một truy vấn khá đơn giản

SELECT TOP 1 dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER

Đó là mang lại cho tôi hiệu suất khủng khiếp (như không bao giờ bận tâm chờ đợi nó kết thúc). Kế hoạch truy vấn trông như thế này:

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

Tuy nhiên, nếu tôi loại bỏ, TOP 1tôi nhận được một kế hoạch giống như thế này và nó sẽ chạy trong 1-2 giây:

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

Đúng PK & lập chỉ mục dưới đây.

Thực tế là TOP 1kế hoạch truy vấn đã thay đổi không làm tôi ngạc nhiên, tôi chỉ hơi ngạc nhiên khi nó làm cho nó tồi tệ hơn nhiều.

Lưu ý: Tôi đã đọc kết quả từ bài đăng này và hiểu khái niệm về Row Goalv.v ... Điều tôi tò mò là làm thế nào tôi có thể thay đổi truy vấn để nó sử dụng kế hoạch tốt hơn. Hiện tại tôi đang bỏ dữ liệu vào một bảng tạm thời sau đó kéo hàng đầu tiên ra khỏi nó. Tôi đang tự hỏi nếu có một phương pháp tốt hơn.

Chỉnh sửa Đối với những người đọc điều này sau khi thực tế ở đây là một vài thông tin bổ sung.

  • Document_Queue - PK / CI là D_ID và nó có ~ 5k hàng.
  • Tương ứng_Journal - PK / CI là FILE_NUMBER, CORRESPONDENCE_ID và nó có ~ 1,4 triệu hàng.

Khi tôi bắt đầu không có chỉ số nào khác. Tôi đã kết thúc với một trên Corr Corrence_Journal (Document_Id, File_Number)


1
Bạn có ràng buộc khóa ngoài thực thi DOCUMENT_IDmối quan hệ giữa hai bảng (hoặc mỗi bản ghi trong đó CORRESPONDENCE_JOURNALcó bản ghi khớp nhau DOCUMENT_QUEUEkhông)?
Daniel Hutmacher

Câu trả lời:


28

Hãy thử buộc tham gia băm *

SELECT TOP 1 
       dc.DOCUMENT_ID,
       dc.COPIES,
       dc.REQUESTOR,
       dc.D_ID,
       cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
INNER HASH JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
       AND dc.QUEUE_DATE <= GETDATE()
       AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER

Trình tối ưu hóa có thể nghĩ rằng một vòng lặp sẽ tốt hơn với top 1 và điều đó có ý nghĩa nhưng thực tế nó không hoạt động ở đây. Chỉ là một phỏng đoán ở đây nhưng có lẽ chi phí ước tính của bộ đệm đó đã bị tắt - nó sử dụng TEMPDB - bạn có thể có một TEMPDB hoạt động kém.


* Hãy cẩn thận với các gợi ý tham gia , bởi vì chúng buộc thứ tự truy cập bảng kế hoạch khớp với thứ tự bằng văn bản của các bảng trong truy vấn (giống như OPTION (FORCE ORDER)đã được chỉ định). Từ liên kết tài liệu:

Chiết xuất BOL

Điều này có thể không tạo ra bất kỳ hiệu ứng không mong muốn nào trong ví dụ, nhưng nói chung, nó rất có thể. FORCE ORDER(ngụ ý hoặc rõ ràng) là một gợi ý rất mạnh mẽ vượt ra ngoài việc thực thi trật tự; nó ngăn chặn một loạt các kỹ thuật tối ưu hóa được áp dụng, bao gồm tập hợp một phần và sắp xếp lại.

Một gợi ý OPTION (HASH JOIN) truy vấn có thể ít xâm phạm hơn trong các trường hợp phù hợp, vì điều này không ngụ ý FORCE ORDER. Tuy nhiên, nó áp dụng cho tất cả các phép nối trong truy vấn. Các giải pháp khác có sẵn.


1
Có vẻ như câu trả lời đúng và sự khác biệt duy nhất giữa nó và kế hoạch đơn giản hơn là Sắp xếp bổ sung ở phía trước.
Kenneth Fisher

3
Không chắc chắn tôi thích câu trả lời này. Tham gia gợi ý rất xâm lấn. Một số thay đổi lập chỉ mục đơn giản nên được thử trước tiên, ví dụ chỉ mục trên cột ngày.
usr

@usr Đó là một tham gia PK đơn giản chạy trong chưa đầy một giây. Đặt cược khá an toàn ở đây.
paparazzo

4
Khi buộc tham gia băm, bạn buộc phải quét bảng lớn. Có những lựa chọn tốt hơn.
Rob Farley

30

Vì bạn có được kế hoạch chính xác với ORDER BY, có lẽ bạn chỉ có thể cuộn TOPtoán tử của riêng mình ?

SELECT DOCUMENT_ID, COPIES, REQUESTOR, D_ID, FILE_NUMBER
FROM (
    SELECT dc.DOCUMENT_ID,
           dc.COPIES,
           dc.REQUESTOR,
           dc.D_ID,
           cj.FILE_NUMBER,
           ROW_NUMBER() OVER (ORDER BY cj.FILE_NUMBER) AS _rownum
    FROM DOCUMENT_QUEUE dc
    INNER JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
    WHERE dc.QUEUE_DATE <= GETDATE()
      AND dc.PRINT_LOCATION = 2
) AS sub
WHERE _rownum=1;

Trong tâm trí của tôi, kế hoạch truy vấn cho ROW_NUMBER()ở trên nên giống như khi bạn có một ORDER BY. Kế hoạch truy vấn bây giờ sẽ có Phân đoạn, Dự án tuần tự và cuối cùng là toán tử Bộ lọc, phần còn lại sẽ trông giống như kế hoạch tốt của bạn.


3
Trên thực tế, mặc dù nó đã cung cấp cho nhà điều hành hàng đầu (và một loạt các công cụ khác (một dự án chuỗi, phân khúc và sắp xếp)), nó vẫn chạy thứ hai. Tôi sẽ đưa ra câu trả lời chính xác cho @frisbee mặc dù là lần đầu tiên và nó đơn giản hơn. Câu trả lời tuyệt vời mặc dù.
Kenneth Fisher

10
@KennethFisher, câu trả lời của frĩaee đơn giản hơn, nhưng theo cách một chiếc búa tạ điều khiển một chiếc đinh hoàn thiện đơn giản hơn một chiếc búa đóng khung tiêu chuẩn. Nó cũng đi kèm với rất nhiều rủi ro, đặc biệt là nếu để tại chỗ cho đường dài. Tôi sẽ không sử dụng các gợi ý như thế ngoại trừ trong thử nghiệm hoặc có thể, CÓ THỂ ngoại lệ bên lề.
Steve Mangiameli

@SteveMangiameli Trong trường hợp cụ thể này chỉ có một người tham gia nên một số mối quan tâm biến mất. Tôi nhận thức được những rủi ro khi sử dụng gợi ý tham gia (hoặc gợi ý truy vấn) Tôi chỉ nghĩ rằng nó hợp lý trong trường hợp này.
Kenneth Fisher

5
@KennethFisher Imo, rủi ro chính của gợi ý truy vấn là khi dữ liệu của bạn tăng hoặc thay đổi, kế hoạch truy vấn bạn thực thi có thể trở nên tồi tệ hơn những gì hệ thống tự tìm thấy. Bạn đã thấy làm thế nào một lỗi nhỏ trong kế hoạch có thể ảnh hưởng nghiêm trọng đến hiệu suất. Sử dụng một gợi ý trong sản xuất là tuyên bố, "Tôi biết kế hoạch này sẽ luôn luôn, luôn luôn là tốt nhất bởi vì tôi rất hiểu kế hoạch và cách dữ liệu của tôi sẽ hoạt động trong suốt vòng đời của truy vấn này trong sản xuất." Tôi chưa bao giờ tự tin về một truy vấn.
jpmc26

29

Chỉnh sửa: +1 hoạt động trong tình huống này vì hóa ra đó FILE_NUMBERlà phiên bản chuỗi không đệm của một số nguyên. Một giải pháp tốt hơn ở đây cho các chuỗi là nối thêm ''(chuỗi trống), vì việc nối thêm một giá trị có thể ảnh hưởng đến thứ tự hoặc cho các số để thêm một thứ không đổi nhưng chứa hàm không xác định, chẳng hạn như sign(rand()+1). Ý tưởng 'phá vỡ sự sắp xếp' vẫn còn hiệu lực ở đây, chỉ là phương pháp của tôi không lý tưởng.

+1

Không, tôi không có nghĩa là tôi đồng ý với bất cứ điều gì, tôi có nghĩa là đó là một giải pháp. Nếu bạn thay đổi truy vấn của bạn để ORDER BY cj.FILE_NUMBER + 1sau đó TOP 1sẽ hành xử khác nhau.

Bạn thấy, với mục tiêu hàng nhỏ thay thế cho một truy vấn có thứ tự, hệ thống sẽ cố gắng tiêu thụ dữ liệu theo thứ tự, để tránh có toán tử Sắp xếp. Nó cũng sẽ tránh xây dựng bảng băm, cho rằng có lẽ nó không phải làm quá nhiều việc để tìm hàng đầu tiên. Trong trường hợp của bạn, điều này là sai - từ độ dày của những mũi tên đó, có vẻ như nó phải tiêu thụ rất nhiều dữ liệu để tìm một kết quả khớp duy nhất.

Độ dày của các mũi tên đó cho thấy DOCUMENT_QUEUEbảng (DQ) của bạn nhỏ hơn nhiều so với CORRESPONDENCE_JOURNALbảng (CJ) của bạn . Và kế hoạch tốt nhất thực sự sẽ là kiểm tra các hàng DQ cho đến khi tìm thấy một hàng CJ. Thật vậy, đó là những gì Trình tối ưu hóa truy vấn (QO) sẽ làm nếu nó không có vấn đề này ORDER BYtrong đó, được hỗ trợ độc đáo bởi một chỉ số bao trùm trên CJ.

Vì vậy, nếu bạn bỏ ORDER BYhoàn toàn, tôi hy vọng bạn sẽ có được một kế hoạch liên quan đến Vòng lặp lồng nhau, lặp lại qua các hàng trong DQ, tìm kiếm vào CJ để đảm bảo hàng tồn tại. Và với TOP 1, điều này sẽ dừng lại sau khi một hàng duy nhất đã được kéo.

Nhưng nếu bạn thực sự cần hàng đầu tiên theo FILE_NUMBERthứ tự, thì bạn có thể lừa hệ thống bỏ qua chỉ số đó có vẻ (không chính xác) rất hữu ích, bằng cách thực hiện ORDER BY CJ.FILE_NUMBER+1- mà chúng ta biết sẽ giữ trật tự như trước, nhưng quan trọng là QO không. QO sẽ tập trung vào việc hoàn thành toàn bộ, để có thể thỏa mãn toán tử Top N Sort. Phương pháp này sẽ tạo ra một kế hoạch có chứa toán tử vô hướng tính toán để tìm ra giá trị để đặt hàng và toán tử Top N Sort để có được hàng đầu tiên. Nhưng ở bên phải của những thứ này, bạn sẽ thấy một Nested Loop đẹp, thực hiện nhiều Tìm kiếm trên CJ. Và hiệu suất tốt hơn so với việc chạy qua một bảng lớn các hàng không khớp với bất cứ thứ gì trong DQ.

Hash Match không nhất thiết phải khủng khiếp, nhưng nếu tập hợp các hàng bạn trở về từ DQ nhỏ hơn so với CJ (như tôi mong đợi), thì Hash Match sẽ quét nhiều hơn về CJ hơn nó cần

Lưu ý: Tôi đã sử dụng +1 thay vì +0 vì trình tối ưu hóa truy vấn có khả năng nhận ra rằng +0 không thay đổi gì. Tất nhiên, điều tương tự có thể áp dụng cho +1, nếu không phải bây giờ, thì tại một thời điểm nào đó trong tương lai.


7

Tôi đã đọc kết quả từ bài đăng này và hiểu khái niệm về Mục tiêu hàng, v.v. Điều tôi tò mò là làm thế nào tôi có thể thay đổi truy vấn để nó sử dụng kế hoạch tốt hơn

Việc thêm OPTION (QUERYTRACEON 4138)tắt chỉ có tác dụng của các mục tiêu hàng cho truy vấn đó mà không quá quy định về kế hoạch cuối cùng và có lẽ sẽ là cách đơn giản nhất / trực tiếp nhất.

Nếu việc thêm gợi ý này cung cấp cho bạn một lỗi quyền (bắt buộc DBCC TRACEON), bạn có thể áp dụng nó bằng hướng dẫn kế hoạch:

Sử dụng QUERYTRACEONtrong hướng dẫn kế hoạch của spaghettidba

... Hoặc chỉ sử dụng một thủ tục được lưu trữ:

Có gì Quyền không QUERYTRACEONCần? của Kendra Little


3

Các phiên bản SQL Server mới hơn cung cấp các tùy chọn khác nhau (và có thể tốt hơn) để xử lý các truy vấn có hiệu suất dưới mức tối ưu khi trình tối ưu hóa có thể áp dụng tối ưu hóa mục tiêu hàng. SQL Server 2016 SP1 đã giới thiệu DISABLE_OPTIMIZER_ROWGOAL USE HINTcái có tác dụng tương tự như cờ theo dõi 4138. Nếu bạn không ở phiên bản đó, bạn cũng có thể xem xét sử dụng OPTIMIZE FORgợi ý truy vấn để có một gói truy vấn được thiết kế để trả về tất cả các hàng thay vì chỉ 1. Truy vấn bên dưới sẽ trả về kết quả giống như câu hỏi trong câu hỏi nhưng nó sẽ không được tạo với mục tiêu chỉ nhận được 1 hàng.

DECLARE @top INT = 1;

SELECT TOP (@top) dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
OPTION (OPTIMIZE FOR (@top = 987654321));

2

Vì bạn đang làm TOP(1), tôi khuyên bạn nên ORDER BYbắt đầu xác định. Ít nhất điều này sẽ đảm bảo kết quả có thể dự đoán được về mặt chức năng (luôn hữu ích cho kiểm tra hồi quy). Có vẻ như bạn cần thêm DC.D_IDCJ.CORRESPONDENCE_IDcho điều đó.

Khi xem xét các kế hoạch truy vấn, đôi khi tôi thấy nó mang tính hướng dẫn để đơn giản hóa truy vấn: Có thể chọn trước tất cả các hàng dc có liên quan vào bảng tạm thời, để loại bỏ các vấn đề với ước tính cardinality trên QUEUE_DATEPRINT_LOCATION. Điều này sẽ được nhanh chóng đưa ra số lượng hàng thấp. Sau đó, bạn có thể thêm chỉ mục vào bảng tạm thời này nếu cần mà không thay đổi bảng cố định.

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.