none
Wird ein Mutex in einem Datenbank-Assembley benötigt? RRS feed

  • Frage

  • Hallo zusammen

    Ich benutze SQLSERVER 2008 R2 SP 1 auf einem 64-Bit-Rechner mit Win2008 R2 auf Hyper - V.

    Ich habe ein Assembley erstellt, um in einer autonomen Transaction Fehlermeldungen in einer Fehlermeldungstabelle zu schreiben.

    Wenn zwei Betriebssystemprozesse , die sich per SQLServer-Driver an der Datenbank anmelden Prozeduren aufrufen, die im Fehlerfall dieses Assembley aufrufen gibt es die Fehlermedlung 10054 Eine vorhandene Verbindung wurde vom Remotehost geschlossen und die Handles zu dem Connection- oder Command-Objekten in der Assembley  gehen verloren.

    Diese geschieht immer dann, wenn die beiden Prozesse gleichzeitig auf das Assembley zugreifen.

    Benötigt ein Datenbank-Assembley einen Mutex, der die Transaktionsverwaltung aushebeln kann???????

    Vielen Grüße

    MAD

    Montag, 22. Oktober 2012 09:31

Antworten

  • Hallo,

    ob INSERT, UPDATE und/oder verteilt spielt keine Rolle, es muss nur der Anweisungsteil für die Zuweisung der Ausgabevariable durchlaufen werden. Was sein könnte, dass die Prozedur "hängt" und gar nicht bis zu der Stelle kommt.

    Morgen ist mir recht... ich bin hier auch noch am überlegen, wie ich mit geringem Aufwand vergleichbare Fehler produzieren kann - bisher fehlen da noch die zündende Idee ;)

    Gruß Elmar

    • Als Antwort markiert mad0 Freitag, 26. Oktober 2012 08:16
    Donnerstag, 25. Oktober 2012 10:11

Alle Antworten

  • Hallo MAD,

    es können durchaus mehrere Prozesse gleichzeitig eine Assembly nutzen. Dort ist (sollte) nur Code enthalten sein.
    Die Fehlermeldung 10054 rührt i. a. daher dass man versucht mit einer getrennten Verbindung weiterzuarbeiten,
    siehe z. B.: A transport-level error has occurred when sending the request to the server.

    Eher zu vermuten ist, dass Du versuchst eine Verbindung über zwei Sitzungen hin zu nutzen (was ein Programmierfehler wäre).

    Gruß Elmar

    Montag, 22. Oktober 2012 09:54
  • Hallo Mad

    Vielen Dank für Deine Antwort, aber ich verstehe sie noch nicht so ganz.

    Was meinst Du mit  "nur Code enthalten sein"? In dem Assembly wird zuerst eine Verbindung mit der Datenbank erstellt mit EnList = false, damit es eine eigenständige Transktion wird.

    Dann wird ein Command-Objekt erstellt, welches beim Command.execute() eine Datenbankprozedur (Rechte vorhanden) aufruft, die eine Datenbankprozedur aufruft, die die Daten in eine Tabelle schreibt und dabei auch ein zweites Assembly aufrufen wird, welches mit EnList = false eine weiter eigenständige Transaktion erstellt, um einen Wert  in einer weiteren Tabelle zu ändern. Dann gibt es ein Commit, im Fehlerfall ein Rollback.

    Also aus einem Assembly heraus wird über eine Datenbankprozedur noch ein Assembly aufgerufen, welches noch eine Transaktion zu öffnen?

    Geht das??

    Dann passiert es, wenn beide Programme über den SQLServer-Dienst auf die Assemblys zugreifen, daß mal das vorher noch erstellt Connection-Objekt nicht mehr existiert, mal das Command-Objekt zwar erstellt wurde, aber beim nächsten Befehl nicht mehr existiert mit der oben genannten Fehlermeldung.

    Viele Grüße

    MAD

    Montag, 22. Oktober 2012 10:29
  • Hallo Elmar

    Irgendwie bin ich etwas durcheinander. Ich meinte natürlich Hallo Elmar und nich MAD.

    Gruß MAD

    Montag, 22. Oktober 2012 10:38
  • Hallo Zusammen

    Nach meinen bisherigen Recherechen, scheint die Verbindung vom Connection-Pool zurückgesetzt zu werden, warum auch immer. Gibt es eine maximale Dauer, die eine Verbindung haben kann oder gibt es einen Parameter, den man einstellen kann.

    Wenn Datenbankprozeduren  Assemblys gleichzeitig aufrufen, gibt es kein Problem. Wenn aber zwei Anwendungen (.exe) jeweils eine Vebindung mit der Datenbank herstellen und Assemblies aufrufen, so wird die erste Verbindung durch die zweite zurückgesetzt und die zweite Verbindung scheint genau dieselbe Verbindung aus dem Connection-Pool zu bekommen.

    Kann man dafür irgendwie Sorgen, daß die Anwendungen garantiert verschiedene Verbindungen aus dem Connection Pool bekommen oder noch besser kann man den Anwendungen dediziert Verbindungen zuweisen?

    Vile Grüße MAD

    Dienstag, 23. Oktober 2012 10:09
  • Hallo,

    entschuldige, ich bin gestern nicht mehr zu einer Antwort gekommen...
    Die Aussage "nur Code enthalten sein" ist etwas verkürzt, und sollte besagen, dass man keine statischen Variablen verwenden sollte (darf). Da der SQL Server durchweg multi-threading verwendet, sollte man nie statische Variablen verwenden.
    Nicht umsonst verbietet der SQL Server die Nutzung solcher Klassen/Methoden in .NET, siehe Hostschutzattribute und Programmierung der CLR-Integration. Bei selbst geschriebenen Assemblies muss man selbst darauf achten, dass die Restriktionen eingehalten werden.

    Die Clr-Assemblies werden je Benutzer und Datenbank in einer AppDomain im SQL Server Prozess gehostet, siehe sys.dm_clr_appdomains.

    Was die Verbindungen angeht: Wenn Du eine Verbindung freigibst (Dispose/using), so wird sie an den Connection Pool zurückgegeben und bei der nächsten Gelegenheit wieder verwendet. Der SQL Server setzt grundsätzlich die Verbindungseinstellungen zurück, mehr siehe http://stackoverflow.com/questions/962331/why-so-many-sp-resetconnections-for-c-sharp-connection-pooling Somit ist eine Wiederverwendung i. a. kein Problem und wie eine "nagelneue" zu sehen.

    Was Deine Beschreibung vom Ablauf angeht, so ist das nun wirklich überkompliziert gemacht und so etwas provoziert geradezu Fehler. Du solltest das Vorgehen grundsätzlich überdenken und die Zahl der "Hops" zwischen CLR  / Prozeduren / Transaktionen reduzieren. Und evtl. darüber nachdenken, den Code clientseitig so auszuführen, dass er die notwendige Transaktionsisolation berücksichtigt. Denn selbst im günstigen Falle (ohne Fehler) belastet Du den SQL Server Prozess mit einer Menge Aufgaben, die ebensogut durch einen externen Prozess erledigt werden können.

    Gruß Elmar

    Dienstag, 23. Oktober 2012 15:37
  • Hallo Elmar

    Vielen Dank für Deine Antwort.

    Diese Vorgehensweise ist wirklich nicht schön, aber autonome Transaktionen lassen sich so viel ich weiß anders nicht einrichten. Ich würde auch lieber nur Transact-SQL benutzen und benutze Assemblys daher nur, um autonome Transaktionen zu erstellen, die eine Datenbankprozedur aufrufen.

    Ich habe zweimal das Management -Studio aufgerufen, um zwei verschiedene Prozesse zu benutzen und dort die betreffenen Prozeduren parallel in einer While-Schleife 1000000 ausführen lassen. Nach zwei Stunden habe ich dieses abgebrochen, weil kein Fehler auftrat.

    Wenn aber die beiden Anwendungen die Prozeduren aufrufen, gibt es den obengenannten Fehler, wenn beide gleichzeitig darauf zugreifen. Kann es daran liegen, daß ich beim in der Using-Methode mit conn.close() die Verbindung schließe?. Statische Variablen benutze ich nicht.

    Viele Grüße

    MAD

    Mittwoch, 24. Oktober 2012 09:03
  • Hallo,

    ich würde das gerne mal nachstellen, aber die Beschreibung ist dafür zu ungenau -
    und auf der anderen Seite zu kompliziert, um es in Kürze nachzubauen.

    Könntest Du noch etwas genauer beschreiben, was da im einzelnen passiert?

    Gruß Elmar

    Mittwoch, 24. Oktober 2012 09:09
  • Hallo Elmar

    Auch ein kurze Beschreibung ist relativ lang. Ich werde mal das wichtigste zusammenfassen und morgen hier einstellen.

    Viele Grüße, bis  morgen

    MAD

    Mittwoch, 24. Oktober 2012 09:42
  • Hallo Elmar
    Der Ablauf sieht ungefähr so aus:

    Ich habe eine Log-Tabelle in der Fehler oder Ereignisse eingefügt werden können.

    Da geplant war SQLServer 2012 zu benutzen, sollte der Primärschlüssel der Tabelle (und auch anderer Tabellen) eine Sequence sein. Da wir jetzt doch SQLServer2008 benutzen und kein  Autowert für die Primärschlüssel benutzt werden sollen, erstelle ich mir also meine eigenen Sequenzen.

    Sowohl das Einfügen der in die Log-Tabelle, als auch die Ermittlung der nächsten laufenden Nummer erfolgen in einer autonomen Transaktion.

    Dafür wurden zwei Assemblys erstellt. Einmal ein Assembly, dass sich mit Enlist=False anmeldet und die Datenbankprozedur aufruft, die die nächste laufende Nummer zurückgibt. Das zweite Assembly, (ebenfalls als autonome Transaktion Enlist=False)  ruft die Datenbankprozedur auf, die die Daten in die Log-Tabelle einfügt, indem sie zuerst die nächste laufende Nummer ermittelt, also das 1. Assembly aufruft und dann ein Insert into Tabelle ausführt. (Dieses könnte man noch ändern, so dass die nächste laufende Nummer vorher ermittelt wird.)

    Das Schreiben in die Log-Tabelle wurde inzwischen auskommentiert, um unnötige Fehler auszuschließen.

    Es gibt zwei Anwendungen mit jeweils mehreren Threads, die sich mit Hilfe des .Net SqlClient Data Provider mit der Datenbank verbinden. Jeder Thread ist mit critical section abgesichert. Die eine Anwendung ist ein Programm in C geschrieben, welches die Datenbankprozedur A aufruft, die andere Anwendung ist ein C#-Programm das entweder die Datenbankprozedur B oder die Datenbankprozedur C aufruft. Diese drei Prozeduren schreiben in drei unabhängige Tabellen, haben aber den Aufruf der nächsten laufenden Nummer gemein.

    Wenn beide Programme gleichzeitig das Assembly aufrufen, scheint die erste Verbindung zurückgesetzt zu werden. Das eben noch erstellte Connection-Objekt ist nicht mehr vorhanden mit der obigen Fehlermeldung, dass eine Remote-Verbindung diese zurückgesetzt hätte. Im Aktivitätsmonitor des Management Studios wird der Taskstatus suspended mit dem Befehl TM Request angezeigt.

    Wenn nur eines der beiden Programme läuft, gibt es keinen Fehler.

    Es gibt noch eine weitere Merkwürdigkeit, doch ich weiß nicht ob es etwas damit zu tun hat. Die Prozedur A des C-Programms enthält einen OUTPUT-Parameter (die ermittelte nächste laufende Nummer) , der seltsamerweise nur dann zurückgegeben wird, wenn ich in der Prozedur die Benutzung von benutzerdefinierten Tabellentypen auskommentiere. Dieses ist unabhängig davon, ob die zweite Anwendung läuft oder nicht.

    Es ist nun leider ein kleiner Roman geworden, aber ich hoffe Du kannst Dir ungefähr die Abläufe vorstellen.

    Viele Grüße
    MAD

    Mittwoch, 24. Oktober 2012 11:20
  • Hallo,

    neben anderen Dingen stößt mir Deine Aussage bezüglich "TM Request" besonders auf.
    Das könnte ein Hinweis darauf sein, dass Deine Transaktionen zu einer verteilten (DTC) Transaktion heraufgestuft werden.
    Lasse bitte mal den SQL Server Profiler mitlaufen und aktiviere die Ereignisse aus der Transaktions-Kategorie

    Würde eine verteilte Transaktion zurückgesetzt werden - die es eigentlich gar nicht geben sollte, wenn Deine Beschreibung nichts vergisst -, sind auch die Verbindungen "im Eimer".

    Hinweis: TM Aktionen vom SQL Client verwenden den Connect Timeout, siehe SqlClient Timeouts Revealed
    Setze dort ggf. einen niedrigeren Wert ein, um Abbrüche früher zu erhalten.

    Desweiteren - Critical Section hin oder her: Jeder Deiner Threads muss eine eigene SqlClient Verbindung verwenden, denn SqlConnection (wie alle SqlClient Klassen) sind nicht threadsafe.

    Gruß Elmar

    Donnerstag, 25. Oktober 2012 09:31
  • Hallo Elmar

    Ich werde es nachher ausprobieren und Dir dann morgen das Ergebnis mitteilen.

    Kann es auch an einer verteilten Transaktion liegen, wenn das C-Programm keinen Output-Parameter zurückbekommt, obwohl im SQL Server Profiler ein Rückgabewert angezeigt wird. Der Output - Parameter wird immer dann nicht zurückgegeben, wenn die SQL-Engine ein Update oder Insert ausführt, was auch die Aufgabe der aufgerufenen Prozedur ist.

    Viele Grüße

    MAD

    Donnerstag, 25. Oktober 2012 09:53
  • Hallo,

    ob INSERT, UPDATE und/oder verteilt spielt keine Rolle, es muss nur der Anweisungsteil für die Zuweisung der Ausgabevariable durchlaufen werden. Was sein könnte, dass die Prozedur "hängt" und gar nicht bis zu der Stelle kommt.

    Morgen ist mir recht... ich bin hier auch noch am überlegen, wie ich mit geringem Aufwand vergleichbare Fehler produzieren kann - bisher fehlen da noch die zündende Idee ;)

    Gruß Elmar

    • Als Antwort markiert mad0 Freitag, 26. Oktober 2012 08:16
    Donnerstag, 25. Oktober 2012 10:11
  • Hallo Elmar

    Ich habe ein paar Stunden den Profiler mitlaufen lassen. Es sind keine verteilten Transactionen aufgetreten und die Variable wird immer gesetzt, wie man bei RPC:Completed sehen kann.

    Leider war deine und meine bisherige Mühe umsonst. Ich muß jetzt die Assemplys löschen und stattdessen die laufenden Nummern direkt ermitteln und Logmeldungen in das Ereignissprotokoll schreiben.

    Trotzdem noch einmal vielen Dank für deine Mühe und Erklärungen. Wahrscheinlich wird irgendwo eine Verbindung ungewollt geschlossen. Wenn ja, dann wird es wieder auftreten und es liegt diesmal nicht an der Datenbank.

    Viele Grüße

    MAD

    Freitag, 26. Oktober 2012 08:16
  • Hallo,

    es tut mir Leid, dass man Dir weitere Arbeit für solche Nebensächlichkeiten aufbürdet.

    Wenn der Assembly Code in der jetzigen Form "überflüssig" ist, könntest Du ihn bereitstellen (ggf. auch per E-Mail)?
    Denn das Problem als solches interessiert mich grundsätzlich (und etwas Zeit ist eh bereits drauf gegangen ;)

    Gruß Elmar

    Freitag, 26. Oktober 2012 08:41
  • Hallo Elmar

    Ich stelle Dir ein Paket mit den Tabellen, Assemblys und Prozeduren-Skripts zusammen, dann kannst Du etwas spielen. Die C# und C-Anwendungen kann ich Dir leider nicht zur Verfügung stellen, da sie nicht von mir sind. Ich bin nur für die Datenbank zuständig.

    Wie gelange ich jetzt zu Deiner Email-Adresse?

    Viele Grüße

    MAD

    Freitag, 26. Oktober 2012 10:27
  • Hallo,

    die E-Mail kannst Du schicken an: ElmarB (at) gmx.net

    Schon mal besten Dank im voraus.

    Gruß Elmar

    Freitag, 26. Oktober 2012 10:49