Tại sao chọn tất cả các cột kết quả của truy vấn này nhanh hơn chọn một cột tôi quan tâm?


13

Tôi có một truy vấn trong đó việc sử dụng select *không chỉ đọc ít hơn rất nhiều mà còn sử dụng thời gian CPU ít hơn đáng kể so với sử dụng select c.Foo.

Đây là truy vấn:

select top 1000 c.ID
from ATable a
    join BTable b on b.OrderKey = a.OrderKey and b.ClientId = a.ClientId
    join CTable c on c.OrderId = b.OrderId and c.ShipKey = a.ShipKey
where (a.NextAnalysisDate is null or a.NextAnalysisDate < @dateCutOff)
    and b.IsVoided = 0
    and c.ComplianceStatus in (3, 5)
    and c.ShipmentStatus in (1, 5, 6)
order by a.LastAnalyzedDate

Điều này kết thúc với 2.473.658 lượt đọc logic, chủ yếu trong Bảng B. Nó sử dụng 26.562 CPU và có thời lượng 7.965.

Đây là kế hoạch truy vấn được tạo:

Lập kế hoạch từ việc chọn giá trị của một cột Trên PasteThePlan: https://www.brentozar.com/pastetheplan/?id=BJAp2mQIQ

Khi tôi thay đổi c.IDđể *, truy vấn kết thúc với 107.049 logic đọc, khá đồng đều lây lan giữa tất cả ba bảng. Nó sử dụng CPU 4.266 và có thời lượng 1.147.

Đây là kế hoạch truy vấn được tạo:

Kế hoạch từ việc chọn tất cả các giá trị Trên PasteThePlan: https://www.brentozar.com/pastetheplan/?id=SyZYn7QUQ

Tôi đã cố gắng sử dụng các gợi ý truy vấn được đề xuất bởi Joe Obbish, với các kết quả sau:
select c.IDkhông có gợi ý: https://www.brentozar.com/pastetheplan/?id=SJfBdOELm
select c.ID với gợi ý: https://www.brentozar.com/pastetheplan/ ? id = B1W ___ N87
select * không có gợi ý: https://www.brentozar.com/pastetheplan/?id=HJ6qddEIm
select * với gợi ý: https://www.brentozar.com/pastetheplan/?id=rJhhudNIQ

Sử dụng OPTION(LOOP JOIN)gợi ý với select c.IDviệc giảm đáng kể số lần đọc so với phiên bản mà không có gợi ý, nhưng nó vẫn đang thực hiện khoảng 4 lần số lần đọc select *truy vấn mà không có bất kỳ gợi ý nào. Thêm OPTION(RECOMPILE, HASH JOIN)vào select *truy vấn làm cho nó thực hiện tồi tệ hơn nhiều so với bất kỳ điều gì khác mà tôi đã thử.

Sau khi cập nhật số liệu thống kê trên các bảng và chỉ mục của chúng bằng cách sử dụng WITH FULLSCAN, select c.IDtruy vấn sẽ chạy nhanh hơn nhiều:
select c.IDtrước khi cập nhật: https://www.brentozar.com/pastetheplan/?id=SkiYoOEUm
select * trước khi cập nhật: https://www.brentozar.com/ pastetheplan /? id = ryrvodEUX
select c.ID sau khi cập nhật: https://www.brentozar.com/pastetheplan/?id=B1MRoO487
select * sau khi cập nhật: https://www.brentozar.com/pastetheplan/?id=Hk7si_V8m

select *vẫn vượt trội select c.IDvề tổng thời lượng và tổng số lần đọc ( select *có khoảng một nửa số lần đọc) nhưng nó sử dụng nhiều CPU hơn. Nhìn chung, chúng gần hơn nhiều so với trước khi cập nhật, tuy nhiên các kế hoạch vẫn khác nhau.

Hành vi tương tự được nhìn thấy vào năm 2016 chạy trong chế độ Tương thích 2014 và vào năm 2014. Điều gì có thể giải thích cho sự chênh lệch giữa hai kế hoạch? Có thể là các chỉ mục "chính xác" chưa được tạo ra? Số liệu thống kê có thể hơi lỗi thời gây ra điều này?

Tôi đã cố gắng di chuyển các vị từ lên đến ONmột phần của phép nối, theo nhiều cách, nhưng kế hoạch truy vấn là giống nhau mỗi lần.

Sau khi xây dựng lại chỉ số

Tôi xây dựng lại tất cả các chỉ mục trên ba bảng liên quan đến truy vấn. c.IDvẫn đang đọc nhiều nhất (gấp đôi số lần *), nhưng mức sử dụng CPU chỉ bằng một nửa *phiên bản. Các c.IDphiên bản cũng đổ vào tempdb trên việc phân loại ATable:
c.ID: https://www.brentozar.com/pastetheplan/?id=HyHIeDO87
* : https://www.brentozar.com/pastetheplan/?id=rJ4deDOIQ

Tôi cũng đã cố gắng buộc nó hoạt động mà không có sự song song và điều đó đã cho tôi truy vấn hoạt động tốt nhất: https://www.brentozar.com/pastetheplan/?id=SJn9-vuLX

Tôi nhận thấy số lần thực thi của các toán tử SAU khi tìm kiếm chỉ mục lớn đang thực hiện lệnh chỉ được thực hiện 1.000 lần trong phiên bản đơn luồng, nhưng đã làm nhiều hơn đáng kể trong phiên bản Song song, giữa 2.622 và 4.315 lần thực thi của các toán tử khác nhau.

Câu trả lời:


4

Đúng là việc chọn nhiều cột hơn ngụ ý rằng SQL Server có thể cần phải làm việc chăm chỉ hơn để có được kết quả truy vấn được yêu cầu. Nếu trình tối ưu hóa truy vấn có thể đưa ra kế hoạch truy vấn hoàn hảo cho cả hai truy vấn thì sẽ hợp lý khi mong đợiSELECT *truy vấn để chạy lâu hơn truy vấn chọn tất cả các cột từ tất cả các bảng. Bạn đã quan sát điều ngược lại cho cặp truy vấn của bạn. Bạn cần cẩn thận khi so sánh chi phí, nhưng truy vấn chậm có tổng chi phí ước tính là 1090,08 đơn vị tối ưu hóa và truy vấn nhanh có tổng chi phí ước tính là 6823,11 đơn vị tối ưu hóa. Trong trường hợp này, có thể nói rằng trình tối ưu hóa thực hiện công việc kém với ước tính tổng chi phí truy vấn. Nó đã chọn một kế hoạch khác cho truy vấn SELECT * của bạn và dự kiến ​​kế hoạch đó sẽ đắt hơn, nhưng đó không phải là trường hợp ở đây. Loại không phù hợp đó có thể xảy ra vì nhiều lý do và một trong những nguyên nhân phổ biến nhất là các vấn đề ước tính về tim mạch. Chi phí khai thác phần lớn được xác định bởi các ước tính cardinality. Nếu ước tính cardinality tại một điểm quan trọng trong kế hoạch là không chính xác thì tổng chi phí của kế hoạch có thể không phản ánh đúng thực tế. Đây là một sự đơn giản hóa quá mức nhưng tôi hy vọng rằng nó sẽ hữu ích cho việc hiểu những gì đang diễn ra ở đây.

Hãy bắt đầu bằng cách thảo luận tại sao một SELECT *truy vấn có thể đắt hơn so với việc chọn một cột duy nhất. Các SELECT *truy vấn có thể biến một số chỉ số bao gồm chỉ số thành noncovering, mà có thể có nghĩa là tôi ưu hoa cần phải làm công việc bổ sung để có được tất cả các cột cần thiết hoặc nó có thể cần phải đọc từ một chỉ số lớn hơn.SELECT *cũng có thể dẫn đến các tập kết quả trung gian lớn hơn cần được xử lý trong khi thực hiện truy vấn. Bạn có thể thấy điều này trong thực tế bằng cách xem kích thước hàng ước tính trong cả hai truy vấn. Trong truy vấn nhanh, kích thước hàng của bạn nằm trong khoảng từ 664 byte đến 3019 byte. Trong truy vấn chậm, kích thước hàng của bạn nằm trong khoảng từ 19 đến 36 byte. Các toán tử chặn như sắp xếp hoặc xây dựng hàm băm sẽ có chi phí cao hơn cho dữ liệu với kích thước hàng lớn hơn vì SQL Server biết rằng sẽ tốn kém hơn khi sắp xếp lượng dữ liệu lớn hơn hoặc biến nó thành bảng băm.

Nhìn vào truy vấn nhanh, trình tối ưu hóa ước tính rằng nó cần thực hiện 2,4 triệu chỉ mục tìm kiếm Database1.Schema1.Object5.Index3. Đó là nơi mà hầu hết các chi phí kế hoạch đến từ. Tuy nhiên, kế hoạch thực tế cho thấy chỉ có 1332 chỉ số tìm kiếm được thực hiện trên toán tử đó. Nếu bạn so sánh thực tế với các hàng ước tính cho các phần bên ngoài của các vòng lặp đó, bạn sẽ thấy sự khác biệt lớn. Trình tối ưu hóa nghĩ rằng sẽ cần nhiều tìm kiếm chỉ mục hơn để tìm 1000 hàng đầu tiên cần thiết cho kết quả của truy vấn. Đó là lý do tại sao truy vấn có kế hoạch chi phí tương đối cao nhưng kết thúc quá nhanh: nhà điều hành được dự đoán là đắt nhất đã thực hiện dưới 0,1% công việc dự kiến ​​của mình.

Nhìn vào truy vấn chậm, bạn nhận được một kế hoạch với hầu hết các phép nối băm (tôi tin rằng phép nối vòng lặp ở đó chỉ để xử lý biến cục bộ). Ước tính cardinality chắc chắn không hoàn hảo, nhưng vấn đề ước tính thực sự duy nhất là ở cuối cùng với loại. Tôi nghi ngờ phần lớn thời gian được dành cho việc quét các bảng với hàng trăm triệu hàng.

Bạn có thể thấy hữu ích khi thêm gợi ý truy vấn cho cả hai phiên bản truy vấn để buộc kế hoạch truy vấn được liên kết với phiên bản khác. Gợi ý truy vấn có thể là một công cụ tốt để tìm ra lý do tại sao trình tối ưu hóa đưa ra một số lựa chọn của nó. Nếu bạn thêm OPTION (RECOMPILE, HASH JOIN)vào SELECT *truy vấn, tôi hy vọng bạn sẽ thấy một kế hoạch truy vấn tương tự với truy vấn băm. Tôi cũng hy vọng rằng chi phí truy vấn sẽ cao hơn nhiều cho kế hoạch tham gia băm vì kích thước hàng của bạn lớn hơn nhiều. Vì vậy, đó có thể là lý do tại sao truy vấn băm không được chọn cho SELECT *truy vấn. Nếu bạn thêm OPTION (LOOP JOIN)vào truy vấn chỉ chọn một cột, tôi hy vọng bạn sẽ thấy một gói truy vấn tương tự như truy vấn choSELECT *truy vấn. Trong trường hợp này, việc giảm kích thước hàng không nên ảnh hưởng nhiều đến chi phí truy vấn chung. Bạn có thể bỏ qua các tra cứu quan trọng nhưng đó là một tỷ lệ nhỏ trong chi phí ước tính.

Tóm lại, tôi hy vọng rằng các kích thước hàng lớn hơn cần thiết để đáp ứng SELECT *truy vấn sẽ đẩy trình tối ưu hóa về phía kế hoạch nối vòng thay vì kế hoạch nối băm. Kế hoạch tham gia vòng lặp có chi phí cao hơn so với dự kiến ​​do các vấn đề ước tính về tim mạch. Giảm kích thước hàng bằng cách chỉ chọn một cột giúp giảm đáng kể chi phí của gói tham gia băm nhưng có lẽ sẽ không ảnh hưởng nhiều đến chi phí cho kế hoạch tham gia vòng lặp, vì vậy bạn kết thúc với kế hoạch tham gia băm ít hiệu quả hơn. Thật khó để nói nhiều hơn điều này cho một kế hoạch ẩn danh.


Cảm ơn bạn rất nhiều vì câu trả lời mở rộng và nhiều thông tin của bạn. Tôi đã thử thêm các gợi ý mà bạn đề xuất. Nó đã làm cho select c.IDtruy vấn nhanh hơn nhiều, nhưng nó vẫn đang thực hiện một số công việc bổ sung mà select *truy vấn, không có gợi ý, thực hiện.
L. Miller

2

Số liệu thống kê cũ chắc chắn có thể khiến trình tối ưu hóa chọn phương pháp tìm dữ liệu kém. Bạn đã thử làm một UPDATE STATISTICS ... WITH FULLSCANhoặc làm đầy đủ REBUILDvề chỉ số? Hãy thử điều đó và xem nếu nó giúp.

CẬP NHẬT

Theo cập nhật từ OP:

Sau khi cập nhật số liệu thống kê trên các bảng và chỉ mục của chúng bằng cách sử dụng WITH FULLSCAN, select c.IDtruy vấn đang chạy nhanh hơn nhiều

Vì vậy, bây giờ, nếu hành động duy nhất được thực hiện UPDATE STATISTICS, thì hãy thử thực hiện một chỉ mục REBUILD(không phải REORGANIZE) như tôi đã thấy sự trợ giúp đó với số lượng hàng ước tính mà cả hai UPDATE STATISTICSvà chỉ số REORGANIZEkhông làm được.


Tôi đã có thể lấy tất cả các chỉ mục trên ba bảng liên quan để xây dựng lại vào cuối tuần và đã cập nhật bài viết của mình để phản ánh những kết quả đó.
L. Miller

-1
  1. Bạn có thể vui lòng bao gồm các tập lệnh chỉ mục?
  2. Bạn đã loại bỏ các vấn đề có thể xảy ra với "đánh hơi thông số" chưa? https://www.mssqltips.com/sqlservertip/3257/different-approaches-to-c chính-sql-server-parameter-sniffing /
  3. Tôi đã thấy kỹ thuật này hữu ích trong một số trường hợp:
    a) viết lại mỗi bảng dưới dạng truy vấn con, theo các quy tắc sau:
    b) CHỌN - đặt các cột tham gia trước
    c) PREDICATE - di chuyển vào các truy vấn con tương ứng của chúng
    d) ĐẶT HÀNG B --NG - di chuyển vào truy vấn con tương ứng, sắp xếp trên THAM GIA CẦU THỦ ĐẦU TIÊN
    e) Thêm truy vấn trình bao cho sắp xếp cuối cùng và CHỌN.

Ý tưởng là sắp xếp trước các cột tham gia bên trong mỗi mục chọn, đặt các cột tham gia trước trong mỗi danh sách chọn.

Ý tôi là đây ....

SELECT ... wrapper query
FROM
(
    SELECT ...
    FROM
        (SELECT ClientID, ShipKey, NextAnalysisDate
         FROM ATABLE
         WHERE (a.NextAnalysisDate is null or a.NextAnalysisDate < @dateCutOff) -- Predicates
         ORDER BY OrderKey, ClientID, LastAnalyzedDate  ---- Pre-sort the join columns
        ) as a
        JOIN 
        (SELECT OrderKey, ClientID, OrderID, IsVoided
         FROM BTABLE
         WHERE IsVoided = 0             ---- Include all predicates
         ORDER BY OrderKey, OrderID, IsVoided       ---- Pre-sort the join columns
        ) as b ON b.OrderKey = a.OrderKey and b.ClientId = a.ClientId
        JOIN
        (SELECT OrderID, ShipKey, ComplianceStatus, ShipmentStatus, ID
         FROM CTABLE
         WHERE ComplianceStatus in (3, 5)       ---- Include all predicates
             AND ShipmentStatus in (1, 5, 6)        ---- Include all predicates
         ORDER BY OrderID, ShipKey          ---- Pre-sort the join columns
        ) as c ON c.OrderId = b.OrderId and c.ShipKey = a.ShipKey
) as d
ORDER BY d.LastAnalyzedDate

1
1. Tôi sẽ cố gắng thêm các tập lệnh DDL chỉ mục vào bài viết gốc, có thể mất một lúc để "xóa" chúng. 2. Tôi đã kiểm tra khả năng này bằng cách xóa cả bộ đệm của kế hoạch trước khi chạy và bằng cách thay thế tham số liên kết bằng một giá trị thực. 3. Tôi đã thử điều này, nhưng ORDER BYkhông hợp lệ trong truy vấn con không có TOP, FORXML, v.v. Tôi đã thử nó mà không có các ORDER BYmệnh đề nhưng đó là cùng một kế hoạch.
L. Miller
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.