Database
 sql >> Baza danych >  >> RDS >> Database

Schemat Switch-A-Roo:Część 2

W sierpniu napisałem post na temat mojej metodologii schema-swap dla T-SQL we wtorek. Podejście to zasadniczo pozwala na leniwe ładowanie kopii tabeli (powiedzmy pewnego rodzaju tabeli przeglądowej) w tle, aby zminimalizować ingerencję użytkowników:gdy tabela w tle jest aktualna, wszystko, co jest wymagane do dostarczenia zaktualizowanych danych dla użytkowników jest przerwą na tyle długą, aby wprowadzić zmianę metadanych.

W tym poście wspomniałem o dwóch zastrzeżeniach, których metodologia, której broniłem przez lata, obecnie nie spełnia:ograniczeń związanych z kluczami zagranicznymi i statystyki . Istnieje wiele innych funkcji, które również mogą zakłócać tę technikę. Jeden, który ostatnio pojawił się w rozmowie:wyzwalacze . Są też inne:kolumny tożsamości , podstawowe ograniczenia klucza , domyślne ograniczenia , sprawdź ograniczenia , ograniczenia odnoszące się do UDF , indeksy , wyświetlenia (w tym widoki indeksowane , które wymagają SCHEMABINDING ) i partycje . Nie zamierzam dzisiaj zajmować się wszystkimi tymi wszystkimi, ale pomyślałem, że przetestuję kilka, aby zobaczyć dokładnie, co się stanie.

Przyznam, że moje oryginalne rozwiązanie było w zasadzie migawką biednego człowieka, bez wszystkich kłopotów, całej bazy danych i wymagań licencyjnych związanych z rozwiązaniami, takimi jak replikacja, dublowanie i grupy dostępności. Były to kopie tabel tylko do odczytu z produkcji, które były „dublowane” przy użyciu T-SQL i techniki wymiany schematów. Nie potrzebowali więc żadnego z tych wymyślnych kluczy, ograniczeń, wyzwalaczy i innych funkcji. Widzę jednak, że technika ta może być użyteczna w większej liczbie scenariuszy i w takich sytuacjach niektóre z powyższych czynników mogą mieć znaczenie.

Skonfigurujmy więc prostą parę tabel, które mają kilka z tych właściwości, wykonaj zamianę schematów i zobaczmy, co się psuje. :-)

Najpierw schematy:

CREATE SCHEMA prep;
GO
CREATE SCHEMA live;
GO
CREATE SCHEMA holder;
GO

Teraz tabela w live schemat, w tym wyzwalacz i 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

Teraz powtarzamy to samo dla kopii tabeli w prep . Potrzebujemy również drugiej kopii wyzwalacza, ponieważ nie możemy utworzyć wyzwalacza w prep schemat, który odwołuje się do tabeli w live , lub odwrotnie. Celowo ustawimy tożsamość na wyższy ziarno i inną wartość domyślną dla int_column (aby pomóc nam lepiej śledzić, z którą kopią tabeli mamy do czynienia po wielu zamianach schematów):

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

Teraz wstawmy kilka wierszy do każdej tabeli i obserwujmy wynik:

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;

Wyniki:

id int_column kolumna_udf computed_column
1

1 20 2
2

1 20 2

Wyniki z live.t1

id int_column kolumna_udf computed_column
1000

2 20 3
1001

2 20 3

Wyniki z prep.t1

A w okienku wiadomości:

live.trig
live.trig
prep.trig
prep.trig

Przeprowadźmy teraz prostą zamianę schematu:

 -- 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;

A potem powtórz ćwiczenie:

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;

Wyniki w tabelach wydają się w porządku:

id int_column kolumna_udf computed_column
1

1 20 2
2

1 20 2
3

1 20 2
4

1 20 2

Wyniki z live.t1

id int_column kolumna_udf computed_column
1000

2 20 3
1001

2 20 3
1002

2 20 3
1003

2 20 3

Wyniki z prep.t1

Ale okienko komunikatów wyświetla dane wyjściowe wyzwalacza w niewłaściwej kolejności:

prep.trig
prep.trig
live.trig
live.trig

Zajmijmy się więc wszystkimi metadanymi. Oto zapytanie, które szybko sprawdzi wszystkie kolumny tożsamości, wyzwalacze, klucze podstawowe, ograniczenia domyślne i sprawdzające dla tych tabel, koncentrując się na schemacie skojarzonego obiektu, nazwie i definicji (oraz początkowej/ostatniej wartości dla kolumny tożsamości):

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');

Wyniki wskazują na dość bałagan metadanych:

typ schemat imię definicja
Sprawdź przygotuj ck_live ([int_column]>(0))
Sprawdź na żywo ck_prep ([int_column]>(1))
Domyślne przygotuj df_live1 ((1))
Domyślne przygotuj df_live2 ([dbo].[udf]())
Domyślne na żywo df_prep1 ((2))
Domyślne na żywo df_prep2 ([dbo].[udf]())
Wyzwalacz przygotuj trig_live CREATE TRIGGER live.trig_live ON live.t1 FOR INSERT AS BEGIN PRINT 'live.trig'; END
Wyzwalacz na żywo trig_prep CREATE TRIGGER prep.trig_prep ON prep.t1 FOR INSERT AS BEGIN PRINT 'prep.trig'; END
Tożsamość przygotuj nasiona =1 ostatnia_wartość =4
Tożsamość na żywo nasiona =1000 ostatnia_wartość =1003
Klucz główny przygotuj pk_live
Klucz główny na żywo pk_prep

Metadane kaczka-kaczka-gęś

Problemy z kolumnami tożsamości i ograniczeniami nie wydają się być dużym problemem. Mimo że obiekty *wydają się* wskazywać niewłaściwe obiekty zgodnie z widokami katalogu, funkcjonalność – przynajmniej dla podstawowych wstawek – działa tak, jak można by się spodziewać, gdybyś nigdy nie zaglądał do metadanych.

Duży problem dotyczy wyzwalacza – zapominając na chwilę, jak trywialny zrobiłem ten przykład, w prawdziwym świecie prawdopodobnie odwołuje się do tabeli bazowej poprzez schemat i nazwę. W takim przypadku, gdy jest przymocowany do niewłaściwego stołu, wszystko może pójść… cóż, źle. Wróćmy z powrotem:

BEGIN TRANSACTION;
  ALTER SCHEMA holder TRANSFER prep.t1;
  ALTER SCHEMA prep   TRANSFER live.t1;
  ALTER SCHEMA live   TRANSFER holder.t1;
COMMIT TRANSACTION;

(Możesz ponownie uruchomić zapytanie o metadane, aby przekonać się, że wszystko wróciło do normy.)

Teraz zmieńmy wyzwalacz *tylko* na live wersji, aby zrobić coś użytecznego (cóż, "użytecznego" w kontekście tego eksperymentu):

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

Teraz wstawmy wiersz:

INSERT live.t1 DEFAULT VALUES;

Wyniki:

id    msg
----  ----------
5     live.trig

Następnie ponownie wykonaj zamianę:

BEGIN TRANSACTION;
  ALTER SCHEMA holder TRANSFER prep.t1;
  ALTER SCHEMA prep   TRANSFER live.t1;
  ALTER SCHEMA live   TRANSFER holder.t1;
COMMIT TRANSACTION;

I wstaw kolejny wiersz:

INSERT live.t1 DEFAULT VALUES;

Wyniki (w okienku wiadomości):

prep.trig

O o. Jeśli wykonujemy tę zamianę schematu raz na godzinę, to przez 12 godzin każdego dnia wyzwalacz nie robi tego, czego oczekujemy, ponieważ jest powiązany z niewłaściwą kopią tabeli! Teraz zmieńmy wersję „prep” wyzwalacza:

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

Wynik:

Msg 208, Poziom 16, Stan 6, Procedura trig_prep, Wiersz 1
Nieprawidłowa nazwa obiektu „prep.trig_prep”.

Cóż, to zdecydowanie nie jest dobre. Ponieważ jesteśmy w fazie wymiany metadanych, nie ma takiego obiektu; wyzwalacze to teraz live.trig_prep i prep.trig_live . Zdezorientowany? Ja też. Spróbujmy więc tego:

EXEC sp_helptext 'live.trig_prep';

Wyniki:

CREATE TRIGGER prep.trig_prep
ON prep.t1
FOR INSERT
AS
BEGIN
  PRINT 'prep.trig';
END

Cóż, czy to nie zabawne? Jak zmienić ten wyzwalacz, gdy jego metadane nie są nawet odpowiednio odzwierciedlone w jego własnej definicji? Spróbujmy tego:

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

Wyniki:

Msg 2103, Poziom 15, Stan 1, Procedura trig_prep, Wiersz 1
Nie można zmienić wyzwalacza „live.trig_prep”, ponieważ jego schemat różni się od schematu tabeli docelowej lub widoku.

To też oczywiście nie jest dobre. Wygląda na to, że tak naprawdę nie ma dobrego sposobu rozwiązania tego scenariusza, który nie wymaga zamiany obiektów z powrotem na ich oryginalne schematy. Mógłbym zmienić ten wyzwalacz, aby był przeciwko 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

Ale teraz mam dwa wyzwalacze, które mówią w swoim tekście, że działają przeciwko live.t1 , ale tylko ten faktycznie jest wykonywany. Tak, moja głowa się kręci (tak jak Michael J. Swart (@MJSwart) w tym poście na blogu). I zauważ, że aby posprzątać ten bałagan, po ponownej zamianie schematów mogę usunąć wyzwalacze z ich oryginalnymi nazwami:

DROP TRIGGER live.trig_live;
DROP TRIGGER prep.trig_prep;

Jeśli spróbuję DROP TRIGGER live.trig_prep; , na przykład otrzymuję błąd nie znaleziono obiektu.

Rezolucje?

Obejściem problemu z wyzwalaczem jest dynamiczne generowanie CREATE TRIGGER kod i upuść i ponownie utwórz wyzwalacz w ramach wymiany. Najpierw ustawmy wyzwalacz z powrotem na *bieżącej* tabeli w live (możesz zdecydować w swoim scenariuszu, czy potrzebujesz wyzwalacza w prep wersja tabeli w ogóle):

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

Teraz krótki przykład tego, jak działa nasza nowa zamiana schematów (w przypadku wielu wyzwalaczy może być konieczne dostosowanie tego, aby poradzić sobie z każdym wyzwalaczem, i powtórzyć to dla schematu w prep wersji, jeśli tam też musisz zachować wyzwalacz. Zwróć szczególną uwagę, aby poniższy kod, dla zwięzłości, zakładał, że istnieje tylko *jeden* wyzwalacz na 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;

Innym (mniej pożądanym) obejściem byłoby dwukrotne wykonanie całej operacji zamiany schematu, łącznie z operacjami wykonywanymi na prep wersja tabeli. Co w dużej mierze niweczy cel zamiany schematów:skrócenie czasu, w którym użytkownicy nie mogą uzyskać dostępu do tabel i dostarczanie im zaktualizowanych danych przy minimalnej przerwie.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak wyeliminować zduplikowane wiersze w SQL?

  2. Naucz się podstawowej analizy danych za pomocą funkcji okna SQL

  3. Czy popełniasz te błędy podczas korzystania z SQL CURSOR?

  4. Stosowanie reguł terenowych za pomocą klasyfikacji

  5. SQL ORDER BY:5 nakazów i zakazów, aby sortować dane jak profesjonalista