sql >> Databáze >  >> RDS >> Database

Schéma Switch-A-Roo:Část 2

V srpnu jsem napsal příspěvek o své metodice swapování schémat pro T-SQL Tuesday. Tento přístup v podstatě umožňuje líné načítání kopie tabulky (řekněme nějaké vyhledávací tabulky) na pozadí, aby se minimalizovaly interference s uživateli:jakmile je tabulka na pozadí aktuální, vše, co je potřeba k dodání aktualizovaných dat. pro uživatele je přerušení dostatečně dlouhé na provedení změny metadat.

V tomto příspěvku jsem zmínil dvě upozornění, že metodika, kterou jsem v průběhu let prosazoval, v současnosti nevyhovuje:zahraniční klíčová omezení a statistiky . Existuje řada dalších funkcí, které mohou narušovat tuto techniku. Jeden, který se nedávno objevil v konverzaci:spouštěče . A jsou tu další:sloupce identity , omezení primárního klíče , výchozí omezení , zkontrolujte omezení , omezení, která odkazují na UDF , indexy , zobrazení (včetně indexovaných zobrazení , které vyžadují SCHEMABINDING ) a oddíly . Nebudu se dnes zabývat všemi těmito, ale řekl jsem si, že jich pár otestuji, abych přesně viděl, co se stane.

Přiznám se, že mé původní řešení bylo v podstatě snímek chudáka, bez všech potíží, celé databáze a licenčních požadavků řešení, jako je replikace, zrcadlení a skupiny dostupnosti. Jednalo se o kopie tabulek pouze pro čtení z produkce, které byly „zrcadleny“ pomocí T-SQL a techniky swapování schémat. Takže nepotřebovali žádné z těchto ozdobných klíčů, omezení, spouštěčů a dalších funkcí. Ale vidím, že tato technika může být užitečná ve více scénářích a v těchto scénářích mohou hrát roli některé z výše uvedených faktorů.

Pojďme tedy nastavit jednoduchý pár tabulek, které mají několik těchto vlastností, proveďte výměnu schématu a uvidíme, co se pokazí. :-)

Nejprve schémata:

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

Nyní tabulka v live schéma, včetně spouštěče a 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

Nyní totéž zopakujeme pro kopii tabulky v prep . Potřebujeme také druhou kopii spouštěče, protože spouštěč nemůžeme vytvořit v prep schéma, které odkazuje na tabulku v live , nebo naopak. Záměrně nastavíme identitu na vyšší počáteční hodnotu a jinou výchozí hodnotu pro int_column (abychom mohli lépe sledovat, se kterou kopií tabulky skutečně máme co do činění po několika výměnách schémat):

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

Nyní vložíme do každé tabulky několik řádků a sledujeme výstup:

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;

Výsledky:

id int_column udf_column computed_column
1

1 20 2
2

1 20 2

Výsledky z live.t1

id int_column udf_column computed_column
1000

2 20 3
1001

2 20 3

Výsledky z prep.t1

A v podokně zpráv:

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

Nyní provedeme jednoduchou výměnu schématu:

 -- 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 pak opakujte cvičení:

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;

Výsledky v tabulkách se zdají být v pořádku:

id int_column udf_column computed_column
1

1 20 2
2

1 20 2
3

1 20 2
4

1 20 2

Výsledky z 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

Výsledky z prep.t1

Ale podokno zpráv uvádí spouštěcí výstup ve špatném pořadí:

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

Pojďme se tedy ponořit do všech metadat. Zde je dotaz, který rychle zkontroluje všechny sloupce identity, spouštěče, primární klíče, výchozí a zkontroluje omezení pro tyto tabulky, se zaměřením na schéma přidruženého objektu, název a definici (a počáteční / poslední hodnotu pro sloupce identity):

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

Výsledky naznačují docela nepořádek v metadatech:

type schéma jméno definice
Zkontrolujte příprava ck_live ([int_column]>(0))
Zkontrolujte živě ck_prep ([int_column]>(1))
Výchozí příprava df_live1 ((1))
Výchozí příprava df_live2 ([dbo].[udf]())
Výchozí živě df_prep1 ((2))
Výchozí živě df_prep2 ([dbo].[udf]())
Spouštěč příprava trig_live CREATE TRIGGER live.trig_live ON live.t1 FOR INSERT AS BEGIN PRINT 'live.trig'; END
Spouštěč živě trig_prep CREATE TRIGGER prep.trig_prep ON prep.t1 FOR INSERT AS BEGIN PRINT 'prep.trig'; END
Identita příprava semeno =1 poslední_hodnota =4
Identita živě semeno =1000 poslední_hodnota =1003
Primární klíč příprava pk_live
Primární klíč živě pk_prep

Metadata kachna-kachna-husa

Problémy se sloupci identity a omezeními se nezdají být velkým problémem. I když se zdá, že objekty podle zobrazení katalogu ukazují na nesprávné objekty, funkce – alespoň pro základní vložky – funguje tak, jak byste očekávali, kdybyste se na metadata nikdy nedívali.

Velký problém je se spouštěčem – když jsem na chvíli zapomněl, jak triviální jsem tento příklad udělal, v reálném světě pravděpodobně odkazuje na základní tabulku podle schématu a názvu. V takovém případě, když je připevněna ke špatnému stolu, věci se mohou pokazit... no, špatně. Vraťme se zpět:

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

(Můžete znovu spustit dotaz na metadata, abyste se přesvědčili, že je vše zpět k normálu.)

Nyní změňme spouštěč *pouze* na live verze, aby skutečně udělal něco užitečného (dobře, „užitečného“ v kontextu tohoto experimentu):

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

Nyní vložíme řádek:

INSERT live.t1 DEFAULT VALUES;

Výsledky:

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

Poté proveďte výměnu znovu:

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

A vložte další řádek:

INSERT live.t1 DEFAULT VALUES;

Výsledky (v podokně zpráv):

prep.trig

A jé. Pokud provádíme tuto výměnu schématu jednou za hodinu, pak po dobu 12 hodin z každého dne spouštěč nedělá to, co očekáváme, protože je spojen se špatnou kopií tabulky! Nyní změňme "prep" verzi spouštěče:

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

Výsledek:

Zpráva 208, úroveň 16, stav 6, procedura trig_prep, řádek 1
Neplatný název objektu 'prep.trig_prep'.

No, to rozhodně není dobré. Protože jsme ve fázi metadata-is-swap, žádný takový objekt neexistuje; spouštěče jsou nyní live.trig_prep a prep.trig_live . Ještě zmatený? Já také. Takže zkusíme toto:

EXEC sp_helptext 'live.trig_prep';

Výsledky:

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

No není to vtipné? Jak změním tento spouštěč, když se jeho metadata ani správně neodrážejí v jeho vlastní definici? Zkusme toto:

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

Výsledky:

Msg 2103, Level 15, State 1, Procedure trig_prep, Line 1
Nelze změnit trigger 'live.trig_prep', protože jeho schéma se liší od schématu cílové tabulky nebo pohledu.

To také není dobré, očividně. Zdá se, že ve skutečnosti neexistuje dobrý způsob, jak vyřešit tento scénář, který nezahrnuje výměnu objektů zpět do jejich původních schémat. Tento spouštěč bych mohl upravit tak, aby byl proti 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 teď mám dva spouštěče, které ve svém hlavním textu říkají, že fungují proti live.t1 , ale pouze tento skutečně provede. Ano, točí se mi hlava (a stejně tak i Michael J. Swart (@MJSwart) v tomto příspěvku na blogu). A všimněte si, že za účelem vyčištění tohoto nepořádku mohu po opětovném prohození schémat zahodit spouštěče s jejich původními názvy:

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

Pokud zkusím DROP TRIGGER live.trig_prep; , například se mi zobrazí chyba nenalezen objekt.

Rozlišení?

Řešením problému se spouštěčem je dynamické generování CREATE TRIGGER kód a v rámci swapu zahoďte a znovu vytvořte spouštěč. Nejprve vložme spouštěč zpět do *aktuální* tabulky v live (ve svém scénáři se můžete rozhodnout, zda vůbec potřebujete spouštěč na prep verze tabulky vůbec):

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

Nyní rychlý příklad toho, jak by fungovala naše nová výměna schématu (a možná budete muset upravit, aby se zabývalo každým spouštěčem, pokud máte více spouštěčů, a zopakovat to pro schéma v prep verzi, pokud tam také potřebujete udržovat spoušť. Věnujte zvláštní pozornost tomu, že níže uvedený kód pro stručnost předpokládá, že na live.t1 je pouze *jeden* spouštěč .

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;

Dalším (méně žádoucím) řešením by bylo provést celou operaci výměny schématu dvakrát, včetně všech operací, které se vyskytnou proti prep verze tabulky. Což v první řadě maří účel výměny schémat:zkrácení doby, po kterou uživatelé nemohou přistupovat k tabulkám a přináší jim aktualizovaná data s minimálním přerušením.


  1. Nelze VYBRAT z klauzule UPDATE RETURNING v postgresu

  2. sql join two table

  3. Jak převést excelový list do databáze sqlite v Androidu

  4. Jak vytvářet a mazat databáze a tabulky v PostgreSQL