我有一个支持.NET Core API应用程序的Azure SQL数据库。浏览Azure门户中的性能概述报告表明,我的数据库服务器上的大部分负载(DTU使用情况)来自CPU,特别是一个查询:
如我们所见,查询3780几乎负责服务器上的所有CPU使用率。
这有点说得通,因为查询3780(见下文)基本上是应用程序的整个关键所在,并且用户经常调用它。这也是一个相当复杂的查询,需要许多联接才能获得所需的正确数据集。该查询来自一个存储库,最终看起来像这样:
-- @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
)
)
如果您愿意,可以在GitHub上找到此数据库的完整源代码。上面查询的来源:
- https://github.com/bitwarden/core/blob/master/src/Sql/dbo/Stored%20Procedures/CipherDetails_ReadByUserId.sql
- https://github.com/bitwarden/core/blob/master/src/Sql/dbo/Functions/UserCipherDetails.sql
- https://github.com/bitwarden/core/blob/master/src/Sql/dbo/Functions/CipherDetails.sql
在过去的几个月中,我花了一些时间在此查询上调整了执行计划,据我所知,它最终是当前状态。使用此执行计划的查询可以在数百万行(<1秒)内快速进行查询,但是如上所述,随着应用程序大小的增加,它们越来越消耗服务器CPU。
我在下面附加了实际的查询计划(不确定是否可以在堆栈交换中共享该计划),该计划显示了生产过程中存储过程对返回的约400个结果数据集的执行情况。
我需要澄清以下几点:
寻求索引
[IX_Cipher_UserId_Type_IncludeAll]
的费用占计划总费用的57%。我对该计划的理解是,此成本与IO有关,这使得Cipher表包含数百万条记录。但是,Azure SQL性能报告向我显示我的问题是由该查询(而不是IO)上的CPU引起的,因此我不确定这是否确实是问题。另外,它已经在这里进行索引搜索,因此我不确定是否有任何改进的余地。所有联接的哈希匹配操作似乎表明计划中的CPU使用率很高(我认为?),但是我不确定如何将其改进。我需要获取数据的方式的复杂性要求在多个表之间进行大量联接。如果可能,我已经在其
ON
子句中短路了许多这些连接(基于先前连接的结果)。
在此处下载完整的执行计划:https : //www.dropbox.com/s/lua1awsc0uz1lo9/CipherDetails_ReadByUserId.sqlplan?dl=0
我觉得可以从该查询中获得更好的CPU性能,但是我目前尚不确定如何进一步调整执行计划。还有哪些其他优化措施可以减少CPU负载?执行计划中哪些操作最严重地影响了CPU使用率?
UNION ALL
(一个用于C.[UserId] = @UserId
和一个用于C.[UserId] IS NULL AND ...
)来重写该存储过程。这减少了联接结果集,并且完全不需要哈希匹配(现在在小型联接集上执行嵌套循环)。现在,查询在CPU上要好得多。谢谢!