Vous ne pouvez pas, en règle générale, émettre ALTER DATABASE
au sein d'un déclencheur (ou de toute transaction contenant d'autres déclarations). Si vous tentez de le faire, vous obtiendrez l'erreur suivante:
Msg 226, niveau 16, état 6,
instruction xxx xxx ALTER DATABASE de ligne non autorisée dans la transaction multi-instruction.
La raison pour laquelle cette erreur n'a pas été rencontrée dans la réponse de @ sp_BlitzErik est le résultat du scénario de test spécifique fourni: l'erreur indiquée ci-dessus est une erreur d'exécution, tandis que l'erreur rencontrée dans sa réponse est une erreur de compilation. Cette erreur au moment de la compilation empêche l'exécution de la commande et il n'y a donc pas de "temps d'exécution". Nous pouvons voir la différence en exécutant ce qui suit:
SET NOEXEC ON;
SELECT N'g' COLLATE Latin1;
SET NOEXEC OFF;
Le lot ci-dessus entraînera une erreur, tandis que les suivants ne le seront pas:
SET NOEXEC ON;
BEGIN TRAN
CREATE TABLE #t (Col1 INT);
ALTER DATABASE CURRENT COLLATE Latin1_General_100_BIN2;
ROLLBACK TRAN;
SET NOEXEC OFF;
Cela vous laisse deux options:
Validez la transaction dans le déclencheur DDL de sorte qu'il n'y ait aucune autre instruction dans la transaction. Ce n'est pas une bonne idée s'il existe plusieurs déclencheurs DDL qui peuvent être déclenchés par une CREATE DATABASE
instruction, et c'est peut-être une mauvaise idée en général, mais cela fonctionne ;-). L'astuce est que vous devez également commencer une nouvelle transaction dans le déclencheur sinon SQL Server remarquera que les valeurs de début et de fin de @@TRANCOUNT
ne correspondent pas et générera une erreur liée à cela. Le code ci-dessous ne fait que cela, et émet également uniquement ALTER
si le classement n'est pas celui souhaité, sinon il ignore la ALTER
commande.
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
Testez avec:
-- 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];
Utilisez SQLCLR pour établir un régulier / externe SqlConnection
, avec Enlist = false;
dans la chaîne de connexion, pour émettre la ALTER
commande car cela ne fera pas partie de la transaction.
Il semble que SQLCLR ne soit pas vraiment une option, mais pas en raison d'une limitation spécifique de SQLCLR. D'une manière ou d'une autre, taper " car cela ne fera pas partie de la transaction " directement ci-dessus n'a pas suffisamment mis en évidence le fait qu'il existe, en fait, une transaction active autour de l' CREATE DATABASE
opération. Le problème ici est que même si SQLCLR peut être utilisé pour sortir de la transaction en cours, il n'y a toujours aucun moyen pour une autre session de modifier la base de données en cours de création jusqu'à ce que la transaction initiale soit validée.
Signification, la session A crée la transaction pour la création de la base de données et le déclenchement du déclencheur. Le déclencheur, à l'aide de SQLCLR, créera la session B pour modifier la base de données qui a été créée, mais la transaction n'a pas encore été validée car elle est en attente jusqu'à la fin de la session B, ce qu'elle ne peut pas car elle attend cette transaction initiale pour Achevée. Il s'agit d'un blocage, mais il ne peut pas être détecté en tant que tel par SQL Server car il ne sait pas que la session B a été créée par quelque chose dans la session A. Ce comportement peut être vu en remplaçant la première partie de l' IF
instruction dans l'exemple ci-dessus dans # 1 avec ce qui suit:
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
...
Le -t 15
commutateur pour SQLCMD définit le délai d'expiration de la commande / requête afin que le test n'attende pas indéfiniment avec le délai d'expiration par défaut. Mais, vous pouvez le définir pour qu'il sys.dm_exec_requests
dure plus de 15 secondes et dans une autre session, vérifiez tous les blocages en cours ;-).
Mettez l'événement en file d'attente quelque part qui sera ensuite lu à partir de cette file d'attente et exécutez l' ALTER DATABASE
instruction appropriée . Cela permettra à l' CREATE DATABASE
instruction de se terminer et à sa transaction de se valider, après quoi une ALTER DATABASE
instruction peut être exécutée. Service Broker peut être utilisé ici. OU, créez une table, insérez le déclencheur dans cette table, puis demandez à un travail de l'Agent SQL Server d'appeler une procédure stockée qui lit à partir de cette table et exécute l' ALTER DATABASE
instruction, puis supprime l'enregistrement de la table de file d'attente.
TOUTEFOIS, les options ci-dessus sont principalement fournies pour aider dans les scénarios où quelqu'un a vraiment besoin de faire un certain type de ALTER DATABASE
déclencheur DDL. Dans ce scénario particulier, si vous ne voulez vraiment pas que des bases de données utilisent le classement par défaut au niveau système / instance, vous serez probablement mieux servi par:
- Créer une nouvelle instance avec le classement souhaité et y déplacer toutes vos bases de données utilisateur.
- Ou, si ce ne sont que les bases de données système qui sont du classement non idéal, il est probablement sûr de changer le classement système à partir de la ligne de commande via setup.exe (par exemple
Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=...
; cette option recrée les bases de données système, vous aurez donc besoin pour créer des scripts pour les objets au niveau du serveur, etc. pour recréer plus tard, ainsi que pour réappliquer les correctifs, etc., FUN, FUN, FUN).
Ou, pour les aventuriers de cœur, il y a l' sqlservr.exe -q
option non documentée (c'est-à-dire non prise en charge, à vos risques et périls mais pourrait très bien fonctionner) qui met à jour TOUTES les bases de données et TOUTES les colonnes (veuillez consulter Modification le classement de l'instance, des bases de données et de toutes les colonnes dans toutes les bases de données utilisateur: qu'est-ce qui pourrait éventuellement mal tourner? pour une description détaillée du comportement de cette option, ainsi que de la portée potentielle de l'impact).
Quelle que soit l'option choisie: assurez-vous toujours d'avoir des sauvegardes de master
et msdb
avant d'essayer de telles choses.
La raison pour laquelle cela vaut la peine de modifier le classement par défaut au niveau du serveur est que le classement par défaut de l'instance (c'est-à-dire au niveau du serveur) contrôle quelques domaines fonctionnels qui pourraient conduire à un comportement inattendu / incohérent, car tout le monde s'attend à ce que les opérations de chaîne fonctionnent dans le sens du classement par défaut pour toutes vos bases de données utilisateur:
Classement par défaut des colonnes de chaînes dans les tables temporaires. Il s'agit d'un problème uniquement lors de la comparaison avec / Union avec d'autres colonnes de chaînes SI il existe une incompatibilité entre les deux colonnes de chaînes. Le problème ici est que lorsque vous ne spécifiez pas explicitement le classement via le COLLATE
mot clé, il est beaucoup plus probable (mais non garanti) de rencontrer des problèmes.
Ce n'est pas un problème pour le type de données XML, les variables de table ou les bases de données contenues.
Métadonnées au niveau de l'instance. Par exemple, le name
champ dans sys.databases
utilisera le classement par défaut au niveau de l'instance. D'autres vues du catalogue système sont également affectées, mais je n'ai pas la liste complète.
Les métadonnées au niveau de la base de données, telles que sys.objects
et sys.indexes
, ne sont pas affectées.
- Résolution de nom pour:
- variables locales (ie
@variable
)
- curseurs
GOTO
Étiquettes
Par exemple, si le classement au niveau de l'instance est insensible à la casse alors que le classement au niveau de la base de données est binaire (c'est-à-dire se terminant par _BIN
ou _BIN2
), la résolution de nom d'objet au niveau de la base de données sera binaire (par exemple [TableA] <> [tableA]
), mais les noms de variables permettront une insensibilité à la casse (par exemple @VariableA = @variableA
).