Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Lược đồ Switch-A-Roo:Phần 2

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 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.trig
live.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.trig
prep.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 1
Tê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_prepprep.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 1
Khô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.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Kết nối PowerShell với Salesforce.com

  2. Ước tính mật độ:Kết hợp thống kê mật độ

  3. Hình dung điểm tới hạn với Plan Explorer

  4. Thông tin liên hệ đang phát triển có nghĩa là thay đổi cơ sở dữ liệu của bạn không?

  5. Cách nhân hai cột trong SQL