sql >> Database teknologi >  >> RDS >> Database

Skema Switch-A-Roo:Del 2

Tilbage i august skrev jeg et indlæg om min skema-swap-metode til T-SQL tirsdag. Fremgangsmåden giver dig i det væsentlige mulighed for doven at indlæse en kopi af en tabel (f.eks. en opslagstabel af en slags) i baggrunden for at minimere interferens med brugere:når først baggrundstabellen er opdateret, er alt, hvad der kræves for at levere de opdaterede data for brugere er en afbrydelse, der er lang nok til at begå en metadataændring.

I det indlæg nævnte jeg to forbehold, som den metodologi, jeg har forkæmpet gennem årene, ikke i øjeblikket imødekommer:fremmede nøglebegrænsninger og statistik . Der er et væld af andre funktioner, der også kan forstyrre denne teknik. En, der kom op i samtalen for nylig:triggere . Og der er andre:identitetskolonner , primære nøglebegrænsninger , standardbegrænsninger , tjek begrænsninger , begrænsninger, der refererer til UDF'er , indekser , visninger (inklusive indekserede visninger , som kræver SCHEMABINDING ), og partitioner . Jeg har ikke tænkt mig at beskæftige mig med alle disse i dag, men jeg tænkte, at jeg ville teste et par stykker for at se præcis, hvad der sker.

Jeg vil indrømme, at min oprindelige løsning dybest set var et fattigmands øjebliksbillede uden alle besværet, hele databasen og licenskravene til løsninger som replikering, spejling og tilgængelighedsgrupper. Disse var skrivebeskyttede kopier af tabeller fra produktion, der blev "spejlet" ved hjælp af T-SQL og skemabytteteknikken. Så de havde ikke brug for nogen af ​​disse smarte nøgler, begrænsninger, udløsere og andre funktioner. Men jeg kan se, at teknikken kan være nyttig i flere scenarier, og i disse scenarier kan nogle af ovenstående faktorer spille ind.

Så lad os opsætte et simpelt tabeller, der har flere af disse egenskaber, udføre en skemabytning og se, hvad der går i stykker. :-)

Først skemaerne:

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

Nu, tabellen i live skema, inklusive en trigger og en 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

Nu gentager vi det samme for kopien af ​​tabellen i prep . Vi har også brug for en anden kopi af triggeren, fordi vi ikke kan oprette en trigger i prep skema, der refererer til en tabel i live , eller omvendt. Vi vil med vilje indstille identiteten til et højere frø og en anden standardværdi for int_column (for at hjælpe os bedre med at holde styr på, hvilken kopi af tabellen vi egentlig har at gøre med efter flere skemabytninger):

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

Lad os nu indsætte et par rækker i hver tabel og observere outputtet:

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;

Resultater:

id int_column udf_column computed_column
1

1 20 2
2

1 20 2

Resultater fra live.t1

id int_column udf_column computed_column
1000

2 20 3
1001

2 20 3

Resultater fra prep.t1

Og i meddelelsesruden:

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

Lad os nu udføre en simpel skemabytning:

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

Og gentag derefter øvelsen:

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;

Resultaterne i tabellerne virker okay:

id int_column udf_column computed_column
1

1 20 2
2

1 20 2
3

1 20 2
4

1 20 2

Resultater fra 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

Resultater fra prep.t1

Men meddelelsesruden viser triggeroutputtet i den forkerte rækkefølge:

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

Så lad os grave i alle metadataene. Her er en forespørgsel, der hurtigt vil inspicere alle identitetskolonner, triggere, primærnøgler, standard- og kontrolbegrænsninger for disse tabeller, med fokus på skemaet for det tilknyttede objekt, navnet og definitionen (og seed/sidste værdien for identitetskolonner):

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

Resultater indikerer noget af et metadatarod:

skriv skema navn definition
Tjek forberedelse ck_live ([int_column]>(0))
Tjek live ck_prep ([int_column]>(1))
Standard forberedelse df_live1 ((1))
Standard forberedelse df_live2 ([dbo].[udf]())
Standard live df_prep1 ((2))
Standard live df_prep2 ([dbo].[udf]())
Trigger forberedelse trig_live CREATE TRIGGER live.trig_live ON live.t1 FOR INSERT AS BEGIN PRINT 'live.trig'; END
Trigger live trig_prep CREATE TRIGGER prep.trig_prep ON prep.t1 FOR INSERT AS BEGIN PRINT 'prep.trig'; END
Identitet forberedelse frø =1 sidste_værdi =4
Identitet live frø =1000 sidste_værdi =1003
Primær nøgle forberedelse pk_live
Primær nøgle live pk_prep

Metadata duck-duck-goose

Problemerne med identitetskolonner og begrænsninger ser ikke ud til at være et stort problem. Selvom objekterne *tilsyneladende* peger på de forkerte objekter ifølge katalogvisningerne, fungerer funktionaliteten – i hvert fald for grundlæggende indsætninger – som du kunne forvente, hvis du aldrig havde set på metadataene.

Det store problem er med triggeren – glemmer et øjeblik hvor trivielt jeg lavede dette eksempel, i den virkelige verden refererer det sandsynligvis til basistabellen efter skema og navn. I så fald, når det er fastgjort til det forkerte bord, kan tingene gå... ja, galt. Lad os skifte tilbage:

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

(Du kan køre metadataforespørgslen igen for at overbevise dig selv om, at alt er tilbage til det normale.)

Lad os nu ændre udløseren *kun* på live version for rent faktisk at gøre noget nyttigt (nå, "nyttigt" i forbindelse med dette eksperiment):

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

Lad os nu indsætte en række:

INSERT live.t1 DEFAULT VALUES;

Resultater:

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

Udfør derefter skiftet igen:

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

Og indsæt endnu en række:

INSERT live.t1 DEFAULT VALUES;

Resultater (i meddelelsesruden):

prep.trig

Åh åh. Hvis vi udfører dette skemaskift en gang i timen, så i 12 timer ud af hver dag, gør triggeren ikke, hvad vi forventer, den gør, da den er forbundet med den forkerte kopi af tabellen! Lad os nu ændre "prep"-versionen af ​​triggeren:

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

Resultat:

Meddelelse 208, niveau 16, tilstand 6, procedure trig_prep, linje 1
Ugyldigt objektnavn 'prep.trig_prep'.

Nå, det er bestemt ikke godt. Da vi er i metadata-byttes-fasen, er der ikke noget sådant objekt; triggerne er nu live.trig_prep og prep.trig_live . Forvirret endnu? Også mig. Så lad os prøve dette:

EXEC sp_helptext 'live.trig_prep';

Resultater:

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

Jamen, er det ikke sjovt? Hvordan ændrer jeg denne trigger, når dens metadata ikke engang afspejles korrekt i dens egen definition? Lad os prøve dette:

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

Resultater:

Msg 2103, Level 15, State 1, Procedure trig_prep, Line 1
Kan ikke ændre trigger 'live.trig_prep', fordi dens skema er forskelligt fra skemaet for måltabellen eller visningen.

Det er åbenbart heller ikke godt. Det ser ud til, at der ikke rigtig er en god måde at løse dette scenarie på, som ikke involverer at bytte objekterne tilbage til deres oprindelige skemaer. Jeg kunne ændre denne trigger til at være imod 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

Men nu har jeg to triggere, der siger, i deres brødtekst, at de opererer mod live.t1 , men kun denne udfører faktisk. Ja, mit hoved snurrer (og det var Michael J. Swarts (@MJSwart) også i dette blogindlæg). Og bemærk, at for at rydde op i dette rod, efter at have byttet skemaer tilbage igen, kan jeg slippe triggerne med deres oprindelige navne:

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

Hvis jeg prøver DROP TRIGGER live.trig_prep; , for eksempel får jeg en fejlmeddelelse om objekt ikke fundet.

Opløsninger?

En løsning på triggerproblemet er dynamisk at generere CREATE TRIGGER kode, og slip og genskab triggeren som en del af byttet. Lad os først sætte en trigger tilbage på den *aktuelle* tabel i live (du kan beslutte i dit scenarie, om du overhovedet har brug for en trigger på prep version af tabellen overhovedet):

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

Nu et hurtigt eksempel på, hvordan vores nye skemabytning ville fungere (og du skal muligvis justere dette for at håndtere hver trigger, hvis du har flere triggere, og gentag det for skemaet på prep version, hvis du også skal bevare en trigger der. Vær særlig opmærksom på, at nedenstående kode for kortheds skyld antager, at der kun er *én* trigger på 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;

En anden (mindre ønskværdig) løsning ville være at udføre hele skemabytteoperationen to gange, inklusive de operationer, der forekommer mod prep version af bordet. Hvilket stort set besejrer formålet med skemabyttet i første omgang:at reducere den tid, brugere ikke kan få adgang til tabellen/tabellerne, og bringe dem de opdaterede data med minimal afbrydelse.


  1. Sådan viser du serversorteringen i SQL Server (T-SQL)

  2. Flere databaseforbindelser i Rails

  3. Konverter kommasepareret kolonneværdi til rækker

  4. Forbind ODBC-applikationer på Windows til SugarCRM