In linea generale, non è possibile emettere ALTER DATABASE
un trigger (o qualsiasi Transazione che contiene altre dichiarazioni in esso). Se si tenta di, si otterrà il seguente errore:
Messaggio 226, livello 16, stato 6, riga xxxx
Istruzione ALTER DATABASE non consentita nella transazione multiistruzione.
Il motivo per cui questo errore non è stato riscontrato nella risposta di @ sp_BlitzErik è il risultato del caso di test specifico fornito: l'errore mostrato sopra è un errore di runtime, mentre l'errore riscontrato nella sua risposta è un errore di compilazione. Questo errore di compilazione impedisce l'esecuzione del comando e quindi non esiste un "run-time". Possiamo vedere la differenza eseguendo quanto segue:
SET NOEXEC ON;
SELECT N'g' COLLATE Latin1;
SET NOEXEC OFF;
Il batch di cui sopra genererà un errore, mentre il seguente non lo farà:
SET NOEXEC ON;
BEGIN TRAN
CREATE TABLE #t (Col1 INT);
ALTER DATABASE CURRENT COLLATE Latin1_General_100_BIN2;
ROLLBACK TRAN;
SET NOEXEC OFF;
Questo ti lascia con due opzioni:
Esegui il commit della transazione all'interno del trigger DDL in modo tale che non vi siano altre dichiarazioni nella transazione. Questa non è una buona idea se ci sono più trigger DDL che possono essere attivati da CREATE DATABASE
un'istruzione, ed è probabilmente una cattiva idea in generale, ma funziona ;-). Il trucco è che devi anche iniziare una nuova Transazione nel Trigger altrimenti SQL Server noterà che i valori di inizio e fine per @@TRANCOUNT
non corrispondono e genererà un errore correlato a quello. Il codice qui sotto fa proprio questo, ed emette anche solo ALTER
se il Collation non è quello desiderato, altrimenti salta il ALTER
comando.
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
Prova con:
-- 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];
Utilizzare SQLCLR per stabilire un normale / esterno SqlConnection
, con Enlist = false;
nella stringa di connessione, per emettere il ALTER
comando in quanto non farà parte della transazione.
Sembra che SQLCLR non sia veramente un'opzione, anche se non a causa di alcuna limitazione specifica di SQLCLR. In qualche modo, digitando "in quanto ciò non farà parte della Transazione " direttamente sopra non è stato sufficientemente evidenziato il fatto che esiste, di fatto, una Transazione attiva attorno CREATE DATABASE
all'operazione. Il problema qui è che mentre SQLCLR può essere utilizzato per uscire dalla Transazione corrente, non è ancora possibile che un'altra Sessione modifichi il Database attualmente in fase di creazione fino a quando la Transazione iniziale non viene confermata.
Significato, la sessione A crea la transazione per la creazione del database e l'attivazione del trigger. Il trigger, utilizzando SQLCLR, creerà la sessione B per modificare il database che è stato creato, ma la transazione non è stata ancora impegnata poiché è in attesa fino al completamento della sessione B, cosa che non può perché è in attesa che la transazione iniziale venga completare. Questo è un deadlock, ma non può essere rilevato come tale da SQL Server poiché non sa che la Sessione B è stata creata da qualcosa all'interno della Sessione A. Questo comportamento può essere visto sostituendo la prima parte IF
dell'istruzione nell'esempio sopra in # 1 con il seguente:
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
...
L' opzione-t 15
per SQLCMD imposta il timeout comando / query in modo che il test non attenda per sempre con il timeout predefinito. Ma puoi impostarlo in modo che sia più lungo di 15 secondi e in un'altra sessione controlla sys.dm_exec_requests
per vedere tutti gli adorabili blocchi in corso ;-).
Metti in coda l'evento da qualche parte che poi leggerà da quella coda ed eseguirà l' ALTER DATABASE
istruzione appropriata . Ciò consentirà il CREATE DATABASE
completamento dell'istruzione e il commit della sua transazione, dopodiché ALTER DATABASE
sarà possibile eseguire un'istruzione. Service Broker può essere utilizzato qui. OPPURE, creare una tabella, fare in modo che il trigger venga inserito in quella tabella, quindi un processo di SQL Server Agent chiama una stored procedure che legge da quella tabella ed esegue l' ALTER DATABASE
istruzione e quindi rimuove il record dalla tabella delle code.
TUTTAVIA, le opzioni di cui sopra sono principalmente fornite per aiutare in scenari in cui qualcuno ha davvero bisogno di fare un qualche tipo ALTER DATABASE
all'interno di un trigger DDL. In questo particolare scenario, se davvero non si desidera che nessun database utilizzi le regole di confronto predefinite a livello di sistema / istanza, è probabile che si verifichino i migliori servizi:
- Creazione di una nuova istanza con le regole di confronto desiderate e spostamento di tutti i database utente su di essa.
- Oppure, se sono solo i database di sistema che fanno parte delle regole di confronto non ideali, è probabilmente sicuro modificare le regole di confronto del sistema dalla riga di comando tramite setup.exe (ad esempio
Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=...
; questa opzione ricrea i DB di sistema, quindi sarà necessario per eseguire lo script di oggetti a livello di server, ecc. per ricrearli in seguito, oltre a riapplicare patch, ecc., FUN, FUN, FUN).
Oppure, per gli avventurosi, c'è l' sqlservr.exe -q
opzione non documentata (cioè non supportata, usa-a-il-tuo-rischio-ma-potrebbe-molto-funzionante) che aggiorna TUTTI i DB e TUTTE le colonne (vedi Modifica la raccolta delle istanze, dei database e di tutte le colonne in tutti i database degli utenti: cosa potrebbe andare storto? per una descrizione dettagliata del comportamento di questa opzione, nonché del potenziale ambito di impatto).
Indipendentemente dall'opzione scelta: assicurarsi sempre di disporre di backup master
e msdb
prima di tentare tali operazioni.
Il motivo per cui varrebbe la pena modificare la Fascicolazione predefinita a livello di server è che la Fascicolazione predefinita dell'istanza (ovvero a livello di server) controlla alcune aree funzionali che potrebbero portare a comportamenti imprevisti / incoerenti, poiché tutti si aspettano che le operazioni sulle stringhe funzionino sulla falsariga delle regole di confronto predefinite per tutti i database utente:
Fascicolazione predefinita per colonne di stringhe in tabelle temporanee. Questo è un problema solo se confrontato con / Unione con altre colonne di stringhe SE c'è una discrepanza tra le due colonne di stringhe. Il problema qui è che quando non si specifica esplicitamente la raccolta tramite la COLLATE
parola chiave, è molto più probabile (sebbene non garantito) incorrere in problemi.
Questo non è un problema per il tipo di dati XML, le variabili di tabella o i database contenuti.
Meta-dati a livello di istanza. Ad esempio, il name
campo in sys.databases
utilizzerà le regole di confronto predefinite a livello di istanza. Anche le altre visualizzazioni del catalogo di sistema sono interessate, ma non ho l'elenco completo.
I metadati a livello di database, come sys.objects
e sys.indexes
, non sono interessati.
- Risoluzione del nome per:
- variabili locali (ie
@variable
)
- cursori
GOTO
etichette
Ad esempio, se le regole di confronto a livello di istanza non fanno distinzione tra maiuscole e minuscole mentre le regole di confronto a livello di database sono binarie (ovvero terminano in _BIN
o _BIN2
), la risoluzione dei nomi degli oggetti a livello di database sarà binaria (ad esempio [TableA] <> [tableA]
), ma i nomi delle variabili consentiranno di distinguere tra maiuscole e minuscole (ad es @VariableA = @variableA
.).