Anda tidak dapat, secara umum, mengeluarkan ALTER DATABASEdalam Pemicu (atau Transaksi apa pun yang memiliki pernyataan lain di dalamnya). Jika Anda berusaha, Anda akan mendapatkan kesalahan berikut:
Msg 226, Level 16, Negara 6, Baris xxxx
ALTER DATABASE pernyataan tidak diizinkan dalam transaksi multi-pernyataan.
Alasan bahwa kesalahan ini tidak ditemukan dalam jawaban @ sp_BlitzErik adalah hasil dari kasus uji khusus yang disediakan: kesalahan yang ditunjukkan di atas adalah kesalahan run-time, sedangkan kesalahan yang ditemukan dalam jawabannya adalah kesalahan waktu kompilasi. Kesalahan waktu kompilasi mencegah eksekusi perintah dan karenanya tidak ada "run-time". Kita dapat melihat perbedaannya dengan menjalankan yang berikut ini:
SET NOEXEC ON;
SELECT N'g' COLLATE Latin1;
SET NOEXEC OFF;
Batch di atas akan error, sedangkan yang berikut tidak akan:
SET NOEXEC ON;
BEGIN TRAN
CREATE TABLE #t (Col1 INT);
ALTER DATABASE CURRENT COLLATE Latin1_General_100_BIN2;
ROLLBACK TRAN;
SET NOEXEC OFF;
Ini memberi Anda dua opsi:
Melakukan Transaksi dalam Pemicu DDL sedemikian rupa sehingga tidak ada pernyataan lain dalam Transaksi. Ini bukan ide yang baik jika ada beberapa Pemicu DDL yang dapat dipecat oleh CREATE DATABASEpernyataan, dan mungkin ide yang buruk secara umum, tetapi itu berhasil ;-). Kuncinya adalah bahwa Anda juga perlu memulai Transaksi baru di Trigger SQL Server yang lain akan melihat bahwa nilai awal dan akhir untuk @@TRANCOUNTtidak cocok dan akan menimbulkan kesalahan terkait dengan itu. Kode di bawah tidak hanya ini, dan juga hanya mengeluarkan ALTERjika Collation bukan yang diinginkan, jika tidak maka lompatan ALTERperintah.
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
Uji dengan:
-- 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];
Gunakan SQLCLR untuk membuat biasa / eksternal SqlConnection, dengan Enlist = false;di Connection Connection, untuk mengeluarkan ALTERperintah karena itu tidak akan menjadi bagian dari Transaksi.
Tampaknya SQLCLR bukan benar-benar pilihan, meskipun bukan karena batasan spesifik SQLCLR. Entah bagaimana, mengetik " karena itu tidak akan menjadi bagian dari Transaksi " langsung di atas tidak cukup menyoroti fakta bahwa, pada kenyataannya, ada Transaksi aktif di sekitar CREATE DATABASEoperasi. Masalahnya di sini adalah bahwa sementara SQLCLR dapat digunakan untuk melangkah keluar dari Transaksi saat ini, masih belum ada cara bagi Sesi lain untuk memodifikasi Database yang saat ini sedang dibuat sampai Transaksi awal tersebut dilakukan.
Artinya, Sesi A menciptakan Transaksi untuk pembuatan Database dan penembakan Pemicu. Pemicu, menggunakan SQLCLR, akan membuat Sesi B untuk memodifikasi Database yang telah dibuat, tetapi Transaksi belum dilakukan sejak ditahan sampai Sesi B selesai, yang tidak bisa karena menunggu Transaksi awal untuk lengkap. Ini adalah jalan buntu, tetapi tidak dapat dideteksi seperti itu oleh SQL Server karena tidak tahu bahwa Sesi B dibuat oleh sesuatu dalam Sesi A. Perilaku ini dapat dilihat dengan mengganti bagian pertama dari IFpernyataan dalam contoh. di atas dalam # 1 dengan yang berikut:
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
...
The -t 15saklar untuk SQLCMD set perintah / permintaan batas waktu sehingga tes tidak menunggu selamanya dengan batas waktu default. Tapi, Anda bisa mengaturnya menjadi lebih dari 15 detik dan di sesi lain periksa sys.dm_exec_requestsuntuk melihat semua pemblokiran yang indah terjadi ;-).
Antri acara di suatu tempat yang kemudian akan membaca dari antrian itu dan jalankan ALTER DATABASEpernyataan yang sesuai . Ini akan memungkinkan untuk menyelesaikan CREATE DATABASEpernyataan dan transaksinya, setelah ALTER DATABASEpernyataan dapat dieksekusi. Broker Layanan dapat digunakan di sini. ATAU, buat tabel, minta Pemicu menyisipkan ke dalam tabel itu, lalu minta pekerjaan SQL Server Agent memanggil Prosedur Tersimpan yang membaca dari tabel itu dan menjalankan ALTER DATABASEpernyataan dan kemudian menghapus catatan dari Tabel antrian.
NAMUN, opsi di atas terutama disediakan untuk membantu dalam skenario di mana seseorang benar-benar perlu melakukan beberapa jenis ALTER DATABASEdalam Pemicu DDL. Dalam skenario khusus ini, jika Anda benar-benar tidak ingin database menggunakan sistem / Collation default tingkat Instance, maka Anda mungkin akan dilayani oleh:
- Membuat instance baru dengan Collation yang diinginkan dan memindahkan semua Database pengguna Anda ke sana.
- Atau, jika itu hanya database sistem yang dari Collation non-ideal, mungkin aman untuk mengubah sistem Collation dari baris perintah melalui setup.exe (misalnya
Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=...; opsi ini menciptakan kembali sistem DB, jadi Anda akan perlu untuk skrip keluar objek tingkat server, dll untuk membuat ulang nanti, ditambah menerapkan kembali tambalan, dll, MENYENANGKAN, MENYENANGKAN, MENYENANGKAN).
Atau, untuk para petualang, ada opsi yang tidak terdokumentasi (yaitu tidak didukung, digunakan dengan risiko Anda sendiri tetapi beresiko sangat baik) sqlservr.exe -qyang memperbarui SEMUA DB dan kolom ALL (silakan lihat Mengubah Kolaborasi Instance, Basis Data, dan Semua Kolom di Semua Basis Data Pengguna: Apa yang Mungkin Salah? untuk deskripsi terperinci tentang perilaku opsi ini, serta potensi lingkup dampak).
Terlepas dari opsi yang dipilih: selalu pastikan untuk memiliki cadangan masterdan msdbsebelum mencoba hal-hal seperti itu.
Alasan yang sepadan dengan upaya untuk mengubah Collation default tingkat Server adalah bahwa Collation default Instance (yaitu Server-level) mengontrol beberapa area fungsional yang dapat menyebabkan perilaku tak terduga / tidak konsisten adalah semua orang mengharapkan operasi string berfungsi sepanjang garis Collation default untuk semua Database Pengguna Anda:
Kolasi Default untuk kolom string dalam tabel sementara. Ini adalah masalah hanya ketika membandingkan / Menyatukan dengan kolom string lain JIKA ada ketidakcocokan antara dua kolom string. Masalahnya di sini adalah bahwa ketika tidak menentukan Kolasi secara eksplisit melalui COLLATEkata kunci, kemungkinan besar (meskipun tidak dijamin) akan mengalami masalah.
Ini bukan masalah untuk tipe data XML, variabel tabel, atau Database yang Terkandung.
Meta-data tingkat instan. Misalnya, namebidang dalam sys.databasesakan menggunakan Kolasi default tingkat Instance. Tampilan katalog sistem lain juga terpengaruh, tetapi saya tidak memiliki daftar lengkap.
Meta-data tingkat basis data, seperti sys.objectsdan sys.indexes, tidak terpengaruh.
- Resolusi nama untuk:
- variabel lokal (yaitu
@variable)
- kursor
GOTO label
Misalnya, jika Instance-level Collation adalah case-insensitive sedangkan Collation-level Database adalah biner (yaitu diakhiri dengan _BINatau _BIN2), maka resolusi nama objek level-Database akan menjadi biner (misalnya [TableA] <> [tableA]) namun nama variabel akan memungkinkan untuk case-insensitivity (misalnya @VariableA = @variableA).