Die Abfrage gegen sys.schemas und sys.synonyms läuft für einen Benutzer sehr langsam

8

Szenario: SQL Server 2014 (v12.0.4100.1)

Der .NET-Dienst führt diese Abfrage aus:

SELECT name, base_object_name 
FROM sys.synonyms 
WHERE schema_id IN (SELECT schema_id 
                    FROM sys.schemas 
                    WHERE name = N'XXXX')
ORDER BY name

... was ungefähr 6500 Zeilen zurückgibt, aber oft nach mehr als 3 Minuten eine Zeitüberschreitung aufweist. Das XXXXobige ist nicht 'dbo'.

Wenn ich diese Abfrage in SSMS als UserA ausführe, wird die Abfrage in weniger als einer Sekunde zurückgegeben.

Bei Ausführung als UserB (wie der .NET-Dienst eine Verbindung herstellt) dauert die Abfrage 3-6 Minuten und die CPU% liegt die ganze Zeit über bei 25% (von 4 Kernen).

UserA ist eine Domänenanmeldung in der Sysadmin-Rolle.

UserB ist ein SQL Login mit:

EXEC sp_addrolemember N'db_datareader', N'UserB'
EXEC sp_addrolemember N'db_datawriter', N'UserB'
EXEC sp_addrolemember N'db_ddladmin', N'UserB'
GRANT EXECUTE TO [UserB]
GRANT CREATE SCHEMA TO [UserB]
GRANT VIEW DEFINITION TO [UserB]

Ich kann dies in SSMS duplizieren, indem ich das obige SQL in einen Execute as...RevertBlock einbinde , sodass der .NET-Code nicht im Bild ist.

Der Ausführungsplan sieht gleich aus. Ich habe das XML unterschieden und es gibt nur geringfügige Unterschiede (CompileTime, CompileCPU, CompileMemory).

Alle E / A-Statistiken zeigen keine physischen Lesevorgänge an:

Tabelle 'sysobjvalues'. Scananzahl 0, logische Lesevorgänge 19970, physische Lesevorgänge 0, Vorauslesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Lobvorlesevorgänge 0.
Tabelle 'Arbeitsdatei'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0, Vorauslesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Lobvorlesevorgänge 0.
Tabelle 'Arbeitstisch'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0, Vorauslesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Lobvorlesevorgänge 0.
Tabelle 'sysschobjs'. Scananzahl 1, logische Lesevorgänge 9122, physische Lesevorgänge 0, Vorauslesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Lobvorlesevorgänge 0.
Tabelle 'sysclsobjs'. Scananzahl 0, logische Lesevorgänge 2, physische Lesevorgänge 0, Vorauslesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Lobvorlesevorgänge 0.

Der Status von XEvent wartet (für eine ~ 3-minütige Abfrage):

+ --------------------- + ------------ + -------------- -------- + ------------------------------ + ---------- ------------------- +
| Warten Sie Typ | Warte Zählung | Gesamtwartezeit (ms) | Wartezeit der gesamten Ressource (ms) | Gesamtsignalwartezeit (ms) |
+ --------------------- + ------------ + -------------- -------- + ------------------------------- + --------- -------------------- +
| SOS_SCHEDULER_YIELD | 37300 | 427 | 20 | 407 |
| NETWORK_IO | 5 | 26 | 26 | 0 |
| IO_COMPLETION | 3 | 1 | 1 | 0 |
+ --------------------- + ------------ + -------------- -------- + ------------------------------- + --------- -------------------- +

Wenn ich die Abfrage neu schreibe (in SSMS habe ich keinen Zugriff auf den App-Code) an

declare @id int 
SELECT @id=schema_id FROM sys.schemas WHERE name = N'XXXX'
SELECT a.name, base_object_name FROM sys.synonyms a
WHERE schema_id = @id
ORDER BY name

dann läuft UserB mit der gleichen (schnellen) Geschwindigkeit wie UserA.

Wenn ich db_ownerzu UserB hinzufüge , wird die Abfrage erneut <1 Sek. Ausgeführt.

Über diese Vorlage erstelltes Schema:

DECLARE @TranName VARCHAR(20)
SELECT @TranName = 'MyTransaction'

BEGIN TRANSACTION @TranName
GO

IF NOT EXISTS (SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA
        WHERE SCHEMA_NAME = '{1}')
BEGIN
    EXEC('CREATE SCHEMA [{1}]')
    EXEC sp_addextendedproperty @name='User', @value='{0}', @level0type=N'Schema', @level0name=N'{1}'
END
GO

{2}

COMMIT TRANSACTION MyTransaction;
GO

Und {2} ist meiner Meinung nach eine Liste von Synonymen, die in diesem Schema erstellt wurden.

Abfrageprofil an zwei Stellen in der Abfrage:

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Ich habe ein Ticket bei Microsoft geöffnet.

Außerdem haben wir versucht, UserB hinzuzufügen db_ownerund dann DENYalle uns bekannten Berechtigungen zu verwenden, die damit verbunden sind db_owner. Das Ergebnis ist eine schnelle Abfrage. Entweder haben wir etwas verpasst (durchaus möglich) oder es gibt eine spezielle Überprüfung für die db_ownerRolle.

James
quelle

Antworten:

5

Möglicherweise möchten Sie Ihre Abfrage wie folgt neu schreiben (ich verwende sie dboeher, als XXXXdass ich einige Synonyme in meiner Testdatenbank finde). Dies ähnelt dem Umschreiben, das Sie als effizienter befunden haben, vermeidet jedoch die Notwendigkeit, eine Variable zu deklarieren und zwei Abfragen zu verwenden.

SELECT name, base_object_name 
FROM sys.synonyms 
WHERE schema_id = SCHEMA_ID(N'dbo')
ORDER BY name

Dies ergibt einen Plan wie den folgenden:

Geben Sie hier die Bildbeschreibung ein

Eine sehr interessante Sache über den FilterOperator in diesem Plan ist, dass er ein Prädikat hat, das eine interne has_access()Prüfung durchführt. Dieser Filter entfernt alle Objekte, für deren Anzeige das aktuelle Konto nicht über ausreichende Berechtigungen verfügt. Diese Prüfung wird jedoch kurzgeschlossen (dh viel schneller abgeschlossen), wenn Sie Mitglied der db_ownerRolle sind, was möglicherweise die festgestellten Leistungsunterschiede erklärt.

Geben Sie hier die Bildbeschreibung ein

Hier ist der Abfrageplan für Ihre ursprüngliche Abfrage. Beachten Sie, dass alle Synonyme in der Datenbank ( 1,126in meinem Fall, aber wahrscheinlich noch viel mehr in Ihrem Fall) den sehr teuren has_access()Filter durchlaufen , obwohl nur 2Synonyme mit dem Schema übereinstimmen. Durch die Verwendung der oben beschriebenen vereinfachten Abfrage können wir sicherstellen, dass diese has_access()nur für die Synonyme aufgerufen wird, die Ihrer Abfrage entsprechen, und nicht für alle Synonyme in der Datenbank.

Geben Sie hier die Bildbeschreibung ein


Verwenden Sie sys.dm_exec_query_profiles, um weitere Informationen zu erhalten

Wie Martin vorschlägt, können wir mithilfe sys.dm_exec_query_profilesvon SQL Server 2014+ bestätigen, dass die Prüfung has_access () einen erheblichen Engpass darstellt . Wenn ich die folgende Abfrage mit einem db_ownerKonto in einer Datenbank mit ~ 700.000 Objekten ausführe, dauert die Abfrage ~500ms:

SELECT COUNT(*)
FROM sys.objects

Bei Ausführung mit einem Konto, das kein Konto ist db_owner, dauert dieselbe Abfrage ungefähr acht Minuten! Wennsys.dm_exec_query_profiles wir mit dem tatsächlichen Plan ausgeführt werden und eine von mir geschriebene p_queryProgress- Prozedur verwenden, um die Analyse einfacher zu analysieren , können wir feststellen, dass fast die gesamte Verarbeitungszeit für den FilterOperator aufgewendet wird, der die has_access()Prüfung durchführt:

Geben Sie hier die Bildbeschreibung ein

Geoff Patterson
quelle
TokenAndPermUserStore-Problem? In diesem Fall könnte dieser KB-Artikel support.microsoft.com/en-gb/kb/955644
Martin Smith
@MartinSmith Sehr interessant, ich kannte die access check cache bucket countund access check cache quotaKonfigurationsoptionen vorher nicht. Muss ein bisschen damit rumspielen.
Geoff Patterson
Ich bin nicht sicher, ob dieser Cache für den Fall hier relevant ist. Ich erinnere mich nur daran, dass es in der Vergangenheit Probleme verursacht hat.
Martin Smith
1
@MartinSmith Obwohl diese Einstellungen keine Auswirkungen hatten, ist mit dem Cache etwas Interessantes los. Es scheint, dass die Existenz des Caches nachteilig ist. Wenn ich beispielsweise für immer WHILE(1=1) BEGIN DBCC FREESYSTEMCACHE ('TokenAndPermUserStore') WAITFOR DELAY '00:00:05' ENDin einer Schleife laufe , wird die Abfrage in weniger als 2 Minuten gegenüber 8 Minuten abgeschlossen.
Geoff Patterson
1
Vielen Dank an alle - die Antwort von Microsoft spiegelt die obigen Kommentare wider, und ein Umschreiben der Abfrage ist die beste Lösung. Es stellt sich heraus, dass has_access () zu Beginn einen Kurzschluss zum Testen auf db_owner oder sysadmin hat, was zu einem großen Zeitunterschied führte.
James
0

Wenn dies noch aktiv ist - wir hatten das gleiche Problem -, scheint es, dass wenn Sie entweder der Dbo oder ein Systemadministrator sind, jeder Zugriff auf sys.objects (oder ähnliches) sofort erfolgt, ohne dass einzelne Objekte überprüft werden müssen.

Wenn es sich um einen niedrigen db_datareader handelt, muss er jedes Objekt der Reihe nach überprüfen ... es ist im Abfrageplan versteckt, da sich diese eher wie Funktionen als wie Ansichten / Tabellen verhalten

Der Plan sieht gleich aus, aber er macht verschiedene Dinge hinter der Haube

Mike
quelle