NẾU EXISTS mất nhiều thời gian hơn câu lệnh chọn nhúng


35

Khi tôi chạy đoạn mã sau, phải mất 22,5 phút và 106 triệu lượt đọc. Tuy nhiên, nếu tôi chỉ chạy câu lệnh chọn bên trong thì chỉ mất 15 giây và đọc được 264k. Là một lưu ý phụ, truy vấn chọn không trả về hồ sơ.

Bất cứ ý tưởng tại sao IF EXISTSsẽ làm cho nó chạy lâu hơn và đọc nhiều hơn nữa? Tôi cũng thay đổi câu lệnh chọn để làm SELECT TOP 1 [dlc].[id]và tôi đã giết nó sau 2 phút.

Để khắc phục tạm thời, tôi đã thay đổi nó để đếm (*) và gán giá trị đó cho một biến @cnt. Sau đó, nó làm một IF 0 <> @cnttuyên bố. Nhưng tôi nghĩ EXISTSsẽ tốt hơn, bởi vì nếu có các bản ghi được trả về trong câu lệnh chọn, nó sẽ dừng thực hiện quét / tìm kiếm một khi nó tìm thấy ít nhất một bản ghi, trong khi đó count(*)sẽ hoàn thành truy vấn đầy đủ. Tôi đang thiếu gì?

IF EXISTS
   (SELECT [dlc].[ID]
   FROM TableDLC [dlc]
   JOIN TableD [d]
   ON [d].[ID] = [dlc].[ID]
   JOIN TableC [c]
   ON [c].[ID] = [d].[ID2]
   WHERE [c].[Name] <> [dlc].[Name])
BEGIN
   <do something>
END

4
Để tránh vấn đề mục tiêu hàng, một ý tưởng khác (chưa được kiểm tra, hãy nhớ bạn!) Có thể là thử nghịch đảo - IF NOT EXISTS (...) BEGIN END ELSE BEGIN <do something> END.
Aaron Bertrand

Câu trả lời:


32

Bất cứ ý tưởng tại sao IF EXISTSsẽ làm cho nó chạy lâu hơn và đọc nhiều hơn nữa? Tôi cũng thay đổi câu lệnh chọn để làm SELECT TOP 1 [dlc].[id]và tôi đã giết nó sau 2 phút.

Như tôi đã giải thích trong câu trả lời của mình cho câu hỏi liên quan này:

TOP (và tại sao) tác động lên kế hoạch thực hiện như thế nào?

Sử dụng EXISTSgiới thiệu một mục tiêu hàng, trong đó trình tối ưu hóa tạo ra một kế hoạch thực hiện nhằm xác định vị trí hàng đầu tiên một cách nhanh chóng. Khi làm điều này, nó giả định rằng dữ liệu được phân phối đồng đều. Ví dụ: nếu số liệu thống kê cho thấy có 100 trận đấu dự kiến ​​trong 100.000 hàng, nó sẽ cho rằng nó sẽ chỉ phải đọc 1.000 hàng để tìm trận đấu đầu tiên.

Điều này sẽ dẫn đến thời gian thực hiện lâu hơn dự kiến ​​nếu giả định này hóa ra là bị lỗi. Ví dụ: nếu SQL Server chọn phương thức truy cập (ví dụ: quét không có thứ tự) xảy ra để xác định giá trị khớp đầu tiên rất muộn trong tìm kiếm, thì nó có thể dẫn đến việc quét gần như hoàn tất. Mặt khác, nếu tìm thấy một hàng phù hợp trong số một vài hàng đầu tiên, hiệu suất sẽ rất tốt. Đây là rủi ro cơ bản với các mục tiêu hàng - hiệu suất không nhất quán.

Để khắc phục tạm thời, tôi đã thay đổi nó để đếm (*) và gán giá trị đó cho một biến

Thông thường có thể định dạng lại truy vấn sao cho mục tiêu hàng không được chỉ định. Không có mục tiêu hàng, truy vấn vẫn có thể kết thúc khi gặp hàng phù hợp đầu tiên (nếu được viết chính xác), nhưng chiến lược kế hoạch thực hiện có thể sẽ khác (và hy vọng, hiệu quả hơn). Rõ ràng, tính (*) sẽ yêu cầu đọc tất cả các hàng, vì vậy nó không phải là một sự thay thế hoàn hảo.

Nếu bạn đang chạy SQL Server 2008 R2 trở lên, bạn cũng có thể thường sử dụng cờ theo dõi được tài liệu và được hỗ trợ 4138 để có kế hoạch thực hiện mà không có mục tiêu hàng. Cờ này cũng có thể được chỉ định bằng cách sử dụng gợi ý được hỗ trợ OPTION (QUERYTRACEON 4138) , mặc dù lưu ý rằng nó yêu cầu quyền sysadmin thời gian chạy , trừ khi được sử dụng với hướng dẫn kế hoạch.

không may

Không có điều nào ở trên là chức năng với một IF EXISTStuyên bố có điều kiện. Nó chỉ áp dụng cho DML thông thường. Nó sẽ hoạt động với SELECT TOP (1)công thức thay thế mà bạn đã thử. Điều đó có thể tốt hơn so với việc sử dụng COUNT(*), phải tính tất cả các hàng đủ điều kiện, như đã đề cập trước đó.

Điều đó nói rằng, có bất kỳ cách nào để thể hiện yêu cầu này sẽ cho phép bạn tránh hoặc kiểm soát mục tiêu hàng, trong khi chấm dứt tìm kiếm sớm. Một ví dụ cuối cùng:

DECLARE @Exists bit;

SELECT @Exists =
    CASE
        WHEN EXISTS
        (
            SELECT [dlc].[ID]
            FROM TableDLC [dlc]
            JOIN TableD [d]
            ON [d].[ID] = [dlc].[ID]
            JOIN TableC [c]
            ON [c].[ID] = [d].[ID2]
            WHERE [c].[Name] <> [dlc].[Name]
        )
        THEN CONVERT(bit, 1)
        ELSE CONVERT(bit, 0)
    END
OPTION (QUERYTRACEON 4138);

IF @Exists = 1
BEGIN
    ...
END;

Ví dụ alt bạn cung cấp đã chạy trong 3,75 phút và thực hiện 46m lượt đọc. Vì vậy, trong khi nhanh hơn truy vấn ban đầu của tôi, tôi nghĩ trong trường hợp này tôi sẽ gắn bó với @cnt = Count (*) và đánh giá biến sau đó. Đặc biệt vì 99% thời gian này sẽ không có gì trong đó. Có vẻ như dựa trên câu trả lời của bạn và Rob rằng Tồn tại chỉ tốt nếu bạn thực sự mong đợi một loại kết quả nào đó và kết quả đó được phân bổ đều trong dữ liệu của bạn.
Chris Woods

3
@ChrisWoods: Bạn nói "Đặc biệt vì 99% thời gian này sẽ không có gì trong đó". Điều này khá nhiều đảm bảo rằng mục tiêu hàng của một người là một ý tưởng tồi, vì bạn mong đợi thường không có hàng nào và phải quét mọi thứ để thấy rằng không có bất kỳ mục nào. Nếu bạn không thể thêm một số chỉ mục thông minh, hãy gắn với COUNT (*).
Ross Presser

25

Bởi vì EXISTS chỉ cần tìm một hàng duy nhất, nên nó sẽ sử dụng mục tiêu của một hàng. Điều này đôi khi có thể tạo ra một kế hoạch ít lý tưởng hơn. Nếu bạn mong đợi nó sẽ giống như vậy đối với bạn, hãy điền một biến có kết quả là a COUNT(*)và sau đó kiểm tra biến đó để xem nó có hơn 0 không.

Vì vậy, ... với một mục tiêu hàng nhỏ, nó sẽ tránh được các hoạt động chặn, chẳng hạn như xây dựng bảng băm hoặc sắp xếp các luồng có thể hữu ích cho các phép nối, bởi vì nó sẽ cho thấy rằng nó sẽ bị ràng buộc để tìm ra thứ gì đó khá nhanh, và do đó các vòng lặp lồng nhau sẽ là tốt nhất nếu nó tìm thấy một cái gì đó. Ngoại trừ việc này có thể làm cho một kế hoạch tồi tệ hơn nhiều trên toàn bộ. Nếu việc tìm một hàng đơn nhanh chóng, bạn sẽ thích phương pháp này để tránh các khối ...

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.