一般的に言えば、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;
これには2つのオプションがあります。
トランザクションに他のステートメントがないように、DDLトリガー内でトランザクションをコミットします。これは、CREATE DATABASE
ステートメントによって起動できるDDLトリガーが複数ある場合はお勧めできません。一般的にはお勧めできませんが、機能します;-)。トリックは、トリガーでも新しいトランザクションを開始する必要があることです。そう@@TRANCOUNT
しないと、SQL Serverはの開始値と終了値が一致しないことに気づき、それに関連するエラーをスローします。以下のコードはこれを実行するだけ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が完了するまで保留されているため、まだコミットされていません。コンプリート。これはデッドロックですが、セッションBがセッションA内の何かによって作成されたことを認識していないため、SQL Serverによって検出することはできません。この動作は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
...
SQLCMDの-t 15
スイッチはコマンド/クエリのタイムアウトを設定するため、テストはデフォルトのタイムアウトで永遠に待機しません。しかし、これを15秒より長く設定して、別のセッションで、素敵なブロックがすべて行われていることを確認できます;-)。sys.dm_exec_requests
イベントをどこかにキューに入れ、そのキューから読み取り、適切なALTER DATABASE
ステートメントを実行します。これにより、CREATE DATABASE
ステートメントが完了し、トランザクションがコミットALTER DATABASE
されます。その後、ステートメントを実行できます。ここではService Brokerを使用できます。または、テーブルを作成し、トリガーをそのテーブルに挿入し、SQL Serverエージェントジョブに、そのテーブルから読み取り、ALTER DATABASE
ステートメントを実行して、キューテーブルからレコードを削除するストアドプロシージャを呼び出します。
ただし、上記のオプションは主に、誰かがALTER DATABASE
DDLトリガー内で何らかのタイプの操作を実際に行う必要があるシナリオを支援するために提供されています。この特定のシナリオで、どのデータベースにもシステム/インスタンスレベルのデフォルトの照合順序を使用させたくない場合は、おそらく次の方法が最適です。
- 目的の照合順序で新しいインスタンスを作成し、すべてのユーザーデータベースをそのインスタンスに移動します。
- または、それが非理想的な照合順序のシステムデータベースだけである場合、setup.exeを介してコマンドラインからシステム照合順序を変更することはおそらく安全
Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=...
です(たとえば、このオプションはシステムDBを再作成するため、必要になりますサーバーレベルのオブジェクトなどをスクリプト化して後で再作成し、さらにパッチなどを再適用します(FUN、FUN、FUN)。
または、冒険心のある方のために、sqlservr.exe -q
すべてのDBとすべての列を更新する、文書化されていない(つまり、サポートされていない、使用時に独自のリスクであるが、適切な作業を行う)オプションがあります(変更を参照してください)。このオプションの動作の詳細な説明と影響の可能性のある範囲については、インスタンス、データベース、およびすべてのユーザーデータベースのすべての列の照合順序:何が問題になるのでしょうか?)
かかわらず、オプションの選択:いつものバックアップ持っていることを確認してくださいmaster
とmsdb
、このような事を試みる前に。
サーバーレベルのデフォルトの照合順序を変更するのに労力を費やす価値があるのは、インスタンス(つまりサーバーレベル)のデフォルトの照合順序が、予期しない動作や一貫性のない動作につながる可能性のあるいくつかの機能領域を制御するためです。すべてのユーザーデータベースのデフォルトの照合順序に沿って:
一時テーブルの文字列列のデフォルトの照合順序。これは、2つの文字列列の間に不一致がある場合に他の文字列列と比較/結合する場合にのみ問題になります。ここでの問題は、COLLATE
キーワードを介して照合順序を明示的に指定しない場合、問題が発生する可能性が非常に高い(保証されていません)ことです。
これは、XMLデータ型、テーブル変数、または包含データベースの問題ではありません。
インスタンスレベルのメタデータ。たとえば、のname
フィールドでsys.databases
は、インスタンスレベルのデフォルトの照合が使用されます。他のシステムカタログビューも影響を受けますが、完全なリストはありません。
sys.objects
およびなどのデータベースレベルのメタデータsys.indexes
は影響を受けません。
- 名前解決:
- ローカル変数(つまり
@variable
)
- カーソル
GOTO
ラベル
たとえば、インスタンスレベルの照合順序が大文字と小文字を区別せず、データベースレベルの照合順序がバイナリ(_BIN
またはで終わる_BIN2
)の場合、データベースレベルのオブジェクト名解決はバイナリ(例[TableA] <> [tableA]
:)になりますが、変数名は大文字と小文字を区別しません。 (例@VariableA = @variableA
)。