Nói chung, bạn không thể phát hành ALTER DATABASE
trong Trình kích hoạt (hoặc bất kỳ Giao dịch nào có các tuyên bố khác trong đó). Nếu bạn cố gắng, bạn sẽ nhận được lỗi sau:
Msg 226, Cấp 16, Trạng thái 6, Dòng xxxx Lệnh
ALTER DATABASE không được phép trong giao dịch đa câu lệnh.
Lý do mà lỗi này không gặp phải trong câu trả lời của @ sp_BlitzErik là kết quả của trường hợp kiểm tra cụ thể được cung cấp: lỗi hiển thị ở trên là lỗi thời gian chạy, trong khi lỗi gặp phải trong câu trả lời của anh ta là lỗi thời gian biên dịch. Lỗi thời gian biên dịch đó ngăn cản việc thực thi lệnh và do đó không có "thời gian chạy". Chúng ta có thể thấy sự khác biệt bằng cách chạy như sau:
SET NOEXEC ON;
SELECT N'g' COLLATE Latin1;
SET NOEXEC OFF;
Lô trên sẽ lỗi, trong khi các lô sau sẽ không:
SET NOEXEC ON;
BEGIN TRAN
CREATE TABLE #t (Col1 INT);
ALTER DATABASE CURRENT COLLATE Latin1_General_100_BIN2;
ROLLBACK TRAN;
SET NOEXEC OFF;
Điều này cho bạn hai tùy chọn:
Cam kết Giao dịch trong Trình kích hoạt DDL sao cho không có tuyên bố nào khác trong Giao dịch. Đây không phải là một ý tưởng tốt nếu có nhiều Triggers DDL có thể bị sa thải bởi một CREATE DATABASE
tuyên bố, và có thể là một ý tưởng tồi nói chung, nhưng nó hoạt động ;-). Mẹo nhỏ là bạn cũng cần bắt đầu Giao dịch mới trong Trình kích hoạt khác Máy chủ SQL sẽ nhận thấy rằng các giá trị bắt đầu và kết thúc @@TRANCOUNT
không khớp và sẽ gây ra lỗi liên quan đến điều đó. Đoạn mã dưới đây thực hiện điều này và cũng chỉ phát hành ALTER
nếu Collation không phải là mã mong muốn, nếu không nó sẽ bỏ qua ALTER
lệnh.
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
Kiểm tra với:
-- 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];
Sử dụng SQLCLR để thiết lập một thông thường / bên ngoài SqlConnection
, Enlist = false;
trong Chuỗi kết nối, để ban hành ALTER
lệnh vì đó sẽ không phải là một phần của Giao dịch.
Dường như SQLCLR không thực sự là một tùy chọn, mặc dù không phải do bất kỳ giới hạn cụ thể nào của SQLCLR. Bằng cách nào đó, việc gõ " vì đó sẽ không phải là một phần của Giao dịch " ngay bên trên không đủ làm nổi bật thực tế là trên thực tế, có một Giao dịch hoạt động xung quanh CREATE DATABASE
hoạt động. Vấn đề ở đây là mặc dù SQLCLR có thể được sử dụng để bước ra ngoài Giao dịch hiện tại, nhưng vẫn không có cách nào để Phiên khác sửa đổi Cơ sở dữ liệu hiện đang được tạo cho đến khi Giao dịch ban đầu được thực hiện.
Có nghĩa là, Phiên A tạo Giao dịch để tạo Cơ sở dữ liệu và kích hoạt Trình kích hoạt. Trình kích hoạt, sử dụng SQLCLR, sẽ tạo Phiên B để sửa đổi Cơ sở dữ liệu đã được tạo, nhưng Giao dịch chưa được cam kết vì nó bị giữ cho đến khi Phiên B hoàn thành, điều đó không thể vì nó đang chờ Giao dịch ban đầu đó hoàn thành. Đây là một bế tắc, nhưng SQL Server không thể phát hiện ra như vậy vì không biết rằng Phiên B được tạo bởi một cái gì đó trong Phần A. Hành vi này có thể được nhìn thấy bằng cách thay thế phần đầu tiên của IF
câu lệnh trong ví dụ ở trên trong # 1 với những điều sau đây:
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
...
Công -t 15
tắc cho SQLCMD đặt thời gian chờ lệnh / truy vấn để kiểm tra không chờ đợi mãi với thời gian chờ mặc định. Tuy nhiên, bạn có thể đặt nó dài hơn 15 giây và trong một phiên khác kiểm tra sys.dm_exec_requests
xem tất cả các chặn đáng yêu đang diễn ra ;-).
Xếp hàng sự kiện ở đâu đó sau đó sẽ đọc từ hàng đợi đó và thực hiện ALTER DATABASE
câu lệnh thích hợp . Điều này sẽ cho phép CREATE DATABASE
câu lệnh hoàn thành và giao dịch của nó được thực hiện, sau đó một ALTER DATABASE
câu lệnh có thể được thực thi. Dịch vụ môi giới có thể được sử dụng ở đây. HOẶC, tạo một bảng, có Trình kích hoạt chèn vào bảng đó, sau đó có một công việc Tác nhân Máy chủ SQL gọi một Quy trình được lưu trữ đọc từ bảng đó và thực hiện ALTER DATABASE
câu lệnh và sau đó xóa bản ghi khỏi Bảng xếp hàng.
TUY NHIÊN, các tùy chọn trên chủ yếu được cung cấp để hỗ trợ trong các tình huống trong đó ai đó thực sự cần phải thực hiện một số loại ALTER DATABASE
trong Trình kích hoạt DDL. Trong trường hợp cụ thể này, nếu bạn thực sự không muốn bất kỳ cơ sở dữ liệu nào được sử dụng Collation mặc định ở cấp độ hệ thống / Instance, thì có lẽ bạn sẽ được phục vụ tốt nhất bởi:
- Tạo một cá thể mới với Collation mong muốn và chuyển tất cả Cơ sở dữ liệu người dùng của bạn sang nó.
- Hoặc, nếu đó chỉ là Cơ sở dữ liệu hệ thống của Collation không lý tưởng, có thể an toàn để thay đổi Collation hệ thống từ dòng lệnh thông qua setup.exe (ví dụ:
Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=...
tùy chọn này tạo lại DB hệ thống, vì vậy bạn sẽ cần để kịch bản ra các đối tượng ở cấp độ máy chủ, v.v. để tạo lại sau này, cộng với các bản vá áp dụng lại, v.v., FUN, FUN, FUN).
Hoặc, đối với người thích phiêu lưu, có tùy chọn không có giấy tờ (nghĩa là không được hỗ trợ, sử dụng theo cách riêng của bạn có nguy cơ nhưng có thể rất tốt) sqlservr.exe -q
cập nhật TẤT CẢ các cột DB và TẤT CẢ (vui lòng xem Thay đổi Đối chiếu của Trường hợp, Cơ sở dữ liệu và Tất cả các Cột trong Tất cả Cơ sở dữ liệu Người dùng: Điều gì có thể xảy ra sai? Để mô tả chi tiết về hành vi của tùy chọn này, cũng như phạm vi tác động tiềm năng).
Bất kể lựa chọn nào được chọn: luôn đảm bảo có bản sao lưu master
và msdb
trước khi thử những thứ đó.
Lý do đáng để nỗ lực thay đổi Collation mặc định ở cấp Máy chủ là vì Collation mặc định của Instance (tức là cấp Máy chủ) kiểm soát một số khu vực chức năng có thể dẫn đến hành vi bất ngờ / không nhất quán là mọi người đều mong đợi các hoạt động chuỗi hoạt động dọc theo dòng Đối chiếu mặc định cho tất cả Cơ sở dữ liệu người dùng của bạn:
Đối chiếu mặc định cho các cột chuỗi trong các bảng tạm thời. Đây chỉ là vấn đề khi so sánh với / Liên kết với các cột chuỗi khác NẾU có sự không khớp giữa hai cột chuỗi. Vấn đề ở đây là khi không chỉ định Collation một cách rõ ràng thông qua COLLATE
từ khóa, nhiều khả năng (mặc dù không được bảo đảm) sẽ gặp vấn đề.
Đây không phải là vấn đề đối với kiểu dữ liệu XML, biến bảng hoặc Cơ sở dữ liệu có chứa.
Dữ liệu meta cấp độ sơ thẩm. Ví dụ: name
trường trong sys.databases
sẽ sử dụng Đối chiếu mặc định ở cấp độ Instance. Các khung nhìn danh mục hệ thống khác cũng bị ảnh hưởng, nhưng tôi không có danh sách đầy đủ.
Dữ liệu meta cấp cơ sở dữ liệu, chẳng hạn như sys.objects
và sys.indexes
, không bị ảnh hưởng.
- Tên giải quyết cho:
- biến cục bộ (tức là
@variable
)
- con trỏ
GOTO
nhãn
Ví dụ: nếu Collation ở cấp độ Instance không phân biệt chữ hoa chữ thường trong khi Collation ở mức cơ sở dữ liệu là nhị phân (nghĩa là kết thúc bằng _BIN
hoặc _BIN2
), thì độ phân giải tên đối tượng ở mức cơ sở dữ liệu sẽ là nhị phân (ví dụ [TableA] <> [tableA]
) các tên biến sẽ cho phép không phân biệt chữ hoa chữ thường (ví dụ @VariableA = @variableA
).