En general, no puede emitir ALTER DATABASE
dentro de un Disparador (o cualquier Transacción que contenga otras declaraciones). Si lo intentas, obtendrás el siguiente error:
Msg 226, Nivel 16, Estado 6, Línea xxxx
ALTER DATABASE no está permitida en la transacción de varios estados.
La razón por la que este error no se encontró en la respuesta de @ sp_BlitzErik es el resultado del caso de prueba específico proporcionado: el error que se muestra arriba es un error en tiempo de ejecución, mientras que el error encontrado en su respuesta es un error en tiempo de compilación. Ese error en tiempo de compilación impide la ejecución del comando y, por lo tanto, no hay "tiempo de ejecución". Podemos ver la diferencia ejecutando lo siguiente:
SET NOEXEC ON;
SELECT N'g' COLLATE Latin1;
SET NOEXEC OFF;
El lote anterior producirá un error, mientras que lo siguiente no:
SET NOEXEC ON;
BEGIN TRAN
CREATE TABLE #t (Col1 INT);
ALTER DATABASE CURRENT COLLATE Latin1_General_100_BIN2;
ROLLBACK TRAN;
SET NOEXEC OFF;
Esto te deja con dos opciones:
Confirme la transacción dentro del DDL Trigger de modo que no haya otras declaraciones en la transacción. Esta no es una buena idea si hay varios disparadores DDL que pueden ser disparados por una CREATE DATABASE
declaración, y es posiblemente una mala idea en general, pero funciona ;-). El truco es que también debe comenzar una nueva transacción en el activador, de lo contrario, SQL Server notará que los valores iniciales y finales @@TRANCOUNT
no coinciden y arrojará un error relacionado con eso. El código a continuación hace exactamente esto, y también solo emite ALTER
si la Clasificación no es la deseada, de lo contrario, omite el 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
Prueba 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];
Use SQLCLR para establecer un regular / externo SqlConnection
, con Enlist = false;
la Cadena de conexión, para emitir el ALTER
comando ya que eso no será parte de la Transacción.
Parece que SQLCLR no es realmente una opción, aunque no debido a ninguna limitación específica de SQLCLR. De alguna manera, escribir " ya que eso no será parte de la Transacción " directamente arriba no resaltó suficientemente el hecho de que hay, de hecho, una Transacción activa alrededor de la CREATE DATABASE
operación. El problema aquí es que, si bien SQLCLR se puede usar para salir de la transacción actual, todavía no hay forma de que otra sesión modifique la base de datos que se está creando actualmente hasta que se confirme la transacción inicial.
Es decir, la sesión A crea la transacción para la creación de la base de datos y el disparo del disparador. El Trigger, utilizando SQLCLR, creará la Sesión B para modificar la Base de datos que se ha creado, pero la Transacción aún no se ha confirmado ya que está en espera hasta que se completa la Sesión B, lo cual no puede porque está esperando que esa Transacción inicial completar. Este es un punto muerto, pero SQL Server no puede detectarlo como tal, ya que no sabe que la sesión B fue creada por algo dentro de la sesión A. Este comportamiento se puede ver al reemplazar la primera parte de la IF
declaración en el ejemplo arriba en el # 1 con lo siguiente:
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
...
El -t 15
conmutador para SQLCMD establece el tiempo de espera del comando / consulta para que la prueba no espere para siempre con el tiempo de espera predeterminado. Pero, puede configurarlo para que dure más de 15 segundos y, en otra sesión, verifique sys.dm_exec_requests
que se esté ejecutando todo el encantador bloqueo ;-).
Ponga en cola el evento en algún lugar que luego leerá de esa cola y ejecutará la ALTER DATABASE
instrucción apropiada . Esto permitirá que la CREATE DATABASE
declaración se complete y su transacción se confirme, después de lo cual ALTER DATABASE
se puede ejecutar una declaración. Service Broker podría usarse aquí. O, cree una tabla, haga que el Trigger se inserte en esa tabla, luego haga que un trabajo del Agente SQL Server llame a un Procedimiento almacenado que lea de esa tabla y ejecute la ALTER DATABASE
instrucción y luego elimine el registro de la Tabla de cola.
SIN EMBARGO, las opciones anteriores se proporcionan principalmente para ayudar en escenarios en los que alguien realmente necesita hacer algún tipo de ALTER DATABASE
disparador DDL. En este escenario particular, si realmente no desea que ninguna base de datos esté utilizando la Clasificación predeterminada del nivel de instancia / sistema, entonces probablemente será mejor para usted:
- Crear una nueva instancia con la Clasificación deseada y mover todas sus Bases de datos de usuario a ella.
- O bien, si solo las bases de datos del sistema son de la clasificación no ideal, probablemente sea seguro cambiar la clasificación del sistema desde la línea de comandos a través de setup.exe (por ejemplo
Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=...
; esta opción recrea las bases de datos del sistema, por lo que necesitará para crear scripts de objetos a nivel de servidor, etc. para recrearlos más tarde, además de volver a aplicar parches, etc., FUN, FUN, FUN).
O, para los aventureros de corazón, existe la sqlservr.exe -q
opción indocumentada (es decir, no admitida, usar bajo su propio riesgo, pero podría funcionar) que actualiza TODOS los DB y TODAS las columnas (consulte Cambiar la recopilación de la instancia, las bases de datos y todas las columnas en todas las bases de datos de usuario: ¿qué podría ir mal? para obtener una descripción detallada del comportamiento de esta opción, así como el alcance potencial del impacto).
Independientemente de la opción elegida: siempre asegúrese de tener copias de seguridad de master
y msdb
antes de intentar tales cosas.
La razón por la que valdría la pena cambiar la Clasificación predeterminada del nivel del servidor es que la Clasificación predeterminada de la instancia (es decir, el nivel del servidor) controla algunas áreas funcionales que podrían conducir a un comportamiento inesperado / inconsistente si todos esperan que las operaciones de cadena funcionen siguiendo las líneas de la Clasificación predeterminada para todas sus Bases de datos de usuario:
Clasificación predeterminada para columnas de cadena en tablas temporales. Este es un problema solo cuando se compara / se une con otras columnas de cadena SI hay una discrepancia entre las dos columnas de cadena. El problema aquí es que cuando no se especifica la Clasificación explícitamente a través de la COLLATE
palabra clave, es mucho más probable (aunque no está garantizado) tener problemas.
Esto no es un problema para el tipo de datos XML, las variables de tabla o las bases de datos contenidas.
Metadatos a nivel de instancia. Por ejemplo, el name
campo en sys.databases
utilizará la Clasificación predeterminada de nivel de instancia. Otras vistas del catálogo del sistema también se ven afectadas, pero no tengo la lista completa.
Los metadatos a nivel de base de datos, como sys.objects
y sys.indexes
, no se ven afectados.
- Resolución de nombre para:
- variables locales (es decir
@variable
)
- cursores
GOTO
etiquetas
Por ejemplo, si la Intercalación a nivel de instancia no distingue entre mayúsculas y minúsculas, mientras que la Intercalación a nivel de base de datos es binaria (es decir, terminada en _BIN
o _BIN2
), la resolución de nombre de objeto a nivel de base de datos será binaria (p [TableA] <> [tableA]
. Ej. ), Pero los nombres variables permitirán la insensibilidad a mayúsculas y minúsculas (por ejemplo @VariableA = @variableA
)