ID first: Dies ist das selektivste (dh eindeutigste) Feld. Da es sich jedoch um ein Auto-Inkrement-Feld handelt (oder um ein zufälliges Feld, wenn weiterhin GUIDs verwendet werden), werden die Kundendaten auf alle Tabellen verteilt. Dies bedeutet, dass ein Kunde manchmal 100 Zeilen benötigt und dass fast 100 Datenseiten von der Festplatte (nicht schnell) in den Pufferpool eingelesen werden müssen (was mehr Platz als 10 Datenseiten beansprucht). Dies erhöht auch die Konkurrenz auf den Datenseiten, da es häufiger vorkommt, dass mehrere Kunden dieselbe Datenseite aktualisieren müssen.
In der Regel treten jedoch nicht so viele Parameter-Sniffing- / Bad-Cached-Plan-Probleme auf, da die Statistiken für die verschiedenen ID-Werte ziemlich konsistent sind. Sie erhalten möglicherweise nicht die optimalen Pläne, aber Sie werden weniger wahrscheinlich schreckliche bekommen. Diese Methode beeinträchtigt im Wesentlichen die Leistung (geringfügig) aller Kunden, um die Vorteile weniger häufiger Probleme zu nutzen.
MieterID zuerst:Dies ist überhaupt nicht selektiv. Wenn Sie nur 100 TenantIDs haben, kann es zu sehr geringen Abweichungen zwischen 1 Million Zeilen kommen. Die Statistik für diese Abfragen ist jedoch genauer, da SQL Server weiß, dass eine Abfrage für Mandant A 500.000 Zeilen zurückzieht, dieselbe Abfrage für Mandant B jedoch nur 50 Zeilen enthält. Hier liegt der Hauptschmerzpunkt. Diese Methode erhöht die Wahrscheinlichkeit von Parameternachforschungsproblemen erheblich, wenn die erste Ausführung einer gespeicherten Prozedur für Mandant A ausgeführt wird. Sie basiert auf dem Abfrageoptimierer, der diese Statistiken anzeigt und weiß, dass 500.000 Zeilen effizient abgerufen werden müssen. Wenn jedoch Mandant B mit nur 50 Zeilen ausgeführt wird, ist dieser Ausführungsplan nicht mehr angemessen und in der Tat völlig unangemessen. UND, da die Daten nicht in der Reihenfolge des führenden Feldes eingefügt werden,
Für die erste TenantID, die eine gespeicherte Prozedur ausführt, sollte die Leistung jedoch besser sein als bei der anderen Methode, da die Daten (zumindest nach der Indexpflege) physisch und logisch so organisiert sind, dass weit weniger Datenseiten erforderlich sind, um die Anforderungen zu erfüllen Abfragen. Dies bedeutet weniger physische E / A, weniger logische Lesevorgänge, weniger Konflikte zwischen Mandanten für dieselben Datenseiten, weniger verschwendeten Speicherplatz im Pufferpool (daher verbesserte Lebenserwartung der Seite) usw.
Es gibt zwei Hauptkosten, um diese verbesserte Leistung zu erhalten. Das erste ist nicht so schwierig: Sie müssen regelmäßige Indexpflege durchführen, um der zunehmenden Fragmentierung entgegenzuwirken. Die zweite ist ein bisschen weniger Spaß.
Um den gestiegenen Problemen mit dem Parameter-Sniffing entgegenzuwirken, müssen Sie die Ausführungspläne zwischen den Mandanten trennen. Der vereinfachte Ansatz besteht darin, WITH RECOMPILE
auf procs oder den Abfragehinweis zurückzugreifen. Dies OPTION (RECOMPILE)
ist jedoch ein Leistungstreffer, der alle Gewinne zunichte machen könnte, die durch das Voranstellen erzielt werden TenantID
. Die Methode, die am besten funktioniert hat, ist die Verwendung von parametrisiertem Dynamic SQL über sp_executesql
. Der Grund für die Verwendung von Dynamic SQL besteht darin, dass die TenantID in den Text der Abfrage eingebunden werden kann, während alle anderen Prädikate, die normalerweise Parameter sind, weiterhin Parameter sind. Wenn Sie beispielsweise nach einem bestimmten Auftrag suchen, tun Sie Folgendes:
DECLARE @GetOrderSQL NVARCHAR(MAX);
SET @GetOrderSQL = N'
SELECT ord.field1, ord.field2, etc.
FROM dbo.Orders ord
WHERE ord.TenantID = ' + CONVERT(NVARCHAR(10), @TenantID) + N'
AND ord.OrderID = @OrderID_dyn;
';
EXEC sp_executesql
@GetOrderSQL,
N'@OrderID_dyn INT',
@OrderID_dyn = @OrderID;
Dies bewirkt, dass nur für diese TenantID ein wiederverwendbarer Abfrageplan erstellt wird, der dem Datenvolumen dieses bestimmten Tenants entspricht. Wenn derselbe Mandant A die gespeicherte Prozedur für einen anderen erneut ausführt @OrderID
, wird dieser zwischengespeicherte Abfrageplan erneut verwendet. Ein anderer Mandant, der dieselbe gespeicherte Prozedur ausführt, generiert einen Abfragetext, der sich nur im Wert der Mandanten-ID unterscheidet. Jeder Unterschied im Abfragetext reicht jedoch aus, um einen anderen Plan zu generieren. Der für Mandant B generierte Plan entspricht nicht nur dem Datenvolumen für Mandant B, sondern kann auch für Mandant B für verschiedene Werte von wiederverwendet werden @OrderID
(da dieses Prädikat noch parametrisiert ist).
Die Nachteile dieses Ansatzes sind:
- Es ist etwas mehr Arbeit als nur das Eingeben einer einfachen Abfrage (aber nicht alle Abfragen müssen Dynamic SQL sein, sondern nur diejenigen, bei denen das Problem des Parameter-Sniffs auftritt).
- Je nachdem, wie viele Mandanten sich in einem System befinden, wird der Plan-Cache vergrößert, da für jede Abfrage jetzt 1 Plan pro Mandanten-ID erforderlich ist, die ihn aufruft. Dies ist möglicherweise kein Problem, aber es ist zumindest etwas zu beachten.
Dynamic SQL unterbricht die Besitzerkette, sodass Lese- / Schreibzugriff auf Tabellen nicht mit der EXECUTE
Berechtigung für die gespeicherte Prozedur angenommen werden kann. Die einfache, aber weniger sichere Lösung besteht darin, dem Benutzer direkten Zugriff auf die Tabellen zu gewähren. Dies ist sicherlich nicht ideal, aber in der Regel ist dies der Kompromiss zwischen schnell und einfach. Der sicherere Ansatz ist die Verwendung der zertifikatbasierten Sicherheit. Das heißt, erstellen Sie ein Zertifikat und anschließend einen Benutzer aus diesem Zertifikat, gewähren Sie diesem Benutzer die gewünschten Berechtigungen (ein zertifikatbasierter Benutzer oder eine Anmeldung kann keine eigene Verbindung zu SQL Server herstellen) und signieren Sie dann die gespeicherten Prozeduren, die Dynamic SQL verwenden gleiches Zertifikat über ADD SIGNATURE .
Weitere Informationen zur Modulsignatur und zu Zertifikaten finden Sie unter: ModuleSigning.Info