In der gestrigen Liste kommen jede Menge Timeouts vor, die zu einer Fehlermeldung bei der Bearbeitung von SQL-Befehlen führen können. Heute stellten wir fest, dass ein "Command Timeout" in der Anwendung (also am Client) am SQL-Server zu offenen Transaktionen führen können.
Beispiel: Die Anwendung schickt einen SQL-Batch an den SQL-Server der am Stück ausgeführt werden soll:
BEGIN TRANSACTION;
INSERT …. SELECT ….; – irgendetwas schrecklich Langes
COMMIT;
Wenn das zu lange dauert und das Client-API daher einen Command-Timeout wirft, dann schickt der Client per TDS einen "Cancel" zum Server. Das sah ich leider nur mit dem NetMon, nicht via Profiler. Der SQL-Server unterbricht dann sofort die Ausführung des aktuellen Batches. Aus dem Batch wird kein weiterer Befehl ausgeführt. Auch kein ROLLBACK, dass in einem CATCH-Block im SQL-Batch enthalten ist. Die Transaktion bleibt einfach offen. Damit hatte ich nicht gerechnet.
Daher muss man in der Fehlerbehandlung der Anwendung immer mal noch einen SQL-Befehl über die Connection schicken, selbst wenn die Transaktion komplett als Batch an den SQL-Server geschickt wurde. Das geht beispielsweise mit einem bedingten ROLLBACK:
IF @@TRANCOUNT > 0 ROLLBACK;
Ausprobiert haben wir ODBC und ADO.net. Bei beiden war es so. Bei ODBC mag es ja meistens noch gereicht haben die Connection einfach zu beenden, was zu einem Rollback führt. Bei ADO.net reicht das normalerweise nicht. Hier ist der bedingte ROLLBACK-Befehl fast immer nötig, weil bei ADO.net in der Regel Connection-Pooling verwendet wird: eine geschlossene Verbindung wandert zurück in den Pool, sie wird nicht wirklich geschlossen. Erst beim Rausholen aus dem Pool wird zum Zweck des Recycling ein "Reset" auf der Connection abgesetzt, dass alle Optionen zurücksetzt, Transaktionen schließt und Sperren freigibt. Diese Kombination kann dazu führen, dass Zombi-Sperren sehr lange gehalten werden und zu einem hübschen Blockierungsproblem führen… 😉