[ Část 1 | Část 2 | Část 3 | Část 4 ]
V této sérii jsem zatím ukázal přímý fyzický dopad na stránku při přenesení z int
na bigint
a poté k této operaci iterovali několik běžných blokátorů. V tomto příspěvku jsem chtěl prozkoumat dvě možná řešení:jedno jednoduché a jedno neuvěřitelně spletité.
Snadná cesta
V komentáři k mému předchozímu příspěvku jsem byl trochu okraden o svůj hrom – Keith Monroe navrhl, že byste mohli jednoduše přesadit tabulku na nižší zápor vázané na datový typ integer, čímž se zdvojnásobí vaše kapacita pro nové hodnoty. Můžete to udělat pomocí DBCC CHECKIDENT
:
DBCC CHECKIDENT(N'dbo.TableName', RESEED, -2147483648);
To by mohlo fungovat za předpokladu, že náhradní hodnoty nemají pro koncové uživatele význam (nebo, pokud ano, že uživatelé nebudou vyděšeni tím, že náhle dostanou záporná čísla). Předpokládám, že byste je mohli oklamat pohledem:
CREATE VIEW dbo.ViewNameAS SELECT ID =CONVERT(bigint, CASE WHEN ID <0 THEN (2147483648*2) - 1 + CONVERT(bigint, ID) ELSE ID END) FROM dbo.TableName;
To znamená, že uživatel, který přidal ID = -2147483648
by ve skutečnosti viděl +2147483648
, uživatel, který přidal ID = -2147483647
uvidí +2147483649
, a tak dále. Museli byste však upravit jiný kód, abyste se ujistili, že provedete opačný výpočet, když uživatel zadá toto ID
, např.
ALTER PROCEDURE dbo.GetRowByID @ID bigintASBEGIN SET NOCOUNT ON; DECLARE @RealID bigint; SET @RealID =CASE WHEN @ID> 2147483647 THEN @ID - (2147483648*2) + 1 ELSE @ID END; SELECT ID, @ID /*, ostatní sloupce */ FROM dbo.TableName WHERE ID =@RealID;ENDGO
Nejsem blázen do tohoto mlžení. Vůbec. Je to chaotické, zavádějící a náchylné k chybám. A podporuje viditelnost náhradních klíčů – obecně IDENTITY
hodnoty by neměly být vystaveny koncovým uživatelům, takže by jim opravdu mělo být jedno, jestli jsou zákazníkem 24, 642, -376 nebo mnohem většími čísly na obou stranách nuly.
Toto „řešení“ také předpokládá, že nikde nemáte kód, který by se řadil podle IDENTITY
sloupec, aby byly jako první uvedeny naposledy vložené řádky, nebo z toho vyvozuje nejvyšší IDENTITY
hodnota musí být nejnovější řádek. Kód, který dělá spoléhat na pořadí řazení IDENTITY
sloupec, ať už explicitně nebo implicitně (což může být více, než si myslíte, pokud jde o seskupený index), již nebude zobrazovat řádky v očekávaném pořadí – zobrazí všechny řádky vytvořené po RESEED
, počínaje prvním a poté zobrazí všechny řádky vytvořené před RESEED
, počínaje prvním.
Primární výhodou tohoto přístupu je, že nevyžaduje změnu datového typu a v důsledku toho RESEED
změna nevyžaduje žádné změny indexů, omezení nebo příchozích cizích klíčů.
Nevýhodou – kromě výše zmíněných změn kódu, samozřejmě – je to, že vám to zkrátí čas. Nakonec také vyčerpáte všechna dostupná záporná celá čísla. A nemyslete si, že se tím zdvojnásobí životnost aktuální verze tabulky z hlediska času – v mnoha případech se růst dat zrychluje a nezůstává konstantní, takže další 2 miliardy řádků spotřebujete mnohem rychleji než první 2 miliardy.
Těžší způsob
Dalším přístupem, který můžete zvolit, je přestat používat IDENTITY
sloupec dohromady; místo toho můžete převést na použití SEQUENCE
. Můžete vytvořit nový bigint
nastavte výchozí hodnotu na další hodnotu z SEQUENCE
, aktualizujte všechny tyto hodnoty hodnotami z původního sloupce (v případě potřeby v dávkách), zrušte původní sloupec a přejmenujte nový sloupec. Vytvořme tuto fiktivní tabulku a vložíme jeden řádek:
VYTVOŘIT TABULKU dbo.SequenceDemo( ID int IDENTITY(1,1), x char(1), OMEZENÍ PK_SD_Identity PRIMÁRNÍ KLÍČ SLUSTROVANÝ (ID));GO INSERT dbo.SequenceDemo(x) VALUES('x');Dále vytvoříme
SEQUENCE
který začíná těsně za horní hranicí int:VYTVOŘTE SEKVENCI dbo.BeyondIntAS bigintZAČNĚTE S 2147483648 PŘÍRŮSTEM O 1;Dále změny v tabulce nutné přepnout pomocí
SEQUENCE
pro nový sloupec:ZAČÁTEK TRANSAKCE; -- přidejte nový sloupec "identity":ALTER TABLE dbo.SequenceDemo ADD ID2 bigint;GO -- nastavte nový sloupec na hodnotu shodnou s existujícími hodnotami identity-- u velkých tabulek to možná bude nutné provést v dávkách:UPDATE dbo.SequenceDemo SET ID2 =ID; -- nyní zajistěte, aby nebylo možné použít hodnotu Null a přidejte výchozí hodnotu z naší SEQUENCE:ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 bigint NOT NULL;ALTER TABLE dbo.SequenceDemo PŘIDAT OMEZENÍ DF_SD_Identity VÝCHOZÍ DALŠÍ HODNOTA PRO dbo.BeyondInt -- potřeba zrušit stávající PK (a všechny indexy):ALTER TABLE dbo.SequenceDemo DROP CONSTRAINT PK_SD_Identity; -- zrušte starý sloupec a přejmenujte nový:ALTER TABLE dbo.SequenceDemo DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN'; -- nyní zálohujte PK:ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED (ID); ZAKÁZAT TRANSAKCI;V tomto případě by další vložení přineslo následující výsledky (všimněte si, že
SCOPE_IDENTITY()
již nevrací platnou hodnotu):INSERT dbo.SequenceDemo(x) VALUES('y');SELECT Si =SCOPE_IDENTITY();SELECT ID, x FROM dbo.SequenceDemo; /* výsledky Si----NULL ID x---------- -1 x2147483648 y */Pokud je tabulka velká a potřebujete aktualizovat nový sloupec v dávkách namísto výše uvedené jednorázové transakce, jak jsem popsal zde – což uživatelům umožňuje mezitím interagovat s tabulkou – budete muset mít spouštěč na místě přepsat
SEQUENCE
hodnotu pro všechny nové řádky, které jsou vloženy, aby nadále odpovídaly tomu, co je na výstupu libovolného volacího kódu. (To také předpokládá, že máte stále nějaký prostor v rozsahu celých čísel, abyste mohli pokračovat v přijímání některých aktualizací; jinak, pokud jste již vyčerpali rozsah, budete muset udělat nějakou prostoj – nebo krátkodobě použít snadné řešení výše .)Zanechme všeho a začněme znovu, pak stačí přidat nový sloupec:
DROP TABLE dbo.SequenceDemo;DROP SEQUENCE dbo.BeyondInt;GO CREATE TABLE dbo.SequenceDemo( ID int IDENTITY(1,1), x char(1), OMEZENÍ PK_SD_Identity PRIMÁRNÍ KLÍČ SE SLUSTROVANÝM (ID));GO INSERT .SequenceDemo(x) VALUES('x');GO VYTVOŘIT SEKVENCI dbo.BeyondIntAS bigintZAČÁTE S 2147483648 PŘÍRŮST O 1;PŘEJÍT ALTER TABLE dbo.SequenceDemo PŘIDAT ID2 bigint;GOA zde je spouštěč, který přidáme:
CREATE TRIGGER dbo.After_SequenceDemoON dbo.SequenceDemoAFTER INSERTASBEGIN UPDATE sd SET sd.ID2 =sd.ID FROM dbo.SequenceDemo AS sd INNER JOIN vložen JAKO i ON sd.ID =i.ID;ENDTentokrát bude další vložení pokračovat ve generování řádků v dolním rozsahu celých čísel pro oba sloupce, dokud nebudou aktualizovány všechny dříve existující hodnoty a nebudou potvrzeny zbývající změny:
INSERT dbo.SequenceDemo(x) VALUES('y');SELECT Si =SCOPE_IDENTITY();SELECT ID, ID2, x FROM dbo.SequenceDemo; /* výsledky Si----2 ID ID2 x---- ---- --1 NULL x2 2 y */Nyní můžeme pokračovat v aktualizaci stávajícího
ID2
hodnoty, zatímco nové řádky se nadále vkládají do nižšího rozsahu:SET NOCOUNT ON; DECLARE @r INT =1; WHILE @r> 0BEGIN BEGIN TRANSACTION; UPDATE TOP (10000) dbo.SequenceDemo SET ID2 =ID KDE JE ID2 NULL; SET @r =@@ŘÁDEK; ZAKÁZAT TRANSAKCI; -- KONTROLNÍ BOD; -- if simple -- BACKUP LOG ... -- if fullENDJakmile aktualizujeme všechny existující řádky, můžeme pokračovat se zbytkem změn a poté spouštěč zrušit:
BEGIN TRANSACTION;ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 BIGINT NOT NULL;ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity VÝCHOZÍ DALŠÍ HODNOTA PRO dbo.BeyondInt FOR ID2;ALTER. DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN';ALTER TABLE dbo.SequenceDemo PŘIDAT OMEZENÍ PK_SD_Identity PRIMÁRNÍ KLÍČ V CLUSTERED (ID);DROP COMMIT TRANSACTION dboequenS před>Nyní další vložení vygeneruje tyto hodnoty:
INSERT dbo.SequenceDemo(x) VALUES('z');SELECT Si =SCOPE_IDENTITY();SELECT ID, x FROM dbo.SequenceDemo; /* výsledky Si----NULL ID x---------- -1 x2 y2147483648 z */Pokud máte kód, který závisí na
SCOPE_IDENTITY()
,@@IDENTITY
neboIDENT_CURRENT()
, musel by se také změnit, protože tyto hodnoty již nejsou vyplněny po vložení – ačkoliOUTPUT
klauzule by měla nadále správně fungovat ve většině scénářů. Pokud potřebujete, aby váš kód nadále věřil, že tabulka generujeIDENTITY
hodnotu, pak byste mohli použít spouštěč k předstírání této hodnoty – bylo by však možné naplnit pouze@@IDENTITY
na vložce, nikoliSCOPE_IDENTITY()
. To může stále vyžadovat změny, protože ve většině případů nechcete spoléhat na@@IDENTITY
pro cokoli (takže, pokud se chystáte provést změny, odstraňte všechny předpoklady oIDENTITY
sloupec vůbec).CREATE TRIGGER dbo.FakeIdentityON dbo.SequenceDemoMÍSTO INSERTASBEGIN SET NOCOUNT ON; DECLARE @lowestID bigint =(SELECT MIN(id) FROM vložen); DECLARE @sql nvarchar(max) =N'DECLARE @foo TABLE(ID bigint IDENTITY(' + CONVERT(varchar(32), @lowestID) + N',1));'; SELECT @sql +=N'INSERT @foo DEFAULT VALUES;' FROM vložen; EXEC sys.sp_executesql @sql; INSERT dbo.SequenceDemo(ID, x) SELECT ID, x FROM vložen;ENDNyní další vložení vygeneruje tyto hodnoty:
INSERT dbo.SequenceDemo(x) VALUES('a');SELECT Si =SCOPE_IDENTITY(), Ident =@@IDENTITY;SELECT ID, x FROM dbo.SequenceDemo; /* výsledky Si Ident---- -----NULL 2147483649 ID x---------- -1 x2 y2147483648 z2147483649 a */S tímto řešením byste se stále museli vypořádat s dalšími omezeními, indexy a tabulkami s příchozími cizími klíči. Místní omezení a indexy jsou docela jednoduché, ale složitější situací s cizími klíči se budu zabývat v další části této série.
Takový, který nebude fungovat, ale přál bych si ho
ALTER TABLE SWITCH
může být velmi účinným způsobem, jak provést některé změny metadat, které je jinak obtížné provést. A na rozdíl od všeobecného přesvědčení to nezahrnuje pouze rozdělování a není omezeno na Enterprise Edition. Následující kód bude fungovat na Express a je to metoda, kterou lidé použili k přidání nebo odstraněníIDENTITY
vlastnost na stole (opět nepočítáme s cizími klíči a všemi těmi otravnými blokátory).CREATE TABLE dbo.WithIdentity( ID int IDENTITY(1,1) NOT NULL); CREATE TABLE dbo.WithoutIdentity( ID int NOT NULL); ALTER TABLE dbo.WithIdentity PŘEPNOUT NA dbo.WithoutIdentity;PŘEJÍT DROP TABLE dbo.WithIdentity;EXEC sys.sp_rename N'dbo.WithoutIdentity', N'dbo.WithIdentity', 'OBJECT';Funguje to, protože datové typy a možnost null se přesně shodují a
IDENTITY
se nevěnuje žádná pozornost atribut. Zkuste však kombinovat datové typy a věci nefungují tak dobře:CREATE TABLE dbo.SourceTable( ID int IDENTITY(1,1) NOT NULL); CREATE TABLE dbo.TrySwitch( ID bigint IDENTITY(1,1) NOT NULL); ALTER TABLE dbo.SourceTable PŘEPNOUT NA dbo.TrySwitch;Výsledkem je:
Msg 4944, Level 16, State 1
Příkaz ALTER TABLE SWITCH selhal, protože sloupec 'ID' má datový typ int ve zdrojové tabulce 'dbo.SourceTable', který se liší od typu bigint v cílové tabulce 'dbo.TrySwitch'.Bylo by fantastické, kdyby
SWITCH
operace by mohla být použita ve scénáři, jako je tento, kde jediný rozdíl ve schématu ve skutečnosti *nevyžadoval* žádné fyzické změny k přizpůsobení (opět, jak jsem ukázal v části 1, data jsou přepsána na nové stránky, i když není to nutné).Závěr
Tento příspěvek zkoumal dvě možná řešení, jak získat čas před změnou stávající
IDENTITY
nebo opuštěníIDENTITY
úplně teď ve prospěchSEQUENCE
. Pokud pro vás není přijatelné ani jedno z těchto řešení, podívejte se na část 4, kde se tomuto problému budeme věnovat přímo.—
[ Část 1 | Část 2 | Část 3 | Část 4 ]