Truy vấn tạm dừng sau khi trả về một số hàng cố định


8

Tôi có chế độ xem chạy nhanh (vài giây) cho tối đa 41 bản ghi (ví dụ TOP 41:) nhưng mất vài phút cho 44 bản ghi trở lên, với kết quả trung gian nếu chạy với TOP 42hoặc TOP 43. Cụ thể, nó sẽ trả lại 39 bản ghi đầu tiên trong vài giây, sau đó tạm dừng gần ba phút trước khi trả lại các bản ghi còn lại. Mẫu này giống nhau khi truy vấn TOP 44hoặc TOP 100.

Chế độ xem này ban đầu xuất phát từ chế độ xem cơ sở, thêm vào cơ sở chỉ một bộ lọc, bộ lọc cuối cùng trong mã bên dưới. Dường như không có sự khác biệt nếu tôi xâu chuỗi khung nhìn con từ cơ sở hoặc nếu tôi viết khung nhìn con với mã từ cơ sở được xếp hàng. Chế độ xem cơ sở trả về 100 bản ghi chỉ trong vài giây. Tôi muốn nghĩ rằng tôi có thể khiến cho chế độ xem trẻ em chạy nhanh như cơ sở, không chậm hơn 50 lần. Có ai nhìn thấy loại hành vi này? Bất kỳ dự đoán là nguyên nhân hoặc giải quyết?

Hành vi này đã được nhất quán trong vài giờ qua khi tôi đã kiểm tra các truy vấn có liên quan, mặc dù số lượng hàng được trả về trước khi mọi thứ bắt đầu chậm lại đã tăng giảm nhẹ. Đây không phải là mới; Bây giờ tôi đang xem xét vì tổng thời gian chạy đã được chấp nhận (<2 phút), nhưng ít nhất tôi đã thấy sự tạm dừng này trong các tệp nhật ký liên quan trong nhiều tháng.

Chặn

Tôi chưa bao giờ thấy truy vấn bị chặn và vấn đề tồn tại ngay cả khi không có hoạt động nào khác trên cơ sở dữ liệu (như được xác thực bởi sp_WhoIsActive). Chế độ xem cơ bản bao gồm NOLOCKtrong suốt, cho những gì đáng giá.

Truy vấn

Đây là phiên bản rút gọn của chế độ xem con, với chế độ xem cơ bản được xếp hàng để đơn giản. Nó vẫn thể hiện bước nhảy trong thời gian chạy ở khoảng 40 hồ sơ.

SELECT TOP 100 PERCENT
    Map.SalesforceAccountID AS Id,
    CAST(C.CustomerID AS NVARCHAR(255)) AS Name,
    CASE WHEN C.StreetAddress = 'Unknown' THEN '' ELSE C.StreetAddress                 END AS BillingStreet,
    CASE WHEN C.City          = 'Unknown' THEN '' ELSE SUBSTRING(C.City,        1, 40) END AS BillingCity,
                                                       SUBSTRING(C.Region,      1, 20)     AS BillingState,
    CASE WHEN C.PostalCode    = 'Unknown' THEN '' ELSE SUBSTRING(C.PostalCode,  1, 20) END AS BillingPostalCode,
    CASE WHEN C.Country       = 'Unknown' THEN '' ELSE SUBSTRING(C.Country,     1, 40) END AS BillingCountry,
    CASE WHEN C.PhoneNumber   = 'Unknown' THEN '' ELSE C.PhoneNumber                   END AS Phone,
    CASE WHEN C.FaxNumber     = 'Unknown' THEN '' ELSE C.FaxNumber                     END AS Fax,
    TransC.WebsiteAddress AS Website,
    C.AccessKey AS AccessKey__c,
    CASE WHEN dbo.ValidateEMail(C.EMailAddress) = 1 THEN C.EMailAddress END,  -- Removing this UDF does not speed things
    TransC.EmailSubscriber
    -- A couple dozen additional TransC fields
FROM
    WarehouseCustomers AS C WITH (NOLOCK)
    INNER JOIN TransactionalCustomers AS TransC WITH (NOLOCK) ON C.CustomerID = TransC.CustomerID
    LEFT JOIN  Salesforce.AccountsMap AS Map WITH (NOLOCK) ON C.CustomerID = Map.CustomerID
WHERE
        C.DateMadeObsolete IS NULL
    AND C.EmailAddress NOT LIKE '%@volusion.%'
    AND C.AccessKey IN ('C', 'R')
    AND C.CustomerID NOT IN (243566)  -- Exclude specific test records
    AND EXISTS (SELECT * FROM Orders AS O WHERE C.CustomerID = O.CustomerID AND O.OrderDate >= '2010-06-28')  -- Only count customers who've placed a recent order
    AND Map.SalesforceAccountID IS NULL  -- Only count customers not already uploaded to Salesforce
-- Removing the ORDER BY clause does not speed things up
ORDER BY
    C.CustomerID DESC

Id IS NULLBộ lọc đó loại bỏ hầu hết các hồ sơ được trả về BaseView; không có TOPđiều khoản, họ trả lại 1.100 hồ sơ và 267K tương ứng.

Số liệu thống kê

Khi chạy TOP 40:

SQL Server parse and compile time:    CPU time = 234 ms, elapsed time = 247 ms.
SQL Server Execution Times:   CPU time = 0 ms,  elapsed time = 0 ms.
SQL Server Execution Times:   CPU time = 0 ms,  elapsed time = 0 ms.

(40 row(s) affected)
Table 'CustomersHistory'. Scan count 2, logical reads 39112, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 752, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AccountsMap'. Scan count 1, logical reads 458, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:   CPU time = 2199 ms,  elapsed time = 7644 ms.

Khi chạy TOP 45:

(45 row(s) affected)
Table 'CustomersHistory'. Scan count 2, logical reads 98268, physical reads 1, read-ahead reads 3, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 1788, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AccountsMap'. Scan count 1, logical reads 2152, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times: CPU time = 41980 ms,  elapsed time = 177231 ms.

Tôi ngạc nhiên khi thấy số lần đọc nhảy ~ 3x cho sự khác biệt khiêm tốn này trong sản lượng thực tế.

So sánh các kế hoạch thực hiện, chúng giống nhau ngoài số lượng hàng được trả về. Như với các số liệu thống kê ở trên, số hàng thực tế cho các bước đầu tiên cao hơn đáng kể trong TOP 45truy vấn, không chỉ cao hơn 12,5%.

Trong phác thảo, nó đang quét một chỉ mục bao trùm từ Đơn hàng, tìm kiếm các bản ghi tương ứng từ WarehouseCustomers; kết nối vòng lặp này với TransactionalCustomers (truy vấn từ xa, kế hoạch chính xác không xác định); và hợp nhất điều này với quét bảng của MapMap. Truy vấn từ xa là 94% chi phí ước tính.

Ghi chú linh tinh

Trước đó, khi tôi thực hiện nội dung mở rộng của chế độ xem dưới dạng truy vấn độc lập, nó chạy khá nhanh: 13 giây cho 100 bản ghi. Bây giờ tôi đang thử nghiệm phiên bản rút gọn của truy vấn, không có truy vấn con và truy vấn đơn giản hơn nhiều này mất ba phút để yêu cầu trả về hơn 40 hàng, ngay cả khi chạy dưới dạng truy vấn độc lập.

Chế độ xem con bao gồm số lần đọc đáng kể (~ 1M mỗi sp_WhoIsActive), nhưng trên máy này (tám lõi, RAM 32 GB, hộp SQL chuyên dụng 95%) thường không phải là vấn đề.

Tôi đã bỏ và tạo lại cả hai lần xem, không có thay đổi.

Dữ liệu không bao gồm bất kỳ trường văn bản hoặc BLOB. Một lĩnh vực liên quan đến UDF; loại bỏ nó không ngăn chặn tạm dừng.

Thời gian được tính tương tự cho dù truy vấn trên máy chủ chính nó, hoặc trên máy trạm của tôi 1.400 dặm, vì vậy sự chậm trễ dường như là vốn có trong truy vấn bản thân chứ không phải gửi kết quả cho khách hàng.

Ghi chú Re: Giải pháp

Việc khắc phục kết thúc rất đơn giản: thay thế LEFT JOINthành Map bằng một NOT EXISTSmệnh đề. Điều này chỉ gây ra một sự khác biệt nhỏ trong kế hoạch truy vấn, tham gia vào bảng Giao dịch khách hàng (một truy vấn từ xa) sau khi tham gia vào bảng Bản đồ thay vì trước đó. Điều này có thể có nghĩa là nó chỉ yêu cầu các bản ghi cần thiết từ máy chủ từ xa, điều này sẽ cắt giảm âm lượng truyền đi ~ 100 lần.

Thông thường tôi là người đầu tiên cổ vũ cho NOT EXISTS; nó thường nhanh hơn một LEFT JOIN...WHERE ID IS NULLcấu trúc và nhỏ gọn hơn một chút. Trong trường hợp này, thật bất tiện vì truy vấn vấn đề được xây dựng trên chế độ xem hiện có và trong khi trường cần cho chống tham gia được hiển thị bởi chế độ xem cơ sở, thì lần đầu tiên chuyển từ số nguyên sang văn bản. Vì vậy, để có hiệu suất tốt, tôi phải bỏ mẫu hai lớp và thay vào đó có hai khung nhìn gần giống nhau, với khung thứ hai bao gồm NOT EXISTSmệnh đề.

Cảm ơn tất cả sự giúp đỡ của bạn trong việc khắc phục sự cố này! Nó có thể quá cụ thể với hoàn cảnh của tôi để giúp đỡ bất cứ ai khác, nhưng hy vọng là không. Nếu không có gì khác, đó là một ví dụ về NOT EXISTSviệc nhanh hơn một chút so với LEFT JOIN...WHERE ID IS NULL. Nhưng bài học thực sự có lẽ là đảm bảo rằng các truy vấn từ xa được tham gia hiệu quả nhất có thể; kế hoạch truy vấn tuyên bố rằng nó chiếm 2% chi phí, nhưng không phải lúc nào nó cũng ước tính chính xác.


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Paul White 9

Câu trả lời:


4

Một số điều cần thử:

  1. Kiểm tra chỉ số của bạn

    • Có phải tất cả các JOINlĩnh vực quan trọng được lập chỉ mục? Nếu bạn sử dụng chế độ xem này rất nhiều, tôi sẽ đi xa hơn để thêm một chỉ mục được lọc cho các tiêu chí trong chế độ xem. Ví dụ...

    • CREATE INDEX ix_CustomerId ON WarehouseCustomers(CustomerId, EmailAddress) WHERE DateMadeObsolete IS NULL AND AccessKey IN ('C', 'R') AND CustomerID NOT IN (243566)

  2. Cập nhật số liệu thống kê

    • Có thể có vấn đề với số liệu thống kê lỗi thời. Nếu bạn có thể xoay nó, tôi sẽ làm một FULLSCAN. Nếu có một số lượng lớn các hàng, có thể dữ liệu đã thay đổi đáng kể mà không kích hoạt tính toán lại tự động.
  3. Làm sạch truy vấn

    • Tạo Map JOINmột NOT EXISTS- Bạn không cần bất kỳ dữ liệu nào từ bảng đó, vì bạn chỉ muốn các bản ghi không khớp

    • Loại bỏ ORDER BY. Tôi biết các ý kiến ​​nói rằng điều đó không quan trọng nhưng tôi thấy điều đó rất khó tin. Nó có thể không quan trọng đối với các tập kết quả nhỏ hơn của bạn vì các trang dữ liệu đã được lưu trữ.


Điểm thú vị lại: chỉ số được lọc. Truy vấn không tự động sử dụng nó, nhưng tôi sẽ kiểm tra buộc nó bằng một gợi ý. Tôi đã cập nhật số liệu thống kê và có thể kiểm tra điều này và các đề xuất khác của bạn sau ngày hôm nay; Tôi cần để cho một hồ sơ tồn đọng tích tụ sau EOWD để tôi có thể kiểm tra đối với một tập hợp dữ liệu hợp lý.
Jon của tất cả các giao dịch

Tôi đã thử các kết hợp khác nhau của các tinh chỉnh này và chìa khóa dường như là chống tham gia với Bản đồ. Như LEFT JOIN...WHERE Id IS NULL, tôi nhận được sự tạm dừng này; như một NOT EXISTSmệnh đề, thời gian chạy là giây. Tôi ngạc nhiên, nhưng tôi không thể tranh luận với kết quả!
Jon của tất cả các giao dịch

2

Cải thiện 1 Xóa SubQuery cho các đơn đặt hàng và chuyển đổi nó thành tham gia

FROM
WarehouseCustomers AS C WITH (NOLOCK)
INNER JOIN TransactionalCustomers AS TransC WITH (NOLOCK) 
                                                        ON C.CustomerID = TransC.CustomerID
LEFT JOIN  Salesforce.AccountsMap AS Map WITH (NOLOCK) 
                                                        ON C.CustomerID = Map.CustomerID
INNER Join Orders AS O 
                                                        ON C.CustomerID = O.CustomerID

 WHERE
    C.DateMadeObsolete IS NULL
    AND C.EmailAddress NOT LIKE '%@volusion.%'
    AND C.AccessKey IN ('C', 'R')
    AND C.CustomerID NOT IN (243566)
    AND O.OrderDate >= '2010-06-28'
    AND Map.SalesforceAccountID IS NULL

Cải thiện 2 - Giữ các bản ghi được giao dịch của khách hàng được lọc trong bảng tạm thời cục bộ

Select 
    CAST(C.CustomerID AS NVARCHAR(255)) AS Name,
    CASE WHEN C.StreetAddress = 'Unknown' THEN '' ELSE C.StreetAddress                 END AS BillingStreet,
    CASE WHEN C.City          = 'Unknown' THEN '' ELSE SUBSTRING(C.City,        1, 40) END AS BillingCity,
                                                       SUBSTRING(C.Region,      1, 20)     AS BillingState,
    CASE WHEN C.PostalCode    = 'Unknown' THEN '' ELSE SUBSTRING(C.PostalCode,  1, 20) END AS BillingPostalCode,
    CASE WHEN C.Country       = 'Unknown' THEN '' ELSE SUBSTRING(C.Country,     1, 40) END AS BillingCountry,
    CASE WHEN C.PhoneNumber   = 'Unknown' THEN '' ELSE C.PhoneNumber                   END AS Phone,
    CASE WHEN C.FaxNumber     = 'Unknown' THEN '' ELSE C.FaxNumber                     END AS Fax,
    C.AccessKey AS AccessKey__c
Into #Temp
From  WarehouseCustomers C
Where C.DateMadeObsolete IS NULL
        AND C.EmailAddress NOT LIKE '%@volusion.%'
        AND C.AccessKey IN ('C', 'R')
        AND C.CustomerID NOT IN (243566)

Truy vấn cuối cùng

FROM
#Temp AS C WITH (NOLOCK)
INNER JOIN TransactionalCustomers AS TransC WITH (NOLOCK) 
                                                            ON C.CustomerID = TransC.CustomerID
LEFT JOIN Salesforce.AccountsMap AS Map WITH (NOLOCK) 
                                                            ON C.CustomerID = Map.CustomerID
INNER Join Orders AS O 
                                                            ON C.CustomerID = O.CustomerID

WHERE
C.DateMadeObsolete IS NULL
AND C.EmailAddress NOT LIKE '%@volusion.%'
AND C.AccessKey IN ('C', 'R')
AND C.CustomerID NOT IN (243566)
AND O.OrderDate >= '2010-06-28'
AND Map.SalesforceAccountID IS NULL

Điểm 3 - Tôi giả sử rằng bạn có các chỉ mục trên CustomerID, EmailAddress, OrderDate


1
Re: "Cải thiện" 1 - EXISTSthường nhanh hơn một JOINtrong trường hợp này và loại bỏ các bản sao tiềm năng. Tôi không nghĩ nó sẽ là một sự cải thiện.
JNK

1
Tuy nhiên, vấn đề có hai mặt - Nó có khả năng THAY ĐỔI KẾT QUẢ và trừ khi cả hai bảng có một chỉ mục cụm duy nhất trên các trường được sử dụng trong phép nối, nó sẽ kém hiệu quả hơn EXISTS. Điều khoản không phải lúc nào cũng xấu.
JNK

@PankajGarg: Cảm ơn bạn đã gợi ý, thật không may, thường có nhiều đơn đặt hàng cho mỗi khách hàng, vì vậy EXISTSlà bắt buộc. Ngoài ra, trong chế độ xem, tôi không thể lưu trữ dữ liệu khách hàng được sử dụng lại, mặc dù tôi đã có ý tưởng về một TVF giả không có tham số.
Jon của tất cả các giao dịch
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.