Điều gì gây ra việc sử dụng CPU cao từ kế hoạch truy vấn / thực thi này?


9

Tôi có Cơ sở dữ liệu Azure SQL cung cấp ứng dụng .NET Core API. Duyệt các báo cáo tổng quan về hiệu suất trong Cổng thông tin Azure cho thấy phần lớn tải (sử dụng DTU) trên máy chủ cơ sở dữ liệu của tôi đến từ CPU và một truy vấn cụ thể:

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

Như chúng ta có thể thấy, truy vấn 3780 chịu trách nhiệm cho gần như tất cả việc sử dụng CPU trên máy chủ.

Điều này phần nào có ý nghĩa, vì truy vấn 3780 (xem bên dưới) về cơ bản là toàn bộ mấu chốt của ứng dụng và được người dùng gọi khá thường xuyên. Đây cũng là một truy vấn khá phức tạp với nhiều tham gia cần thiết để có được bộ dữ liệu phù hợp cần thiết. Truy vấn đến từ một sproc cuối cùng trông như thế này:

-- @UserId UNIQUEIDENTIFIER

SELECT
    C.[Id],
    C.[UserId],
    C.[OrganizationId],
    C.[Type],
    C.[Data],
    C.[Attachments],
    C.[CreationDate],
    C.[RevisionDate],
    CASE
        WHEN
            @UserId IS NULL
            OR C.[Favorites] IS NULL
            OR JSON_VALUE(C.[Favorites], CONCAT('$."', @UserId, '"')) IS NULL
        THEN 0
        ELSE 1
    END [Favorite],
    CASE
        WHEN
            @UserId IS NULL
            OR C.[Folders] IS NULL
        THEN NULL
        ELSE TRY_CONVERT(UNIQUEIDENTIFIER, JSON_VALUE(C.[Folders], CONCAT('$."', @UserId, '"')))
    END [FolderId],
    CASE 
        WHEN C.[UserId] IS NOT NULL OR OU.[AccessAll] = 1 OR CU.[ReadOnly] = 0 OR G.[AccessAll] = 1 OR CG.[ReadOnly] = 0 THEN 1
        ELSE 0
    END [Edit],
    CASE 
        WHEN C.[UserId] IS NULL AND O.[UseTotp] = 1 THEN 1
        ELSE 0
    END [OrganizationUseTotp]
FROM
    [dbo].[Cipher] C
LEFT JOIN
    [dbo].[Organization] O ON C.[UserId] IS NULL AND O.[Id] = C.[OrganizationId]
LEFT JOIN
    [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId
LEFT JOIN
    [dbo].[CollectionCipher] CC ON C.[UserId] IS NULL AND OU.[AccessAll] = 0 AND CC.[CipherId] = C.[Id]
LEFT JOIN
    [dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
    [dbo].[GroupUser] GU ON C.[UserId] IS NULL AND CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
    [dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
    [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId]
WHERE
    C.[UserId] = @UserId
    OR (
        C.[UserId] IS NULL
        AND OU.[Status] = 2
        AND O.[Enabled] = 1
        AND (
            OU.[AccessAll] = 1
            OR CU.[CollectionId] IS NOT NULL
            OR G.[AccessAll] = 1
            OR CG.[CollectionId] IS NOT NULL
        )
)

Nếu bạn quan tâm, nguồn đầy đủ cho cơ sở dữ liệu này có thể được tìm thấy trên GitHub tại đây . Nguồn từ truy vấn trên:

Tôi đã dành một chút thời gian cho truy vấn này trong nhiều tháng để điều chỉnh kế hoạch thực hiện theo cách tốt nhất mà tôi biết, kết thúc với trạng thái hiện tại. Các truy vấn với kế hoạch thực hiện này nhanh chóng trên hàng triệu hàng (<1 giây), nhưng như đã lưu ý ở trên, đang ăn CPU của máy chủ ngày càng nhiều khi ứng dụng tăng kích thước.

Tôi đã đính kèm kế hoạch truy vấn thực tế bên dưới (không chắc chắn về bất kỳ cách nào khác để chia sẻ rằng ở đây trên trao đổi ngăn xếp), trong đó cho thấy một thực thi của sproc trong sản xuất đối với dữ liệu được trả về ~ 400 kết quả.

Một số điểm tôi đang tìm kiếm làm rõ về:

  • Chỉ số Tìm kiếm [IX_Cipher_UserId_Type_IncludeAll]chiếm 57% tổng chi phí của kế hoạch. Sự hiểu biết của tôi về kế hoạch là chi phí này có liên quan đến IO, do bảng Mã hóa chứa hàng triệu bản ghi. Tuy nhiên, báo cáo hiệu suất Azure SQL đang cho tôi thấy rằng các vấn đề của tôi xuất phát từ CPU trên truy vấn này, không phải IO, vì vậy tôi không chắc liệu đây có thực sự là vấn đề hay không. Thêm vào đó, nó đã thực hiện một chỉ mục tìm kiếm ở đây, vì vậy tôi không thực sự chắc chắn có bất kỳ chỗ nào để cải thiện.

  • Các hoạt động Hash Match từ tất cả các phép nối dường như là những gì đang cho thấy việc sử dụng CPU đáng kể trong kế hoạch (tôi nghĩ vậy?), Nhưng tôi không thực sự chắc chắn làm thế nào điều này có thể được làm tốt hơn. Bản chất phức tạp của cách tôi cần để có được dữ liệu cần rất nhiều phép nối trên một số bảng. Tôi đã ngắn mạch nhiều trong số các phép nối này nếu có thể (dựa trên kết quả từ lần tham gia trước) trong các ONmệnh đề của chúng .

Tải xuống gói thực hiện đầy đủ tại đây: https://www.dropbox.com/s/lua1awsc0uz1lo9/CodesDetails_ReadByUserId.sqlplan?dl=0

Tôi cảm thấy mình có thể đạt được hiệu năng CPU tốt hơn từ truy vấn này, nhưng tôi đang ở giai đoạn mà tôi không chắc chắn làm thế nào để tiếp tục điều chỉnh kế hoạch thực hiện nữa. Những tối ưu hóa nào khác có thể phải giảm tải CPU? Những hoạt động nào trong kế hoạch thực hiện là những người phạm tội tồi tệ nhất trong việc sử dụng CPU?

Câu trả lời:


4

Bạn có thể xem CPU ở cấp độ nhà điều hành và số liệu thời gian đã trôi qua trong SQL Server Management Studio, mặc dù tôi không thể nói mức độ tin cậy của chúng đối với các truy vấn kết thúc nhanh như của bạn. Gói của bạn chỉ có các toán tử chế độ hàng, vì vậy các số liệu thời gian áp dụng cho toán tử đó cũng như các toán tử trong cây con bên dưới nó. Sử dụng phép nối vòng lặp lồng nhau làm ví dụ, SQL Server sẽ cho bạn biết rằng toàn bộ cây con mất 60 ms thời gian CPU và 80 ms thời gian đã trôi qua:

chi phí phụ

Hầu hết thời gian của cây con đó được dành cho việc tìm kiếm chỉ mục. Index cũng tìm CPU. Có vẻ như chỉ mục của bạn có chính xác các cột cần thiết nên không rõ làm thế nào bạn có thể giảm chi phí CPU của nhà điều hành đó. Khác với việc tìm kiếm phần lớn thời gian CPU trong kế hoạch được dành cho các kết quả khớp băm thực hiện các phép nối của bạn.

Đây là một sự đơn giản hóa quá lớn, nhưng CPU được thực hiện bởi các phép nối băm đó sẽ phụ thuộc vào kích thước của đầu vào cho bảng băm và số lượng hàng được xử lý ở phía đầu dò. Quan sát một số điều về kế hoạch truy vấn này:

  • Nhiều nhất 461 hàng trả lại có C.[UserId] = @UserId. Những hàng này không quan tâm đến các liên kết.
  • Đối với các hàng cần tham gia, SQL Server không thể áp dụng bất kỳ bộ lọc nào sớm (ngoại trừ OU.[UserId] = @UserId).
  • Gần như tất cả các hàng được xử lý được loại bỏ gần cuối kế hoạch truy vấn (đọc từ phải sang trái) bởi bộ lọc: [vault].[dbo].[Cipher].[UserId] as [C].[UserId]=[@UserId] OR ([vault].[dbo].[OrganizationUser].[AccessAll] as [OU].[AccessAll]=(1) OR [vault].[dbo].[CollectionUser].[CollectionId] as [CU].[CollectionId] IS NOT NULL OR [vault].[dbo].[Group].[AccessAll] as [G].[AccessAll]=(1) OR [vault].[dbo].[CollectionGroup].[CollectionId] as [CG].[CollectionId] IS NOT NULL) AND [vault].[dbo].[Cipher].[UserId] as [C].[UserId] IS NULL AND [vault].[dbo].[OrganizationUser].[Status] as [OU].[Status]=(2) AND [vault].[dbo].[Organization].[Enabled] as [O].[Enabled]=(1)

Sẽ là tự nhiên hơn để viết truy vấn của bạn như là một UNION ALL. Nửa đầu của UNION ALLcó thể bao gồm các hàng trong đó C.[UserId] = @UserIdvà nửa thứ hai có thể bao gồm các hàng ở đó C.[UserId] IS NULL. Bạn đã thực hiện hai chỉ mục tìm kiếm [dbo].[Cipher](một cho @UserIdvà một cho NULL) nên có vẻ như UNION ALLphiên bản sẽ chậm hơn. Viết ra các truy vấn riêng biệt sẽ cho phép bạn thực hiện một số bộ lọc sớm, cả về mặt xây dựng và mặt thăm dò. Truy vấn có thể nhanh hơn nếu họ cần xử lý ít dữ liệu trung gian hơn.

Tôi không biết phiên bản SQL Server của bạn có hỗ trợ điều này không, nhưng nếu điều đó không giúp bạn có thể thử thêm chỉ mục cột vào truy vấn của mình để làm cho hàm băm của bạn tham gia đủ điều kiện cho chế độ hàng loạt . Cách ưa thích của tôi là tạo một bảng trống có CCI trên đó và rời khỏi tham gia vào bảng đó. Các phép nối băm có thể hiệu quả hơn nhiều khi chúng chạy ở chế độ hàng loạt so với chế độ hàng.


Theo đề xuất, tôi đã có thể viết lại sproc với 2 truy vấn UNION ALL(một cho C.[UserId] = @UserIdvà một cho C.[UserId] IS NULL AND ...). Điều này làm giảm các tập kết quả nối và loại bỏ hoàn toàn nhu cầu băm khớp (hiện đang thực hiện các vòng lặp lồng nhau trên các tập hợp nhỏ). Các truy vấn bây giờ tốt hơn nhiều trên CPU. Cảm ơn bạn!
kspearrin

0

Cộng đồng wiki trả lời :

Bạn có thể thử chia điều này thành hai truy vấn và kết hợp UNION ALLchúng lại với nhau.

WHEREĐiều khoản của bạn đang xảy ra ở cuối, nhưng nếu bạn chia nó thành:

  • Một truy vấn trong đó C.[UserId] = @UserId
  • Một nơi khác C.[UserId] IS NULL AND OU.[Status] = 2 AND O.[Enabled] = 1

... mỗi người có thể có được một kế hoạch đủ tốt để làm cho nó có giá trị trong khi.

Nếu mỗi truy vấn áp dụng vị từ sớm trong kế hoạch, bạn sẽ không phải tham gia quá nhiều hàng cuối cùng được lọc ra.

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.