Kích hoạt để thay đổi đối chiếu cơ sở dữ liệu khi tạo


9

Tôi đang cố gắng tạo Trình kích hoạt, để thay đổi đối chiếu cơ sở dữ liệu khi tạo, nhưng làm cách nào tôi có thể bắt được tên cơ sở dữ liệu để sử dụng bên trong trình kích hoạt?

USE master
GO
CREATE TRIGGER trg_DDL_ChangeCOllationDatabase
ON ALL SERVER
FOR CREATE_DATABASE
AS
declare @databasename varchar(200)
set @databasename =db_name()
    ALTER DATABASE @databasename COLLATE xxxxxxxxxxxxxxxxxxx
GO

Rõ ràng, điều này không hoạt động.


1
Có lý do gì bạn không thể thay đổi cơ sở dữ liệu MODEL thành đối chiếu yêu cầu của bạn không? - tất cả các cơ sở dữ liệu mới được tạo sẽ sử dụng MODEL làm mẫu
Scott Hodgin

Tôi đã thử điều đó nhưng nó nói rằng cơ sở dữ liệu mô hình là một cơ sở dữ liệu hệ thống, vì vậy tôi không thể thay đổi nó.
Racer SQL

Vì vậy, cơ sở dữ liệu hệ thống của bạn sẽ ở một đối chiếu khác với db người dùng của bạn? Bạn đã xem xét các vấn đề đối chiếu tiềm năng với các bảng tạm thời, vv?
George.Palacios

Wow, Yeah, tôi đã đọc nó như 5 phút trước. Không nghĩ về điều đó. Đó không phải là một ý tưởng tốt.
Racer SQL

Câu trả lời:


8

Nói chung, bạn không thể phát hành ALTER DATABASEtrong 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:

  1. 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 DATABASEtuyê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 @@TRANCOUNTkhô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 ALTERnếu Collation không phải là mã mong muốn, nếu không nó sẽ bỏ qua ALTERlệ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];
  2. 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 ALTERlệ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 DATABASEhoạ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 IFcâ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 15tắ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_requestsxem tất cả các chặn đáng yêu đang diễn ra ;-).

  3. Xếp hàng sự kiện ở đâu đó sau đó sẽ đọc từ hàng đợi đó và thực hiện ALTER DATABASEcâu lệnh thích hợp . Điều này sẽ cho phép CREATE DATABASEcâu lệnh hoàn thành và giao dịch của nó được thực hiện, sau đó một ALTER DATABASEcâ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 DATABASEcâ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 DATABASEtrong 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:

  1. 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ó.
  2. 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).
  3. 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 -qcậ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 mastermsdbtrướ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:

  1. Đố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 COLLATEtừ 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.

  2. Dữ liệu meta cấp độ sơ thẩm. Ví dụ: nametrường trong sys.databasessẽ 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.objectssys.indexes, không bị ảnh hưởng.

  3. Tên giải quyết cho:
    1. biến cục bộ (tức là @variable)
    2. con trỏ
    3. 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 _BINhoặ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).


11

Bạn sẽ cần sử dụng SQL động và hàm EVENTDATA () .

USE master
GO
CREATE TRIGGER trg_DDL_ChangeCOllationDatabase
ON ALL SERVER
FOR CREATE_DATABASE
AS
SET NOCOUNT ON; 
DECLARE @databasename NVARCHAR(256) = N''
DECLARE @event_data XML; 
DECLARE @sql NVARCHAR(4000) = N''

SET @event_data = EVENTDATA()

SET @databasename = @event_data.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'NVARCHAR(256)') 

SET @sql += 'ALTER DATABASE ' + QUOTENAME(@databasename) + ' COLLATE al''z a-b-cee''z'

PRINT @sql

EXEC sys.sp_executesql @sql

GO

Chỉ cần phụ trong đối chiếu của bạn cho người giả mạo của tôi .

Bây giờ khi tôi tạo một cơ sở dữ liệu ...

CREATE DATABASE DingDong

Tôi nhận được thông báo này (từ bản in):

THAY ĐỔI CƠ SỞ [ĐinhDong] THU THẬP al'z ab-cee'z

Chỉ cần lưu ý rằng nếu các cơ sở dữ liệu khác (bao gồm tempdb) sử dụng các đối chiếu khác nhau, bạn có thể gặp vấn đề khi so sánh dữ liệu chuỗi. Bạn sẽ phải thêm các mệnh đề THU để so sánh chuỗi trong trường hợp vỏ hoặc dấu trọng yếu và ngay cả khi chúng không bạn có thể gặp lỗi. Câu hỏi liên quan nơi tôi gặp phải một vấn đề mã tương tự ở đây .


1
@RafaelPiccinelli và Erik: chỉ FYI, câu trả lời này không hoàn toàn chính xác. Mã không hoạt động, nhưng lỗi thực sự được che dấu do thử nghiệm sử dụng tên Collation không hợp lệ. Tôi đã cập nhật câu trả lời của mình để giải thích (về phía trên) vì nó quá nhiều cho một nhận xét.
Solomon Rutzky

2

Bạn không thể ALTER DATABASEkích hoạt. Bạn sẽ cần sáng tạo với đánh giá và sửa lỗi. Cái gì đó như:

EXEC sp_MSforeachdb N'IF EXISTS 
(
     select top 1 name from sys.databases where collation_name != 
     SQL_Latin1_General_CP1_CI_AS
)
BEGIN
    -- do something
END';

Mặc dù bạn không nên sử dụng sp_MSforeachdb .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.