Vào tháng 8, tôi đã viết một bài về phương pháp hoán đổi lược đồ cho T-SQL vào Thứ Ba. Về cơ bản, phương pháp này cho phép bạn tải một cách lười biếng một bản sao của bảng (giả sử một loại bảng tra cứu nào đó) trong nền để giảm thiểu sự can thiệp vào người dùng:khi bảng nền được cập nhật, tất cả những gì cần thiết để cung cấp dữ liệu được cập nhật đối với người dùng là khoảng thời gian gián đoạn đủ lâu để thực hiện thay đổi siêu dữ liệu.
Trong bài đăng đó, tôi đã đề cập đến hai lưu ý rằng phương pháp mà tôi đã ủng hộ trong nhiều năm hiện không phục vụ cho: các ràng buộc khóa ngoại và thống kê . Có một loạt các tính năng khác cũng có thể ảnh hưởng đến kỹ thuật này. Một điều đã xuất hiện trong cuộc trò chuyện gần đây: trình kích hoạt . Và còn những cái khác: cột nhận dạng , ràng buộc khóa chính , ràng buộc mặc định , kiểm tra các ràng buộc , các ràng buộc tham chiếu đến các UDF , chỉ mục , lượt xem (bao gồm cả các chế độ xem được lập chỉ mục , yêu cầu SCHEMABINDING
) và phân vùng . Tôi sẽ không giải quyết tất cả những điều này ngày hôm nay, nhưng tôi nghĩ tôi sẽ kiểm tra một vài điều để xem chính xác điều gì sẽ xảy ra.
Tôi sẽ thú nhận rằng giải pháp ban đầu của tôi về cơ bản là một bản chụp nhanh của một người nghèo, không có tất cả phức tạp, toàn bộ cơ sở dữ liệu và các yêu cầu cấp phép của các giải pháp như sao chép, phản chiếu và Nhóm khả dụng. Đây là các bản sao chỉ đọc của các bảng từ quá trình sản xuất được "nhân bản" bằng T-SQL và kỹ thuật hoán đổi lược đồ. Vì vậy, họ không cần bất kỳ phím, ràng buộc, trình kích hoạt và các tính năng khác ưa thích này. Nhưng tôi thấy rằng kỹ thuật này có thể hữu ích trong nhiều trường hợp hơn và trong những trường hợp đó, một số yếu tố trên có thể phát huy tác dụng.
Vì vậy, chúng ta hãy thiết lập một cặp bảng đơn giản có một số thuộc tính này, thực hiện hoán đổi giản đồ và xem những gì bị ngắt. :-)
Đầu tiên, các lược đồ:
CREATE SCHEMA prep; GO CREATE SCHEMA live; GO CREATE SCHEMA holder; GO
Bây giờ, bảng trong live
lược đồ, bao gồm trình kích hoạt và UDF:
CREATE FUNCTION dbo.udf() RETURNS INT AS BEGIN RETURN (SELECT 20); END GO CREATE TABLE live.t1 ( id INT IDENTITY(1,1), int_column INT NOT NULL DEFAULT 1, udf_column INT NOT NULL DEFAULT dbo.udf(), computed_column AS CONVERT(INT, int_column + 1), CONSTRAINT pk_live PRIMARY KEY(id), CONSTRAINT ck_live CHECK (int_column > 0) ); GO CREATE TRIGGER live.trig_live ON live.t1 FOR INSERT AS BEGIN PRINT 'live.trig'; END GO
Bây giờ, chúng ta lặp lại điều tương tự cho bản sao của bảng trong prep
. Chúng tôi cũng cần bản sao thứ hai của trình kích hoạt, vì chúng tôi không thể tạo trình kích hoạt trong prep
lược đồ tham chiếu đến một bảng trong live
, hoặc ngược lại. Chúng tôi sẽ cố ý đặt danh tính thành hạt giống cao hơn và giá trị mặc định khác cho int_column
(để giúp chúng tôi theo dõi tốt hơn bản sao của bảng mà chúng tôi đang thực sự xử lý sau nhiều lần hoán đổi giản đồ):
CREATE TABLE prep.t1 ( id INT IDENTITY(1000,1), int_column INT NOT NULL DEFAULT 2, udf_column INT NOT NULL DEFAULT dbo.udf(), computed_column AS CONVERT(INT, int_column + 1), CONSTRAINT pk_prep PRIMARY KEY(id), CONSTRAINT ck_prep CHECK (int_column > 1) ); GO CREATE TRIGGER prep.trig_prep ON prep.t1 FOR INSERT AS BEGIN PRINT 'prep.trig'; END GO
Bây giờ, hãy chèn một vài hàng vào mỗi bảng và quan sát kết quả:
SET NOCOUNT ON; INSERT live.t1 DEFAULT VALUES; INSERT live.t1 DEFAULT VALUES; INSERT prep.t1 DEFAULT VALUES; INSERT prep.t1 DEFAULT VALUES; SELECT * FROM live.t1; SELECT * FROM prep.t1;
Kết quả:
id | int_column | udf_column | computed_column |
---|---|---|---|
1 | 1 | 20 | 2 |
2 | 1 | 20 | 2 |
Kết quả từ live.t1
id | int_column | udf_column | computed_column |
---|---|---|---|
1000 | 2 | 20 | 3 |
1001 | 2 | 20 | 3 |
Kết quả từ prep.t1
Và trong ngăn thư:
live.triglive.trig
prep.trig
prep.trig
Bây giờ, hãy thực hiện một hoán đổi giản đồ đơn giản:
-- assume that you do background loading of prep.t1 here BEGIN TRANSACTION; ALTER SCHEMA holder TRANSFER prep.t1; ALTER SCHEMA prep TRANSFER live.t1; ALTER SCHEMA live TRANSFER holder.t1; COMMIT TRANSACTION;
Và sau đó lặp lại bài tập:
SET NOCOUNT ON; INSERT live.t1 DEFAULT VALUES; INSERT live.t1 DEFAULT VALUES; INSERT prep.t1 DEFAULT VALUES; INSERT prep.t1 DEFAULT VALUES; SELECT * FROM live.t1; SELECT * FROM prep.t1;
Kết quả trong bảng có vẻ ổn:
id | int_column | udf_column | computed_column |
---|---|---|---|
1 | 1 | 20 | 2 |
2 | 1 | 20 | 2 |
3 | 1 | 20 | 2 |
4 | 1 | 20 | 2 |
Kết quả từ live.t1
id | int_column | udf_column | computed_column |
---|---|---|---|
1000 | 2 | 20 | 3 |
1001 | 2 | 20 | 3 |
1002 | 2 | 20 | 3 |
1003 | 2 | 20 | 3 |
Kết quả từ prep.t1
Nhưng ngăn thông báo liệt kê đầu ra trình kích hoạt không đúng thứ tự:
prep.trigprep.trig
live.trig
live.trig
Vì vậy, chúng ta hãy tìm hiểu tất cả siêu dữ liệu. Đây là một truy vấn sẽ nhanh chóng kiểm tra tất cả các cột nhận dạng, trình kích hoạt, khóa chính, các ràng buộc mặc định và kiểm tra cho các bảng này, tập trung vào lược đồ của đối tượng được liên kết, tên và định nghĩa (và giá trị gốc / cuối cùng cho cột nhận dạng):
SELECT [type] = 'Check', [schema] = OBJECT_SCHEMA_NAME(parent_object_id), name, [definition] FROM sys.check_constraints WHERE OBJECT_SCHEMA_NAME(parent_object_id) IN (N'live',N'prep') UNION ALL SELECT [type] = 'Default', [schema] = OBJECT_SCHEMA_NAME(parent_object_id), name, [definition] FROM sys.default_constraints WHERE OBJECT_SCHEMA_NAME(parent_object_id) IN (N'live',N'prep') UNION ALL SELECT [type] = 'Trigger', [schema] = OBJECT_SCHEMA_NAME(parent_id), name, [definition] = OBJECT_DEFINITION([object_id]) FROM sys.triggers WHERE OBJECT_SCHEMA_NAME(parent_id) IN (N'live',N'prep') UNION ALL SELECT [type] = 'Identity', [schema] = OBJECT_SCHEMA_NAME([object_id]), name = 'seed = ' + CONVERT(VARCHAR(12), seed_value), [definition] = 'last_value = ' + CONVERT(VARCHAR(12), last_value) FROM sys.identity_columns WHERE OBJECT_SCHEMA_NAME([object_id]) IN (N'live',N'prep') UNION ALL SELECT [type] = 'Primary Key', [schema] = OBJECT_SCHEMA_NAME([parent_object_id]), name, [definition] = '' FROM sys.key_constraints WHERE OBJECT_SCHEMA_NAME([object_id]) IN (N'live',N'prep');
Kết quả chỉ ra một mớ hỗn độn siêu dữ liệu:
loại | giản đồ | tên | định nghĩa |
---|---|---|---|
Kiểm tra | chuẩn bị | ck_live | ([int_column]> (0)) |
Kiểm tra | trực tiếp | ck_prep | ([int_column]> (1)) |
Mặc định | chuẩn bị | df_live1 | ((1)) |
Mặc định | chuẩn bị | df_live2 | ([dbo]. [udf] ()) |
Mặc định | trực tiếp | df_prep1 | ((2)) |
Mặc định | trực tiếp | df_prep2 | ([dbo]. [udf] ()) |
Trình kích hoạt | chuẩn bị | trig_live | CREATE TRIGGER live.trig_live ON live.t1 FOR INSERT AS BEGIN PRINT 'live.trig'; END |
Trình kích hoạt | trực tiếp | trig_prep | CREATE TRIGGER prep.trig_prep ON prep.t1 FOR INSERT AS BEGIN PRINT 'prep.trig'; END |
Danh tính | chuẩn bị | seed =1 | last_value =4 |
Danh tính | trực tiếp | hạt giống =1000 | last_value =1003 |
Khóa chính | chuẩn bị | pk_live | |
Khóa chính | trực tiếp | pk_prep |
Siêu dữ liệu vịt-vịt-ngỗng
Các vấn đề với các cột nhận dạng và các ràng buộc dường như không phải là một vấn đề lớn. Mặc dù các đối tượng * dường như * trỏ đến các đối tượng sai theo chế độ xem danh mục, chức năng - ít nhất là đối với các phần chèn cơ bản - hoạt động như bạn có thể mong đợi nếu bạn chưa bao giờ xem siêu dữ liệu.
Vấn đề lớn là với trình kích hoạt - quên mất một lúc tôi đã làm ví dụ này tầm thường như thế nào, trong thế giới thực, nó có thể tham chiếu đến bảng cơ sở theo lược đồ và tên. Trong trường hợp đó, khi nó được gắn vào bảng sai, mọi thứ có thể diễn ra… tốt, sai. Hãy quay lại:
BEGIN TRANSACTION; ALTER SCHEMA holder TRANSFER prep.t1; ALTER SCHEMA prep TRANSFER live.t1; ALTER SCHEMA live TRANSFER holder.t1; COMMIT TRANSACTION;
(Bạn có thể chạy lại truy vấn siêu dữ liệu để thuyết phục bản thân rằng mọi thứ đã trở lại bình thường.)
Bây giờ hãy thay đổi trình kích hoạt * chỉ * trên live
phiên bản để thực sự làm điều gì đó hữu ích (tốt, "hữu ích" trong ngữ cảnh của thử nghiệm này):
ALTER TRIGGER live.trig_live ON live.t1 FOR INSERT AS BEGIN SELECT i.id, msg = 'live.trig' FROM inserted AS i INNER JOIN live.t1 AS t ON i.id = t.id; END GO
Bây giờ hãy chèn một hàng:
INSERT live.t1 DEFAULT VALUES;
Kết quả:
id msg ---- ---------- 5 live.trig
Sau đó thực hiện lại hoán đổi:
BEGIN TRANSACTION; ALTER SCHEMA holder TRANSFER prep.t1; ALTER SCHEMA prep TRANSFER live.t1; ALTER SCHEMA live TRANSFER holder.t1; COMMIT TRANSACTION;
Và chèn một hàng khác:
INSERT live.t1 DEFAULT VALUES;
Kết quả (trong ngăn thư):
prep.trig
Ồ, ồ. Nếu chúng ta thực hiện hoán đổi giản đồ này mỗi giờ một lần, thì trong 12 giờ mỗi ngày, trình kích hoạt sẽ không làm những gì chúng ta mong đợi, vì nó được liên kết với bản sao sai của bảng! Bây giờ, hãy thay đổi phiên bản "chuẩn bị" của trình kích hoạt:
ALTER TRIGGER prep.trig_prep ON prep.t1 FOR INSERT AS BEGIN SELECT i.id, msg = 'prep.trig' FROM inserted AS i INNER JOIN prep.t1 AS t ON i.id = t.id; END GO
Kết quả:
Bản tin thứ 208, Mức 16, Trạng thái 6, Thủ tục trig_prep, Dòng 1Tên đối tượng không hợp lệ 'prep.trig_prep'.
Chà, điều đó chắc chắn không tốt. Vì chúng ta đang ở giai đoạn hoán đổi siêu dữ liệu, nên không có đối tượng nào như vậy; các trình kích hoạt bây giờ là live.trig_prep
và prep.trig_live
. Bạn bối rối chưa? Tôi cũng vậy. Vì vậy, hãy thử điều này:
EXEC sp_helptext 'live.trig_prep';
Kết quả:
CREATE TRIGGER prep.trig_prep ON prep.t1 FOR INSERT AS BEGIN PRINT 'prep.trig'; END
Chà, điều đó có buồn cười không? Làm cách nào để thay đổi trình kích hoạt này khi siêu dữ liệu của nó thậm chí không được phản ánh đúng theo định nghĩa của chính nó? Hãy thử điều này:
ALTER TRIGGER live.trig_prep ON prep.t1 FOR INSERT AS BEGIN SELECT i.id, msg = 'prep.trig' FROM inserted AS i INNER JOIN prep.t1 AS t ON i.id = t.id; END GO
Kết quả:
Msg 2103, Mức 15, Trạng thái 1, Thủ tục trig_prep, Dòng 1Không thể thay đổi trình kích hoạt 'live.trig_prep' vì giản đồ của nó khác với lược đồ của bảng hoặc chế độ xem đích.
Điều này cũng không tốt, rõ ràng. Có vẻ như không có cách nào thực sự tốt để giải quyết tình huống này mà không liên quan đến việc hoán đổi các đối tượng trở lại lược đồ ban đầu của chúng. Tôi có thể thay đổi trình kích hoạt này để chống lại live.t1
:
ALTER TRIGGER live.trig_prep ON live.t1 FOR INSERT AS BEGIN SELECT i.id, msg = 'live.trig' FROM inserted AS i INNER JOIN live.t1 AS t ON i.id = t.id; END GO
Nhưng bây giờ tôi có hai trình kích hoạt nói rằng, trong văn bản nội dung của chúng, chúng hoạt động chống lại live.t1
, nhưng chỉ cái này thực sự thực thi. Vâng, đầu tôi đang quay cuồng (và của Michael J. Swart (@MJSwart) trong bài đăng trên blog này). Và lưu ý rằng, để làm sạch mớ hỗn độn này, sau khi hoán đổi lại các lược đồ, tôi có thể loại bỏ các trình kích hoạt với tên ban đầu của chúng:
DROP TRIGGER live.trig_live; DROP TRIGGER prep.trig_prep;
Nếu tôi thử DROP TRIGGER live.trig_prep;
, ví dụ:tôi gặp lỗi không tìm thấy đối tượng.
Giải pháp?
Một giải pháp cho vấn đề về trình kích hoạt là tạo động CREATE TRIGGER
mã, thả và tạo lại trình kích hoạt, như một phần của hoán đổi. Đầu tiên, hãy đặt lại trình kích hoạt trên bảng * current * trong live
(bạn có thể quyết định trong trường hợp của mình nếu bạn thậm chí cần một trình kích hoạt trên prep
phiên bản của bảng):
CREATE TRIGGER live.trig_live ON live.t1 FOR INSERT AS BEGIN SELECT i.id, msg = 'live.trig' FROM inserted AS i INNER JOIN live.t1 AS t ON i.id = t.id; END GO
Bây giờ, một ví dụ nhanh về cách hoán đổi giản đồ mới của chúng tôi sẽ hoạt động (và bạn có thể phải điều chỉnh điều này để đối phó với từng trình kích hoạt, nếu bạn có nhiều trình kích hoạt và lặp lại nó cho lược đồ trên prep
phiên bản, nếu bạn cũng cần duy trì một trình kích hoạt ở đó. Đặc biệt lưu ý rằng đoạn mã dưới đây, cho ngắn gọn, giả định rằng chỉ có * một * trình kích hoạt trên live.t1
.
BEGIN TRANSACTION; DECLARE @sql1 NVARCHAR(MAX), @sql2 NVARCHAR(MAX); SELECT @sql1 = N'DROP TRIGGER live.' + QUOTENAME(name) + ';', @sql2 = OBJECT_DEFINITION([object_id]) FROM sys.triggers WHERE [parent_id] = OBJECT_ID(N'live.t1'); EXEC sp_executesql @sql1; -- drop the trigger before the transfer ALTER SCHEMA holder TRANSFER prep.t1; ALTER SCHEMA prep TRANSFER live.t1; ALTER SCHEMA live TRANSFER holder.t1; EXEC sp_executesql @sql2; -- re-create it after the transfer COMMIT TRANSACTION;
Một cách giải quyết khác (ít mong muốn hơn) sẽ là thực hiện toàn bộ hoạt động hoán đổi giản đồ hai lần, bao gồm bất kỳ hoạt động nào xảy ra với prep
phiên bản của bảng. Điều này phần lớn đánh bại mục đích của hoán đổi giản đồ ngay từ đầu:giảm thời gian người dùng không thể truy cập (các) bảng và mang lại cho họ dữ liệu cập nhật mà ít bị gián đoạn.