Leistung der CLR-Integrationsarchitektur

Gilt für: SQL Server Azure SQL Managed Instance

In diesem Artikel werden einige der Entwurfsoptionen erläutert, die die Leistung der SQL Server-Integration in die Common Language Runtime (CLR) von Microsoft .NET Framework verbessern.

Der Kompilierungsprozess

Bei der Kompilierung von SQL-Ausdrücken wird beim Auftreten eines Verweises auf eine verwaltete Routine ein MSIL-Stub (Microsoft Intermediate Language) generiert. Dieser Stub enthält Code, um die Routineparameter von SQL Server an die CLR zu marshallen, die Funktion aufzurufen und das Ergebnis zurückzugeben. Dieser "Verbindungscode" basiert auf dem Parametertyp und der Parameterrichtung (IN, OUT oder Verweis).

Der Klebecode ermöglicht typspezifische Optimierungen und gewährleistet eine effiziente Erzwingung der SQL Server-Semantik, z. B. Nullierbarkeit, Einschränken von Facets, Nachwert- und Standard-Ausnahmebehandlung. Durch die Codegenerierung für genaue Argumenttypen vermeiden Sie Kosten für Typenumwandlung und die Erstellung von Wrapperobjekten ("Boxing" genannt) über die Aufrufgrenze hinweg.

Der generierte Stub wird dann in systemeigenem Code kompiliert und für die spezielle Hardwarearchitektur optimiert, auf der SQL Server ausgeführt wird, wobei die Just-in-Time-Kompilierungsdienste (JUST-in-Time, JIT) der CLR verwendet werden. Die JIT-Dienste werden auf Methodenebene aufgerufen und ermöglichen es der SQL Server-Hostingumgebung, eine einzelne Kompilierungseinheit zu erstellen, die sowohl sql Server- als auch CLR-Ausführung umfasst. Sobald der Stub kompiliert ist, wird der resultierende Funktionszeiger zur Laufzeitimplementierung der Funktion. Dieser Codegenerierungsansatz stellt sicher, dass zur Laufzeit keine zusätzlichen Aufrufkosten im Zusammenhang mit Spiegelung oder Metadatenzugriff auftreten.

Schnelle Übergänge zwischen SQL Server und CLR

Der Kompilierungsprozess erzeugt einen Funktionszeiger, der zur Laufzeit über systemeigenen Code aufgerufen werden kann. Bei benutzerdefinierten skalaren Funktionen erfolgt der Aufruf dieser Funktion pro Zeile. Um die Kosten für den Übergang zwischen SQL Server und CLR zu minimieren, weisen Anweisungen, die einen verwalteten Aufruf enthalten, einen Startschritt auf, um die Zielanwendungsdomäne zu identifizieren. Dieser Identifizierungsschritt reduziert die Kosten für den Übergang der einzelnen Zeilen.

Überlegungen zur Leistung

Im folgenden Abschnitt werden Leistungsüberlegungen zusammengefasst, die für die CLR-Integration in SQL Server spezifisch sind. Weitere Informationen finden Sie unter Verwenden der CLR-Integration in SQL Server 2005. Informationen zur Leistung von verwaltetem Code finden Sie unter Verbessern der Leistung und Skalierbarkeit von .NET-Anwendungen.

Benutzerdefinierte Funktionen

CLR-Funktionen profitieren von einem schnelleren Aufrufpfad als benutzerdefinierte Transact-SQL-Funktionen. Darüber hinaus hat verwalteter Code einen entscheidenden Leistungsvorteil gegenüber Transact-SQL im Hinblick auf prozeduralen Code, Berechnung und Zeichenfolgenmanipulation. CLR-Funktionen, die rechenintensiv sind und keinen Datenzugriff ausführen, sind besser in verwaltetem Code geschrieben. Transact-SQL-Funktionen führen jedoch den Datenzugriff effizienter aus als die CLR-Integration.

Benutzerdefinierte Aggregate

Verwalteter Code ist deutlich leistungsfähiger als die cursorbasierte Aggregation. Verwalteter Code führt in der Regel etwas langsamer aus als integrierte SQL Server-Aggregatfunktionen. Wir empfehlen daher, eine systemeigene integrierte Aggregatfunktion zu verwenden, sofern sie zur Verfügung steht. In Fällen, in denen die erforderliche Aggregation nicht nativ unterstützt wird, sollten Sie aus Leistungsgründen ein benutzerdefiniertes CLR-Aggregat über eine cursorbasierte Implementierung in Betracht ziehen.

Streamingtabellenwertfunktionen

Anwendungen müssen oft als Reaktion auf einen Funktionsaufruf eine Tabelle als Ergebnis zurückgeben. Beispiele dafür sind das Lesen von Tabellendaten aus einer Datei als Teil eines Importvorgangs oder die Konvertierung von durch Trennzeichen getrennten Werte in eine relationale Darstellung. In der Regel erreichen Sie dies durch Materialisieren und Auffüllen der Ergebnistabelle, bevor sie vom Aufrufer verwendet werden kann. Die Integration der CLR in SQL Server führt einen neuen Erweiterbarkeitsmechanismus ein, der als Streamingtabellenwertfunktion (STVF) bezeichnet wird. Verwaltete STVF sind leistungsfähiger als vergleichbare Implementierungen mit erweiterten gespeicherten Prozeduren.

STVFs sind verwaltete Funktionen, die eine IEnumerable-Schnittstelle zurückgeben. IEnumerable verfügt über Methoden, um in von STVF zurückgegebenen Resultsets zu navigieren. Wenn die STVF aufgerufen wird, wird die zurückgegebene IEnumerable-Schnittstelle direkt mit dem Abfrageplan verbunden. Der Abfrageplan ruft IEnumerable-Methoden auf, wenn er Zeilen abrufen muss. Dieses Iterationsmodell ermöglicht es, dass Ergebnisse sofort nach Abruf der ersten Zeile verarbeitet werden. Es muss nicht gewartet werden, bis die gesamte Tabelle aufgefüllt ist. Dadurch wird zudem der durch den Funktionsaufruf benötigte Arbeitsspeicher stark reduziert.

Arrays im Vergleich zu Cursorn

Wenn Transact-SQL-Cursor Daten durchlaufen müssen, die einfacher als Array ausgedrückt werden, kann verwalteter Code mit erheblichen Leistungsgewinnen verwendet werden.

Zeichenfolgendaten

SQL Server-Zeichendaten, z . B. Varchar, können vom Typ "SqlString" oder "SqlChars" in verwalteten Funktionen sein. SqlString-Variablen erstellen im Arbeitsspeicher eine Instanz des gesamten Werts. SqlChars-Variablen stellen eine Streamingschnittstelle bereit, mit der eine höhere Leistung und bessere Skalierbarkeit erreicht wird, die jedoch nicht zum Erstellen einer Instanz des gesamten Werts im Arbeitsspeicher verwendet werden kann. Dies wird für Daten mit großen Objekten (LOB) wichtig. Darüber hinaus kann über eine von SqlXml.CreateReader() zurückgegebene Streamingschnittstelle auf XML-Serverdaten zugegriffen werden.

CLR im Vergleich zu erweiterten gespeicherten Prozeduren

Die Microsoft.SqlServer.Server Anwendungsprogrammierschnittstellen (APPLICATION Programming Interfaces, APIs), mit denen verwaltete Prozeduren Resultsets zurück an den Client senden können, sind besser als die ODS-APIs (Open Data Services), die von erweiterten gespeicherten Prozeduren verwendet werden. Darüber hinaus unterstützen die System.Data.SqlServer-APIs Datentypen wie XML, varchar(max), nvarchar(max) und varbinary(max), eingeführt in SQL Server 2005 (9.x), während die ODS-APIs nicht erweitert wurden, um die neuen Datentypen zu unterstützen.

Mit verwaltetem Code verwaltet SQL Server die Verwendung von Ressourcen wie Arbeitsspeicher, Threads und Synchronisierung. Dies liegt daran, dass die verwalteten APIs, die diese Ressourcen verfügbar machen, über dem SQL Server-Ressourcen-Manager implementiert werden. Umgekehrt hat SQL Server keine Ansicht oder Kontrolle über die Ressourcennutzung der erweiterten gespeicherten Prozedur. Wenn beispielsweise eine erweiterte gespeicherte Prozedur zu viel CPU- oder Arbeitsspeicherressourcen verbraucht, gibt es keine Möglichkeit, diese mit SQL Server zu erkennen oder zu steuern. Mit verwaltetem Code kann SQL Server jedoch erkennen, dass ein bestimmter Thread nicht für einen längeren Zeitraum zurückgegeben wurde, und dann erzwingt die Aufgabe, sodass andere Arbeiten geplant werden können. Die Verwendung von verwaltetem Code bietet also bessere Skalierbarkeit und Systemressourcennutzung.

Verwalteter Code verursacht möglicherweise zusätzlichen Aufwand, der erforderlich ist, um die Ausführungsumgebung aufrechtzuerhalten und Sicherheitsüberprüfungen durchzuführen. Dies ist beispielsweise der Fall, wenn sie in SQL Server ausgeführt wird und zahlreiche Übergänge von verwaltetem zu systemeigenem Code erforderlich sind (da SQL Server bei threadspezifischen Einstellungen zusätzliche Wartung durchführen muss, wenn sie zu systemeigenem Code und zurück wechseln). Daher können erweiterte gespeicherte Prozeduren verwalteten Code, der in SQL Server ausgeführt wird, erheblich übersteigen, wenn es häufige Übergänge zwischen verwaltetem und systemeigenem Code gibt.

Hinweis

Entwickeln Sie keine neuen erweiterten gespeicherten Prozeduren, da dieses Feature veraltet ist.

Native Serialisierung für benutzerdefinierte Typen

Benutzerdefinierte Typen (UDTs) wurden als Erweiterungsmechanismus für das Skalartypsystem entworfen. SQL Server implementiert ein Serialisierungsformat für UDTs namens Format.Native. Während der Kompilierung wird die Struktur des Typs zur Generierung von MSIL geprüft, das für die betreffende Typdefinition angepasst wird.

Native Serialisierung ist die Standardimplementierung für SQL Server. Die benutzerdefinierte Serialisierung ruft eine Methode auf, die vom Typautor für die Durchführung der Serialisierung definiert wurde. Für eine optimale Leistung sollte, sofern möglich, die Format.Native-Serialisierung verwendet werden.

Normalisierung vergleichbarer UDTs

Relationale Vorgänge, z. B. die Sortierung und das Vergleichen von UDTs, verwenden direkt die binäre Darstellung des Werts. Zu diesem Zweck wird eine normalisierte (binär sortierte) Darstellung des UDT-Zustands auf Festplatte gespeichert.

Die Normalisierung hat zwei Vorteile:

  • dadurch wird der Vergleichsvorgang erheblich kostengünstiger, indem die Konstruktion der Typinstanz und der Methodenaufrufaufwand vermieden werden.

  • es erstellt eine binäre Domäne für das UDT und ermöglicht die Erstellung von Histogrammen, Indizes und Histogrammen für Werte des Typs.

Normalisierte UDTs verfügen also über ein ähnliches Leistungsprofil wie die systemeigenen integrierten Typen für Vorgänge, die keinen Methodenaufruf erfordern.

Skalierbare Speicherauslastung

Um die verwaltete Garbage Collection in SQL Server gut auszuführen und zu skalieren, vermeiden Sie eine große, einzelne Zuordnung. Zuordnungen, die größer als 88 KB sind, werden auf dem Heap für große Objekte platziert, was dazu führt, dass die Garbage Collection schlechter als viele kleinere Zuordnungen ausgeführt und skaliert wird. Wenn Sie z. B. ein großes mehrdimensionales Array zuweisen müssen, ist es besser, ein gezacktes (punktiertes) Array zuzuweisen.