Вы не можете, вообще говоря, выпускать ALTER DATABASE
в Триггере (или в любой Транзакции, в которой есть другие заявления). Если вы попытаетесь, вы получите следующую ошибку:
Сообщение 226, Уровень 16, Состояние 6, Строка xxxx
Оператор ALTER DATABASE не разрешен в транзакции с несколькими операторами.
Причина, по которой эта ошибка не встречалась в ответе @ sp_BlitzErik, является результатом предоставленного конкретного теста: показанная выше ошибка является ошибкой во время выполнения, тогда как ошибка, обнаруженная в его ответе, является ошибкой времени компиляции. Эта ошибка во время компиляции препятствует выполнению команды и, следовательно, не имеет времени выполнения. Мы можем увидеть разницу, запустив следующее:
SET NOEXEC ON;
SELECT N'g' COLLATE Latin1;
SET NOEXEC OFF;
Вышеуказанная партия будет с ошибкой, а следующая не будет:
SET NOEXEC ON;
BEGIN TRAN
CREATE TABLE #t (Col1 INT);
ALTER DATABASE CURRENT COLLATE Latin1_General_100_BIN2;
ROLLBACK TRAN;
SET NOEXEC OFF;
Это оставляет вам два варианта:
Зафиксируйте транзакцию в триггере DDL так, чтобы в транзакции не было других операторов. Это не очень хорошая идея, если есть несколько триггеров DDL, которые могут быть запущены CREATE DATABASE
оператором, и, возможно, это плохая идея в целом, но она работает ;-). Хитрость заключается в том, что вам также нужно начать новую транзакцию в триггере, иначе SQL Server заметит, что начальное и конечное значения @@TRANCOUNT
не совпадают, и выдаст ошибку, связанную с этим. Приведенный ниже код делает именно это, а также выдает только, ALTER
если сортировка не является желаемой, иначе она пропускает ALTER
команду.
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
Тест с:
-- 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];
Используйте SQLCLR, чтобы установить обычный / внешний SqlConnection
, Enlist = false;
в строке подключения, для выдачи ALTER
команды, так как она не будет частью транзакции.
Похоже, что SQLCLR на самом деле не вариант, хотя не из-за какого-либо конкретного ограничения SQLCLR. Каким-то образом ввод текста « как это не будет частью транзакции » непосредственно выше недостаточно подчеркивал факт наличия активной транзакции вокруг CREATE DATABASE
операции. Проблема здесь заключается в том, что хотя SQLCLR можно использовать для выхода за пределы текущей транзакции, другой сеанс по-прежнему не может модифицировать базу данных, создаваемую в данный момент, до тех пор , пока эта первоначальная транзакция не будет зафиксирована.
Это означает, что сессия A создает транзакцию для создания базы данных и запуска триггера. Триггер, использующий SQLCLR, создаст сеанс B, чтобы изменить созданную базу данных, но транзакция еще не зафиксирована, поскольку она находится в режиме ожидания до завершения сеанса B, чего не может быть, потому что она ожидает этой первоначальной транзакции для полный. Это тупик, но он не может быть обнаружен как таковой SQL Server, поскольку он не знает, что сеанс B был создан чем-то внутри сеанса A. Это поведение можно увидеть, заменив первую часть IF
оператора в примере выше в # 1 со следующим:
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
...
-t 15
Переключатель для SQLCMD задает команду / запрос тайм - аут , так что тест не ждать вечно с тайм - аут по умолчанию. Но вы можете установить его длительностью более 15 секунд и в другом сеансе проверить, sys.dm_exec_requests
чтобы увидеть все происходящие прекрасные блокировки ;-).
Поставьте в очередь событие где-нибудь, чтобы потом прочитать из этой очереди и выполнить соответствующий ALTER DATABASE
оператор. Это позволит CREATE DATABASE
оператору завершиться и выполнить транзакцию, после чего ALTER DATABASE
оператор может быть выполнен. Сервисный брокер может быть использован здесь. ИЛИ, создайте таблицу, вставьте триггер в эту таблицу, затем задание агента SQL Server вызовет хранимую процедуру, которая читает из этой таблицы и выполняет ALTER DATABASE
инструкцию, а затем удаляет запись из таблицы очереди.
ОДНАКО вышеупомянутые опции в основном предоставлены, чтобы помочь в сценариях, где кто-то действительно должен сделать некоторый тип в ALTER DATABASE
пределах DDL Trigger. В этом конкретном сценарии, если вы действительно не хотите, чтобы какие-либо базы данных использовали параметры сортировки по умолчанию на уровне системы / экземпляра, вам, вероятно, лучше всего подойдут:
- Создание нового экземпляра с желаемой сортировкой и перенос всех пользовательских баз данных на него.
- Или, если это не только системные базы данных, которые имеют неидеальную сортировку, вероятно, безопасно изменить системную сортировку из командной строки через setup.exe (например
Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=...
, эта опция воссоздает системные базы данных, поэтому вам потребуется для создания сценариев объектов уровня сервера и т. д. для последующего создания, а также повторного применения исправлений и т. д., FUN, FUN, FUN).
Или, для приключений в глубине души, есть недокументированный (то есть неподдерживаемый sqlservr.exe -q
вариант « используйте на свой страх и риск, но возможно, очень хорошо работает»), который обновляет ВСЕ БД и ВСЕ столбцы (см. Изменение «Сравнение экземпляра, баз данных и всех столбцов во всех пользовательских базах данных: что может быть неправильным» для подробного описания поведения этой опции, а также потенциальной области влияния).
Независимо от выбранного варианта: всегда проверяйте наличие резервных копий master
и msdb
перед попыткой таких действий .
Причиной того, что стоит изменить сортировку по умолчанию на уровне сервера, является то, что по умолчанию сортировка экземпляра (т.е. уровня сервера) контролирует несколько функциональных областей, которые могут привести к неожиданному / противоречивому поведению, поскольку все ожидают, что строковые операции будут функционировать в соответствии с параметрами сортировки по умолчанию для всех ваших пользовательских баз данных:
Сортировка по умолчанию для строковых столбцов во временных таблицах. Эта проблема возникает только при сравнении / Объединение с другими строковыми столбцами, ЕСЛИ существует несоответствие между двумя строковыми столбцами. Проблема здесь заключается в том, что, если не указать Collation в явном виде через COLLATE
ключевое слово, гораздо более вероятно (хотя и не гарантировано) столкнуться с проблемами.
Это не проблема для типа данных XML, табличных переменных или автономных баз данных.
Метаданные уровня экземпляра. Например, в name
поле sys.databases
будет использоваться сопоставление по умолчанию на уровне экземпляра. Другие представления системного каталога также затронуты, но у меня нет полного списка.
Метаданные на уровне базы данных, такие как sys.objects
и sys.indexes
, не затрагиваются.
- Разрешение имени для:
- локальные переменные (то есть
@variable
)
- курсоры
GOTO
этикетки
Например, если сопоставление на уровне экземпляра нечувствительно к регистру, а сопоставление на уровне базы данных является двоичным (т. Е. Оканчивается на _BIN
или _BIN2
), тогда разрешение имен объектов уровня базы данных будет двоичным (например [TableA] <> [tableA]
), а имена переменных будут учитывать нечувствительность к регистру (например @VariableA = @variableA
).