Sie können im Allgemeinen keine Ausgabe ALTER DATABASE
innerhalb eines Triggers (oder einer Transaktion mit anderen Anweisungen) ausgeben . Wenn Sie dies versuchen, wird folgende Fehlermeldung angezeigt:
Meldung 226, Ebene 16, Status 6, Zeile xxxx
ALTER DATABASE-Anweisung innerhalb einer Transaktion mit mehreren Anweisungen nicht zulässig.
Der Grund, warum dieser Fehler in der Antwort von @ sp_BlitzErik nicht aufgetreten ist, ist ein Ergebnis des angegebenen speziellen Testfalls: Der oben gezeigte Fehler ist ein Laufzeitfehler, während der in seiner Antwort aufgetretene Fehler ein Fehler zur Kompilierungszeit ist. Dieser Fehler bei der Kompilierung verhindert die Ausführung des Befehls und daher gibt es keine "Laufzeit". Wir können den Unterschied erkennen, indem wir Folgendes ausführen:
SET NOEXEC ON;
SELECT N'g' COLLATE Latin1;
SET NOEXEC OFF;
Der obige Stapel wird fehlerhaft sein, während der folgende nicht:
SET NOEXEC ON;
BEGIN TRAN
CREATE TABLE #t (Col1 INT);
ALTER DATABASE CURRENT COLLATE Latin1_General_100_BIN2;
ROLLBACK TRAN;
SET NOEXEC OFF;
Damit haben Sie zwei Möglichkeiten:
Übernehmen Sie die Transaktion innerhalb des DDL-Triggers so, dass die Transaktion keine weiteren Anweisungen enthält. Dies ist keine gute Idee, wenn es mehrere DDL-Trigger gibt, die von einer CREATE DATABASE
Anweisung ausgelöst werden können , und ist möglicherweise eine schlechte Idee im Allgemeinen, aber es funktioniert ;-). Der Trick besteht darin, dass Sie auch eine neue Transaktion im Trigger starten müssen, da SQL Server sonst feststellt, dass die Anfangs- und Endwerte für @@TRANCOUNT
nicht übereinstimmen, und einen diesbezüglichen Fehler auslöst. Der folgende Code macht genau dies und gibt auch nur dann aus, ALTER
wenn die Sortierung nicht die gewünschte ist, andernfalls wird der ALTER
Befehl übersprungen .
USE [master];
GO
CREATE TRIGGER trg_DDL_ChangeDatabaseCollation
ON ALL SERVER
FOR CREATE_DATABASE
AS
SET NOCOUNT ON;
DECLARE @CollationName [sysname] = N'Latin1_General_100_BIN2',
@SQL NVARCHAR(4000);
SELECT @SQL = N'ALTER DATABASE ' + QUOTENAME(sd.[name]) + N' COLLATE ' + @CollationName
FROM sys.databases sd
WHERE sd.[name] = EVENTDATA().value(N'(/EVENT_INSTANCE/DatabaseName)[1]', N'sysname')
AND sd.[collation_name] <> @CollationName;
IF (@SQL IS NOT NULL)
BEGIN
PRINT @SQL; -- DEBUG
COMMIT TRAN; -- close existing Transaction, else will get error
EXEC sys.sp_executesql @SQL;
BEGIN TRAN; -- begin new Transaction, else will get different error
END;
ELSE
BEGIN
PRINT 'Collation already correct.';
END;
GO
Test mit:
-- skip ALTER:
CREATE DATABASE [tttt] COLLATE Latin1_General_100_BIN2;
DROP DATABASE [tttt];
-- perform ALTER:
CREATE DATABASE [tttt] COLLATE SQL_Latin1_General_CP1_CI_AI;
DROP DATABASE [tttt];
Verwenden SQLCLR ein regelmäßigen / extern zu schaffen SqlConnection
, mit Enlist = false;
der Connection String, der zur Ausgabe von ALTER
Befehl als das nicht Teil der Transaktion sein wird.
Es scheint, dass SQLCLR keine echte Option ist, obwohl dies nicht auf eine spezifische Einschränkung von SQLCLR zurückzuführen ist. Irgendwie hat die Eingabe von " da dies nicht Teil der Transaktion sein wird " direkt oben die Tatsache nicht ausreichend hervorgehoben, dass es tatsächlich eine aktive Transaktion um die CREATE DATABASE
Operation gibt. Das Problem hierbei ist , dass während SQLCLR kann zu Schritt außerhalb der aktuellen Transaktion verwendet wird, gibt es noch keine Möglichkeit für eine weitere Sitzung der Datenbank zu ändern , zur Zeit erstellt wird , bis dass die erste Transaktion Commits.
Das heißt, Sitzung A erstellt die Transaktion für die Erstellung der Datenbank und das Auslösen des Triggers. Der Abzug, mit SQLCLR wird Session B erstellen , die Datenbank zu ändern , die erstellt wurde, aber die Transaktion noch nicht begangen , da sie in der Warteschleife, bis Session B abgeschlossen ist , was es kann nicht , weil es für diese erste Transaktion wartet auf Komplett. Dies ist ein Deadlock, kann jedoch von SQL Server nicht als solcher erkannt werden, da nicht bekannt ist, dass Sitzung B von etwas in Sitzung A erstellt wurde. Dieses Verhalten kann durch Ersetzen des ersten Teils der IF
Anweisung im Beispiel festgestellt werden oben in # 1 mit folgendem:
IF (@SQL IS NOT NULL)
BEGIN
/*
PRINT @SQL; -- DEBUG
COMMIT TRAN; -- close existing Transaction, else will get error
EXEC sys.sp_executesql @sql;
BEGIN TRAN; -- begin new Transaction, else will get different error
*/
DECLARE @CMD NVARCHAR(MAX) = N'EXEC xp_cmdshell N''sqlcmd -S . -d master -E -Q "'
+ @SQL + N';" -t 15''';
PRINT @CMD;
EXEC (@CMD);
END;
ELSE
...
Der -t 15
Schalter für SQLCMD legt das Befehls- / Abfragezeitlimit fest, damit der Test mit dem Standardzeitlimit nicht ewig wartet. Aber du kannst es länger als 15 Sekunden einstellen und in einer anderen Sitzung überprüfen sys.dm_exec_requests
, ob all die schönen Blockierungen stattfinden ;-).
Stellen Sie das Ereignis an eine Stelle, die dann aus dieser Warteschlange gelesen wird, und führen Sie die entsprechende ALTER DATABASE
Anweisung aus. Auf diese Weise kann die CREATE DATABASE
Anweisung abgeschlossen und ihre Transaktion festgeschrieben werden. Anschließend ALTER DATABASE
kann eine Anweisung ausgeführt werden. Service Broker könnte hier verwendet werden. ODER erstellen Sie eine Tabelle, lassen Sie den Trigger in diese Tabelle einfügen, und lassen Sie einen SQL Server-Agent-Job eine gespeicherte Prozedur aufrufen, die aus dieser Tabelle liest, die ALTER DATABASE
Anweisung ausführt und dann den Datensatz aus der Warteschlangentabelle entfernt.
Die oben genannten Optionen werden jedoch hauptsächlich zur Unterstützung von Szenarien bereitgestellt, in denen jemand wirklich eine Art von ALTER DATABASE
DDL-Trigger ausführen muss. Wenn Sie in diesem speziellen Szenario wirklich nicht möchten, dass Datenbanken die Standardkollatierung auf System- / Instanzebene verwenden, werden Sie wahrscheinlich am besten bedient von:
- Erstellen einer neuen Instanz mit der gewünschten Sortierung und Verschieben aller Benutzerdatenbanken in diese.
- Wenn es sich nur um die Systemdatenbanken handelt, die nicht der idealen Sortierung entsprechen, ist es wahrscheinlich sicher, die Systemkollatierung über die Befehlszeile über setup.exe zu ändern (z
Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=...
. B. diese Option erstellt die System-DBs neu, sodass Sie sie benötigen um Objekte auf Serverebene usw. zu skripten, um sie später neu zu erstellen, und um Patches usw. erneut anzuwenden (FUN, FUN, FUN).
Oder für Abenteuerlustige gibt es die undokumentierte sqlservr.exe -q
Option (dh nicht unterstützte Option, die auf eigenes Risiko verwendet wird, aber möglicherweise sehr gut funktioniert) , mit der ALLE DBs und ALLE Spalten aktualisiert werden (siehe Ändern Die Zusammenstellung der Instanz, der Datenbanken und aller Spalten in allen Benutzerdatenbanken: Was könnte möglicherweise schief gehen? ( für eine detaillierte Beschreibung des Verhaltens dieser Option sowie des möglichen Einflussbereichs).
Unabhängig von der gewählten Option: Stellen Sie immer sicher, dass Sie Backups von master
und haben, msdb
bevor Sie solche Dinge versuchen.
Der Grund, warum es sich lohnt, die Standardkollatierung auf Serverebene zu ändern, besteht darin, dass die Standardkollatierung der Instanz (dh auf Serverebene) einige Funktionsbereiche steuert, die zu unerwartetem / inkonsistentem Verhalten führen können, da jeder erwartet, dass Zeichenfolgenoperationen funktionieren in Anlehnung an die Standardkollatierung für alle Ihre Benutzerdatenbanken:
Standardkollatierung für Zeichenfolgenspalten in temporären Tabellen. Dies ist nur beim Vergleich mit / Unioning mit anderen Zeichenfolgenspalten ein Problem, wenn zwischen den beiden Zeichenfolgenspalten eine Nichtübereinstimmung besteht. Das Problem hierbei ist, dass COLLATE
es viel wahrscheinlicher (wenn auch nicht garantiert) zu Problemen kommt, wenn die Sortierung nicht explizit über das Schlüsselwort angegeben wird.
Dies ist kein Problem für den XML-Datentyp, Tabellenvariablen oder enthaltene Datenbanken.
Metadaten auf Instanzebene. In dem name
Feld in sys.databases
wird beispielsweise die Standardkollatierung auf Instanzebene verwendet. Andere Systemkatalogansichten sind ebenfalls betroffen, aber ich habe nicht die vollständige Liste.
Metadaten auf Datenbankebene wie sys.objects
und sys.indexes
sind nicht betroffen.
- Namensauflösung für:
- lokale Variablen (dh
@variable
)
- Cursor
GOTO
Etiketten
Wenn bei der Sortierung auf Instanzebene beispielsweise die Groß- und Kleinschreibung nicht berücksichtigt wird, während die Sortierung auf Datenbankebene binär ist (dh mit _BIN
oder endet _BIN2
), ist die Auflösung von Objektnamen auf Datenbankebene binär (z. B. [TableA] <> [tableA]
), bei Variablennamen wird jedoch die Groß- und Kleinschreibung nicht berücksichtigt (zB @VariableA = @variableA
).