Ich habe eine Azure SQL-Datenbank, die eine .NET Core API-App unterstützt. Das Durchsuchen der Leistungsübersichtsberichte im Azure-Portal weist darauf hin, dass der Großteil der Last (DTU-Nutzung) auf meinem Datenbankserver von der CPU stammt, und eine Abfrage speziell:
Wie wir sehen können, ist die Abfrage 3780 für fast die gesamte CPU-Auslastung auf dem Server verantwortlich.
Dies ist etwas sinnvoll, da die Abfrage 3780 (siehe unten) im Grunde genommen den gesamten Kern der Anwendung darstellt und von Benutzern häufig aufgerufen wird. Es ist auch eine ziemlich komplexe Abfrage mit vielen Verknüpfungen, die erforderlich sind, um den richtigen Datensatz zu erhalten. Die Abfrage stammt von einem Sproc, der am Ende folgendermaßen aussieht:
-- @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
)
)
Wenn Sie sich interessieren, finden Sie die vollständige Quelle für diese Datenbank auf GitHub hier . Quellen aus der obigen Abfrage:
- 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
Ich habe im Laufe der Monate einige Zeit mit dieser Abfrage verbracht, um den Ausführungsplan so gut wie möglich zu optimieren und den aktuellen Status zu erhalten. Abfragen mit diesem Ausführungsplan sind über Millionen von Zeilen (<1 Sek.) Schnell, verbrauchen jedoch, wie oben erwähnt, die Server-CPU immer mehr, je größer die Anwendung wird.
Ich habe den tatsächlichen Abfrageplan unten angehängt (ich bin mir nicht sicher, wie ich ihn hier beim Stapeltausch teilen kann), der eine Ausführung des Sproc in der Produktion anhand eines zurückgegebenen Datensatzes von ~ 400 Ergebnissen zeigt.
Einige Punkte, die ich klären möchte:
Index Seek on
[IX_Cipher_UserId_Type_IncludeAll]
übernimmt 57% der Gesamtkosten des Plans. Mein Verständnis des Plans ist, dass diese Kosten mit E / A zusammenhängen, was bedeutet, dass die Verschlüsselungstabelle Millionen von Datensätzen enthält. Azure SQL-Leistungsberichte zeigen mir jedoch, dass meine Probleme von der CPU bei dieser Abfrage und nicht von E / A herrühren. Daher bin ich mir nicht sicher, ob dies tatsächlich ein Problem ist oder nicht. Außerdem wird hier bereits eine Indexsuche durchgeführt, sodass ich nicht sicher bin, ob Verbesserungspotenzial besteht.Die Hash-Match-Operationen aller Joins scheinen die signifikante CPU-Auslastung im Plan zu zeigen (glaube ich?), Aber ich bin mir nicht sicher, wie dies verbessert werden könnte. Die Komplexität, wie ich die Daten abrufen muss, erfordert viele Verknüpfungen über mehrere Tabellen hinweg. Ich habe bereits viele dieser Verknüpfungen, wenn möglich, (basierend auf den Ergebnissen einer vorherigen Verknüpfung) in ihren
ON
Klauseln kurzgeschlossen.
Laden Sie den vollständigen Ausführungsplan hier herunter: https://www.dropbox.com/s/lua1awsc0uz1lo9/CipherDetails_ReadByUserId.sqlplan?dl=0
Ich habe das Gefühl, dass ich mit dieser Abfrage eine bessere CPU-Leistung erzielen kann, bin jedoch in einem Stadium, in dem ich nicht sicher bin, wie ich den Ausführungsplan weiter optimieren soll. Welche anderen Optimierungen könnten erforderlich sein, um die CPU-Auslastung zu verringern? Welche Vorgänge im Ausführungsplan sind die schlimmsten Straftäter der CPU-Auslastung?
UNION ALL
(eine fürC.[UserId] = @UserId
und eine fürC.[UserId] IS NULL AND ...
). Dies reduzierte die Join-Ergebnismengen und beseitigte die Notwendigkeit von Hash-Übereinstimmungen insgesamt (jetzt werden verschachtelte Schleifen für kleine Join-Mengen ausgeführt). Die Abfrage ist jetzt auf der CPU viel besser. Vielen Dank!Antwort des Community-Wikis :
Sie können versuchen, dies in zwei Abfragen aufzuteilen und
UNION ALL
sie wieder zusammenzufügen.Ihre
WHERE
Klausel passiert alles am Ende, aber wenn Sie sie aufteilen in:C.[UserId] = @UserId
C.[UserId] IS NULL AND OU.[Status] = 2 AND O.[Enabled] = 1
... jeder könnte einen Plan bekommen, der gut genug ist, damit es sich lohnt.
Wenn jede Abfrage das Prädikat zu Beginn des Plans anwendet, müssen Sie nicht so viele Zeilen verbinden, die letztendlich herausgefiltert werden.
quelle