Wie kann man die CLR-Funktion aus Sicht der Leistung besser nutzen (in jeder Datenbank wiederholen oder allgemeine Funktion haben)?

7

Ich habe eine Frage zur Validierung der XMLVerwendung von XSD schemainside gestellt SQL Server 2012(siehe Link ). Ich verstehe (wie ich vermutet habe), dass ich verwenden muss CLR Function. Die Funktion wird erhalten XSD schema textund XML textund Validierung machen.

Ich werde 1 Konfigurationsdatenbank und viele Installationsdatenbanken haben. Unter diesem Gesichtspunkt frage ich mich, wo diese Funktion erstellt werden soll - in der Konfigurationsdatenbank oder in jeder Installationsdatenbank?

Aus Sicht der Unterstützung wäre es besser, nur eine CLR-Funktion zu haben.

Bogdan Bogdanov
quelle
Zu Ihrer Information, ich habe gerade aktualisiert, um weitere Informationen aufzunehmen :-). (Ich bin mir nicht sicher, ob Sie benachrichtigt wurden, als ich meinen Kommentar zu meiner Antwort hinzufügte, da Ihr Kommentar entfernt wurde.)
Solomon Rutzky

Antworten:

8

Dies scheint ein Duplikat dieser Frage zu sein:

Einrichten einer zentralen CLR-Bibliothek für gespeicherte Prozeduren / Funktionen für interne gespeicherte Prozesse in anderen Datenbanken?

Ich bin jedoch nicht der Meinung, dass eine der beiden Antworten angemessen ist, da sie einige der wichtigeren Aspekte dieser Frage nicht erwähnen.


Hier gibt es keine offensichtliche Wahl, welcher Ort für SQLCLR-Objekte im Allgemeinen besser ist, da durch die Ausführung des SQLCLR-Codes Einschränkungen auferlegt werden können. Es gibt einige Verwendungszwecke, bei denen die Assembly in jeder einzelnen Datenbank vorhanden sein muss, und eine Verwendung, bei der sich die Assembly in einer zentralisierten Datenbank befinden muss. Es hängt alles von einigen verschiedenen Aspekten ab, was der Code tut. Daher müssen wir uns ansehen, was diese Aspekte sind, um festzustellen, ob es überhaupt eine Wahl gibt und wenn ja, welche Vor- und Nachteile dies hätte.

SQLCLR-spezifische funktionale Aspekte

  • Benutzerdefinierte Typen (UDTs): UDTs können nicht datenbankübergreifend referenziert werden. Sie können nicht mit dreiteiligen Namen deklariert werden (z. B. DatabaseName.SchemaName.UserDefinedTypeName). Wenn UDTs verwendet werden, muss die Assembly zu jeder Datenbank hinzugefügt werden, in der der UDT verwendet wird. Wenn jedoch andere SQLCLR-Objekte verwendet werden und davon ausgegangen wird, dass diese Objekte entweder in einer zentralen Datenbank oder in jeder Kunden- / Anwendungs-Datenbank platziert werden können, können Sie die UDTs jederzeit in einer Assembly platzieren, die in jedem Kunden platziert wird / application DB und eine andere Assembly mit Funktionen / gespeicherten Prozeduren / benutzerdefinierten Aggregaten / Triggern.

  • Sicherheit:

    • Wie viele Datenbanken sind betroffen: Tut der CLR-Code etwas, bei dem die Assembly mit einem PERMISSION_SETvon entweder EXTERNAL_ACCESSoder gekennzeichnet werden muss UNSAFE? Wenn ja, importieren Sie DLLs, die außerhalb Ihrer Kontrolle signiert wurden und nicht zurückgetreten werden können? In der Regel handelt es sich dabei um nicht unterstützte .NET Framework-Bibliotheken oder DLLs von Drittanbietern. Wenn Sie keine Kontrolle über das Signieren von Baugruppen haben, die entweder EXTERNAL_ACCESSoder markiert werden müssen, müssen UNSAFESie möglicherweise die Datenbank mit den Baugruppen auf setzen TRUSTWORTHY ON. Seit dem Einstellen einer Datenbank aufTRUSTWORTHY ONDa dies ein Sicherheitsrisiko darstellt, ist es vorzuziehen, die Anzahl der Datenbanken zu minimieren, für die Sie dies tun müssten. In diesem Fall scheint das Einfügen des Codes in eine zentralisierte Datenbank ein besserer Ansatz zu sein. Und wenn Sie bereits eine zentrale Datenbank für anderen Code haben und diese Art von Sicherheitsrisiko wirklich minimieren möchten, können Sie eine zweite zentralisierte Datenbank nur für diesen Code haben.

      Wenn Sie die Kontrolle über das Signieren der DLL (s) haben, sollten Sie auf jeden Fall ein Zertifikat oder einen asymmetrischen Schlüssel in der masterDatenbank basierend auf der DLL erstellen, dann eine Anmeldung basierend auf diesem Zertifikat oder diesem asymmetrischen Schlüssel erstellen und dann zuweisen entweder die EXTERNAL ACCESS ASSEMBLYoder die UNSAFE ASSEMBLYErlaubnis zu diesem Login. Diese wenigen Schritte genau dort (und die einzigen Dinge, die erstellt werden, sind das Zertifikat oder der Schlüssel und die Anmeldung) ermöglichen es, dass jede Assembly, die mit demselben privaten Schlüssel signiert ist, entweder EXTERNAL_ACCESSoder UNSAFE(abhängig davon, welche Berechtigung für die Anmeldung erteilt wurde), Nr egal in welche Datenbank (en) es geladen wird. Und wenn Sie dazu in der Lage sind, können Sie Baugruppen entweder auf EXTERNAL_ACCESSoder setzenUNSAFEin allen Kunden- / Anwendungsdatenbanken ohne größeres Sicherheitsrisiko, als wenn Sie denselben Code in eine zentralisierte Datenbank gestellt hätten ** .

    • Unterschiedliche Berechtigungen für unterschiedliche Clients / Apps sind erforderlich: Wenn einige Clients / Apps aus irgendeinem Grund andere BerechtigungenPERMISSION_SET als andere benötigen, müssen die Assemblys in jede Client- / App-Datenbank geladen werden. Auf diese Weise können Sie einige Datenbanken verwenden, SAFEwährend andere verwendet werden EXTERNAL_ACCESS. Dies geht über das hinaus, was mit Berechtigungen auf Objektebene möglich ist. Indem Sie eine Assembly festlegen, die Code für Dateisystemfunktionen enthält SAFE, stellen Sie sicher, dass der Code nicht funktioniert, auch wenn jemand einen Weg findet, Ihre reguläre Sicherheit zu umgehen, und EXECUTEdie gespeicherte SQLCLR-Prozedur weiterhin ausführen kann .

  • AppDomains: Dieser Aspekt betrifft die Speicher- / Ressourcennutzung und -trennung. Dies ist wahrscheinlich der Bereich, der in Bezug auf Überlegungen am stärksten beeinflusst, aber wahrscheinlich auch am wenigsten verstanden wird. Lassen Sie uns zunächst untersuchen, wie T-SQL-Objekte dieselbe zentrale Datenbank für jede Client- / App-Datenbankfrage behandeln würden.

    T-SQL-Funktionen und gespeicherte Prozeduren speichern nach ihrer Ausführung ihre Ausführungspläne im Plan-Cache (also nicht in Inline-TVFs), der sich im Speicher befindet. Wenn Sie nur an die Speichernutzung denken, hat die Verwendung einer zentralisierten Datenbank den Vorteil, dass ein einzelner Plan anstelle eines Plans pro Client / App-Datenbank gespeichert wird, insbesondere wenn 100 oder mehr DBs vorhanden sind. Ein zwischengespeicherter Plan wirft jedoch die Frage auf, ob er ein optimaler Plan für nachfolgende Ausführungen ist oder nicht. Es ist möglich, dass ein einziger Plan für einige großartig, für andere aber auch fürchterlich ist, da die Ausführung in so vielen Client- / App-DBs möglicherweise sehr unterschiedlich ist. Wenn Sie nicht möchten, dass die Leistung beeinträchtigt wirdWITH RECOMPILEDie Bereitstellung auf jeder Client- / App-Datenbank würde eine individuellere Optimierung ermöglichen. Kurz gesagt: Die zentrale Datenbank verwendet weniger Speicher für den Plan-Cache, aber möglicherweise eine schlechtere Leistung. Separate DBs bieten mehr Speicher für den Plan-Cache, aber weniger potenzielle Leistungsprobleme.

    Wenn es um SQLCLR-Objekte geht, gibt es für jeden Ansatz die gleichen Vor- und Nachteile beim Plan-Caching. Aber jetzt, da wir uns mit App-Domänen befassen, sind zusätzliche Konsequenzen zu berücksichtigen. App-Domänen sind Speicherbereiche / Sandboxen, in denen .NET Code ausführt. Jede App-Domäne ist eine eigene Sandbox. In SQL Server werden App-Domänen für jede Kombination aus Datenbank und Assembly-Eigentümer erstellt. Mehrere Assemblies in derselben Datenbank, die demselben Benutzer gehören, teilen sich eine App-Domäne, aber Assemblies in derselben DB, die einem anderen Benutzer gehören, haben eine andere App-Domäne, und Assemblys in anderen DBs befinden sich in ihren eigenen App-Domänen. In diesem Sinne:

    • Der Speicherverbrauch steigt bei der Bereitstellung auf einzelnen Client- / App-DBs schneller an, da die verwendeten Assemblys in die App-Domänen geladen werden (sie werden jedoch erst geladen, wenn sie zum ersten Mal verwendet werden). Die App-Domäne enthält auch alle Variablen, Ressourcenhandles usw. (bis diese Dinge für Garbage Collection und markiert sindGC entscheidet, dass Mond und Sterne perfekt ausgerichtet sind und nimmt dies als Zeichen zum Laufen. Eine 2-MB-Assembly in einer Datenbank mit einer AppDomain, in der eine bestimmte Menge an Speicher für Variablen usw. reserviert ist, kann sich also erheblich vom Laden derselben Assembly in 100 DBs unterscheiden, in denen sie jetzt 200 MB beträgt (technisch gesehen gibt es einen Teil der DLL) Das wird im Speicher von mehreren Instanzen geteilt, aber ich bin mir nicht sicher, wie ich das messen soll.) plus 100-mal so viel Speicherplatz, der für Variablen usw. reserviert ist.

      Ein verwandtes Problem ist, wenn Sie reguläre Ausdrücke verwenden und die RegEx-Option verwenden, für Compileddie der Ausdruck bis zur Intermediate Language (MSIL) kompiliert wird. Dies beschleunigt zwar die Verwendung wiederholt verwendeter Ausdrücke, aber sobald ein Ausdruck kompiliert wurde, kann er nicht mehr gesammelt werden und bleibt in der AppDomain, bis er neu gestartet wird. Wenn es eine häufig verwendete RegEx-Funktion gibt, die diese CompiledOption verwendet, wird der zum Speichern verwendete Speicher für jede Datenbank wiederholt, wenn die Assembly in jede Datenbank geladen wird. In diesem Fall kann es sinnvoll sein, diesen Code in einer zentralen Datenbank abzulegen.

    • Ressourcenbeschränkungen können bei der Verwendung einer zentralisierten Datenbank ein Problem sein. Abhängig davon, welche Klassen Sie verwenden, können Sie unwissentlich einen Ressourcenengpass verursachen. Zum Beispiel:

      • Wenn Sie die statischen RegEx-Methoden anstelle der Instanzmethoden verwenden, werden die von Ihnen verwendeten regulären Ausdrücke zwischengespeichert. Die Standard-Cache-Größe beträgt jedoch nur 15 Ausdrücke. Wenn eine große Anzahl von Ausdrücken von einer großen Anzahl von Clients oder Apps gesendet wird, bleiben Ausdrücke nicht sehr lange im Cache. Wenn dies der einzige Grund ist, die Assembly in jede Datenbank zu laden, können Sie einfach die Cache-Größe erhöhen. Weitere Informationen finden Sie auf der MSDN-Seite für RegEx.CacheSize .

      • In ähnlicher Weise gibt es beim Herstellen WebRequestseiner Standardmaximalanzahl aktiver Verbindungen, die zu einem bestimmten URI hergestellt werden können. Und dieser Standardwert ist nur 2. Wenn Sie mehr Anforderungen an denselben URI stellen (sehr einfach, wenn es sich um einen statischen Speicherort handelt und Sie eine zentralisierte Datenbank für diesen Code verwenden), warten alle Anforderungen über diesem Maximum einfach in der Schlange eine aktuelle Verbindung zum Schließen (dh Blockieren). Sie müssten also entweder die Assembly in jede Client- / App-Datenbank laden oder das Limit für Verbindungen pro URI erhöhen. Sie können das Standardmaximum für alle URIs in der aktuellen App-Domäne festlegen, indem Sie ServicePointManager.DefaultConnectionLimit festlegen(Dies kann einmal pro Start der App-Domäne festgelegt werden, z. B. in einem statischen Klassenkonstruktor.) Sie kann auch auf URI-Basis festgelegt werden, indem Sie eine HttpWebRequest erstellen und dann die Eigenschaft .ServicePoint.ConnectionLimit festlegen (dies muss erforderlich sein) Dies erfolgt jedes Mal, wenn die WebRequest instanziiert wird, da das Objekt eine maximale Lebensdauer hat. Sobald der Müll gesammelt wurde, wird das ConnectionLimit auf den ServicePointManager.DefaultConnectionLimitWert zurückgesetzt, wie oben angegeben, wenn eine neue Instanz erstellt wird.

    • Wenn Sie eine statische Variable zum Zwischenspeichern bestimmter Werte verwenden (gemeinsam genutzter Speicher - selten, aber immer noch möglich), müssen Sie entscheiden, in welchem ​​Umfang diese Werte gemeinsam genutzt werden sollen. Wenn die Freigabe in jeder Client- / App-Datenbank enthalten sein soll, laden Sie die Assembly in jede Client- / App-Datenbank. Wenn Sie diese Werte jedoch für alle DBs freigeben möchten, fügen Sie die Assembly in eine gemeinsam genutzte, zentralisierte DB ein.

Allgemeine funktionale Aspekte

  • Datenbankzugriff: Verweist der Code auf datenbankspezifische Objekte? Beachten Sie, dass die Ausführung von SQL mithilfe des In-Process / der Context Connection = true;Verbindung zunächst ausgeführt wird, wobei die "aktuelle" Datenbank auf die Datenbank festgelegt wird, in der das Objekt vorhanden ist, und nicht unbedingt auf die Datenbank, von der aus das Objekt aufgerufen wird. Daher kann Code, der in einer Kunden- / Anwendungsdatenbank ausgeführt wird und ein Objekt in einer zentralen Datenbank aufruft, Objekte nicht mit nur zweiteiligen Namen referenzieren. Sie können jedoch weiterhin eine zentralisierte Datenbank für diesen Code verwenden, solange Sie einen Eingabeparameter für @DatabaseName(use :) haben [SqlFacet(MaxSize = 128)] SqlString DatabaseNameund diesen dann übergeben DB_NAME(). Dann können Sie DatabaseName.Valueim SQLCLR-Code entweder aUSE Anweisung oder zum Verketten in Dynamic SQL, um die entsprechenden vollständig qualifizierten Objektnamen (dh dreiteilige Namen) zu erstellen.

    Dies ist wahrscheinlich kein entscheidender Faktor, wenn Sie nur auf systembasierte Objekte verweisen (dh sys.databases), die unabhängig von der Datenbank, in der Sie sich befinden, dieselben Zeilen zurückgeben. Es sollte auch kein Problem sein, wenn Sie eine externe Verbindung herstellen, da dies bereits der Fall wäre Wenn Sie den Datenbanknamen für die Verbindungszeichenfolge übergeben, melden Sie sich einfach bei der Standarddatenbank für die Anmeldung an, die die Verbindung herstellt.

  • Sortierunterschiede: Wenn die Sortierungen zwischen der zentralisierten Datenbank und der Client / App-Datenbank identisch sind, ist dies kein entscheidender Faktor bei der Entscheidung zwischen diesen beiden Modellen. Wenn Ihr System jedoch unterschiedliche Kollatierungen unterstützt, müssen Sie wissen, was Ihr Code tut, da dies möglicherweise durch die Kollatierungspräzision beeinflusst wird. Wenn Sie Zeichenfolgen senden, die mit anderen Zeichenfolgen verglichen werden, entspricht das Verhalten möglicherweise nicht den Erwartungen, auch wenn kein Fehler auftritt. Die zum Vergleichen lokaler Variablen und Zeichenfolgenliterale verwendete Kollatierung ist die Standardkollatierung, in der das Objekt (dh gespeicherte Prozedur oder Funktion) vorhanden ist. Wenn sich diese Sortierung von der Sortierung der "aktuellen" Datenbank unterscheidet, wenn dieses Objekt aufgerufen wird (wenn ein Literal oder eine Variable übergeben wird), oder sich von dem Feld unterscheidet, das übergeben wird, kann es verschiedene Unterschiede bei der Art und Weise dieses Vergleichs geben erledigt. Wenn eine Vielzahl von Kollatierungen unterstützt wird, sind stringbasierte Vorgänge möglicherweise stabiler / konsistenter, wenn der Code für jede Client- / App-Datenbank bereitgestellt wird.

Ablenkungen

Die folgenden Gründe werden in dieser Frage und in der oben in dieser Antwort verlinkten doppelten Frage angegeben, um die eine oder andere Methode zu bevorzugen, aber ich bin der Meinung, dass sie für die Entscheidung, welche Methode besser passt, nicht wirklich relevant sind:

  • Die zentralisierte Datenbank ist einfacher zu unterstützen: Wie? Der Code sollte in der Quellcodeverwaltung nur einmal vorhanden sein. Unter der Annahme, dass der in jede Client / App-Datenbank geladene Code derselbe ist, sollte die Fehlerbehebung in beiden Fällen ungefähr gleich sein. Wenn Sie den SQLCLR-Code getrennt von den Client- / App-DBs in eine gemeinsame Datenbank stellen, wird dies zu einer Komplikationsebene bei datenbankübergreifenden Aufrufen, die technisch als Grund dafür angesehen werden kann, diesen (oder einen anderen) Code nicht zu zentralisieren.
  • Die zentralisierte Datenbank ist bei Bereitstellungen einfacher: Wenn dies der Fall ist, würde ich sagen, dass der Bereitstellungsprozess korrigiert werden muss. Wenn Sie mit einem Modell arbeiten, in dem Sie die Client- / App-Datenbanken duplizieren, um identisches Schema und nur unterschiedliche Daten zu erhalten, tritt dieses Problem bereits beim Bereitstellen von Schemaänderungen auf. Die Bereitstellung von SQLCLR besteht ebenfalls nur aus DDL-Anweisungen. Es ist möglicherweise schwieriger, SQLCLR-Code bereitzustellen, wenn versucht wird, die Assembly aus der DLL zu laden. Es gibt jedoch keinen Grund, dies zu tun. Stellen Sie einfach sicher, dass Sie ein in sich geschlossenes SQL-Skript haben, das ein CREATE ASSEMBLYoder ALTER ASSEMBLYdie Hex-Bytes (dh FROM 0x4D5F000002C...) verwendet.
  • Einzelne Client- / Anwendungs-DBs eignen sich besser für die Sicherung / Wiederherstellung: Wenn hier ein Problem vorliegt und Sie eine Wiederherstellung durchführen müssen, ist es am einfachsten, alles, was in der Client / App-Datenbank enthalten ist, wiederherzustellen. Ich würde sagen, wenn Sie eine Wiederherstellung durchführen müssen, ist das Wiederherstellen von 2 DBs (Client / App-DB und gemeinsamer DB) fast dieselbe Arbeit, und das Wiederherstellen von 101 DBs (100 Client / App-DBs und 1 gemeinsamer DB) ist immer noch hübsch ziemlich dasselbe. Unter dem Gesichtspunkt der Zuverlässigkeit ist entweder Ihr Sicherungsprozess zuverlässig und kann als vertrauenswürdig eingestuft werden, um sowohl Client- / App-DBs als auch allgemeine DBs ordnungsgemäß zu sichern, oder Sie müssen Ihren Sicherungsprozess korrigieren ;-).
  • Einzelne Client- / Anwendungs-DBs sind zum Testen von Variationen einfacher: Bis zu einem gewissen Grad gilt dies, wenn Sie eine Version des Codes testen müssen, ohne alle Referenzen zu ändern. Normalerweise sollte dies jedoch zunächst durch Testen in einer völlig anderen Umgebung erfolgen. Wenn es jedoch einen Kunden gibt, der beispielsweise eine Codeänderung im Beta-Test durchführen wird, kann diese Situation leicht genug gemildert werden, indem das von @AaronBertrand in seiner Antwort auf diese Frage vorgeschlagene Modell verwendet wird, das sehr ähnlich ist (es betrifft hauptsächlich) T-SQL-Objekte und nicht speziell SQLCLR-Code): Funktion in zentraler Datenbank erstellen oder in jeder Datenbank wiederholen? . Das in dieser Antwort diskutierte Modell besteht darin, für die meisten / alle Fälle eine zentralisierte Datenbank zu verwenden, undSynonyme in jeder Client / App-Datenbank erstellen, damit der lokale Code auf lokale Namen verweisen kann. Wenn dann eine Variation benötigt wird, kann Code in eine bestimmte Client / App-Datenbank eingefügt werden, und die Synonyme werden dann entfernt. In diesem Fall würde der lokale T-SQL-Code immer noch auf dieselben lokalen Namen verweisen und den Unterschied nicht kennen. Persönlich mag ich diesen Ansatz sehr und kann mir keinen besonderen Nachteil vorstellen, außer dass ich die Synonyme erstellen / entfernen muss. Dies scheint kein hoher Preis für die gewonnene Flexibilität zu sein.

Ergo: Für die in dieser Frage beschriebene besondere Situation (dh Skalarfunktion, keine externen Ressourcen, kein Zugriff auf Datenbankobjekte, keine Ressourcenbeschränkungen) scheint die Verwendung Ihrer einzelnen Konfigurationsdatenbank in Ordnung zu sein.


** Wenn Sie denken, "aber Assemblys, die auf EXTERNAL_ACCESS oder UNSAFE gesetzt sind, stellen Sicherheitsrisiken dar, weil sie dies zulassen": Ich habe nicht gesagt, dass bei Assemblys, die auf EXTERNAL_ACCESS oder UNSAFE gesetzt sind, überhaupt kein Risiko besteht, wenn Sie die verwenden Zertifikat- / asymmetrische schlüsselbasierte Methode. Was ich damit sagen möchte, ist, dass in dieser Konfiguration das Risiko nicht zwischen dem Platzieren der Assembly in einer zentralisierten Datenbank und dem Platzieren in jeder Client- / Anwendungsdatenbank besteht. Dies liegt daran, dass potenzielle Sicherheitsprobleme, die sich aus Assemblys ergeben können, die auf EXTERNAL_ACCESS oder UNSAFE festgelegt sind, nicht in der Datenbank lokalisiert sind, in der diese Assemblys vorhanden sind (im Gegensatz zu Einstellung TRUSTWORTHYauf ON). Alle Sicherheitsprobleme sind systemweit. Aber beim Einstellen von Datenbanken aufTRUSTWORTHY ONDann haben Sie zusätzliche Sicherheitsprobleme pro Datenbank.

Solomon Rutzky
quelle