Anda tidak dapat, secara umum, mengeluarkan ALTER DATABASE
dalam 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 DATABASE
pernyataan, 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 @@TRANCOUNT
tidak cocok dan akan menimbulkan kesalahan terkait dengan itu. Kode di bawah tidak hanya ini, dan juga hanya mengeluarkan ALTER
jika Collation bukan yang diinginkan, jika tidak maka lompatan ALTER
perintah.
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 ALTER
perintah 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 DATABASE
operasi. 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 IF
pernyataan 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 15
saklar 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_requests
untuk melihat semua pemblokiran yang indah terjadi ;-).
Antri acara di suatu tempat yang kemudian akan membaca dari antrian itu dan jalankan ALTER DATABASE
pernyataan yang sesuai . Ini akan memungkinkan untuk menyelesaikan CREATE DATABASE
pernyataan dan transaksinya, setelah ALTER DATABASE
pernyataan 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 DATABASE
pernyataan 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 DATABASE
dalam 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 -q
yang 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 master
dan msdb
sebelum 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 COLLATE
kata 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, name
bidang dalam sys.databases
akan menggunakan Kolasi default tingkat Instance. Tampilan katalog sistem lain juga terpengaruh, tetapi saya tidak memiliki daftar lengkap.
Meta-data tingkat basis data, seperti sys.objects
dan 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 _BIN
atau _BIN2
), maka resolusi nama objek level-Database akan menjadi biner (misalnya [TableA] <> [tableA]
) namun nama variabel akan memungkinkan untuk case-insensitivity (misalnya @VariableA = @variableA
).