[ Част 1 | Част 2 | Част 3 | Част 4 ]
Досега в тази серия демонстрирах прякото физическо въздействие върху страницата при увеличаване на размера от int
към bigint
, и след това повторен през няколко от обичайните блокери към тази операция. В тази публикация исках да разгледам две възможни решения:едно просто и едно невероятно заплетено.
Лесният начин
Бях лишен от гръмотевицата си малко в коментар към предишната ми публикация – Кийт Монро предложи, че можете просто да поставите отново таблицата на по-ниския отрицателен bound на целочисления тип данни, удвоявайки капацитета ви за нови стойности. Можете да направите това с DBCC CHECKIDENT
:
DBCC CHECKIDENT(N'dbo.TableName', RESEED, -2147483648);
Това би могло да работи, ако приемем, че сурогатните стойности нямат значение за крайните потребители (или, ако имат, че потребителите няма да се уплашат от внезапно получаване на отрицателни числа). Предполагам, че бихте могли да ги заблудите с изглед:
СЪЗДАВАНЕ НА ИЗГЛЕД dbo.ViewNameAS ИЗБИРАНЕ ИД =CONVERT(bigint, CASE WHEN ID <0 THEN (2147483648*2) - 1 + CONVERT(bigint, ID) ELSE ID END) ОТ dbo.TableName;
Това означава, че потребителят, добавил ID = -2147483648
всъщност ще види +2147483648
, потребителят, добавил ID = -2147483647
ще види +2147483649
, и така нататък. Все пак ще трябва да коригирате друг код, за да сте сигурни, че правите обратното изчисление, когато потребителят предаде този ID
, напр.
ПРОМЕНЯ ПРОЦЕДУРА dbo.GetRowByID @ID bigintASBEGIN SET NOCOUNT ON; ДЕКЛАРИРАНЕ @RealID bigint; SET @RealID =CASE WHEN @ID> 2147483647 THEN @ID - (2147483648*2) + 1 ELSE @ID END; SELECT ID, @ID /*, други колони */ FROM dbo.TableName WHERE ID =@RealID;ENDGO
Не съм луд по това объркване. Изобщо. Той е разхвърлян, подвеждащ и податлив на грешки. И насърчава видимостта на сурогатните ключове – обикновено IDENTITY
Стойностите не трябва да се излагат на крайните потребители, така че те наистина не трябва да ги интересува дали са клиент 24, 642, -376 или много по-големи числа от двете страни на нулата.
Това „решение“ също предполага, че никъде нямате код, който да поръчва от IDENTITY
колона, за да се представят най-наскоро вмъкнатите редове, или прави извода, че най-високият IDENTITY
стойността трябва да е най-новият ред. Код, който прави разчитайте на реда на сортиране на IDENTITY
колона, изрично или имплицитно (което може да е повече, отколкото си мислите, ако е клъстерираният индекс), вече няма да представя редовете в очаквания ред – ще показва всички редове, създадени след RESEED
, като се започне с първия и след това ще се покажат всички редове, създадени преди RESEED
, като се започне от първия.
Основното предимство на този подход е, че не изисква от вас да променяте типа данни и в резултат на това RESEED
промяната не изисква никакви промени в индекси, ограничения или входящи външни ключове.
Недостатъкът – в допълнение към промените в кода, споменати по-горе, разбира се – е, че това ви печели време само в краткосрочен план. В крайна сметка ще изчерпите и всички налични отрицателни числа. И не мислете, че това удвоява полезния живот на текущата версия на таблицата по отношение на времето – в много случаи растежът на данните се ускорява, а не остава постоянен, така че ще изразходвате следващите 2 милиарда реда много по-бързо от първите 2 милиарда.
По-труден начин
Друг подход, който бихте могли да предприемете, е да спрете да използвате IDENTITY
колона като цяло; вместо това можете да конвертирате в използване на SEQUENCE
. Можете да създадете нов bigint
колона, задайте по подразбиране следващата стойност от SEQUENCE
, актуализирайте всички тези стойности със стойностите от оригиналната колона (на партиди, ако е необходимо), пуснете оригиналната колона и преименувайте новата колона. Нека създадем тази фиктивна таблица и да вмъкнем един ред:
СЪЗДАЙТЕ ТАБЛИЦА dbo.SequenceDemo( ID int IDENTITY(1,1), x char(1), ОГРАНИЧЕНИЕ PK_SD_Identity ПЪРВИЧЕН КЛУСТРИРАН (ID));GO INSERT dbo.SequenceDemo(x) VALUES('x');предварително>След това ще създадем
SEQUENCE
който започва точно отвъд горната граница на int:СЪЗДАВАНЕ НА ПОСЛЕДОВАТЕЛНОСТ dbo.BeyondIntAS bigintSTART С 2147483648 НАРАЩАНЕ С 1;След това промените в таблицата, необходими за преминаване към използване на
SEQUENCE
за новата колона:ЗАПОЧНЕТЕ ТРАНЗАКЦИЯ; -- добавете нова колона "идентификация":ALTER TABLE dbo.SequenceDemo ADD ID2 bigint;GO -- задайте новата колона равна на съществуващите стойности на идентичност-- за големи таблици може да се наложи да направите това в пакети:UPDATE dbo.SequenceDemo SET ID2 =ID; -- сега го направете без нула и добавете по подразбиране от нашата SEQUENCE:ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 bigint NOT NULL;ALTER TABLE dbo.SequenceDemo ДОБАВЯНЕ НА ОГРАНИЧЕНИЕ DF_SD_Identity DEFAULT СЛЕДВАЩА СТОЙНОСТ ЗА dbo.BeyondInt; -- трябва да премахнете съществуващия PK (и всички индекси):ALTER TABLE dbo.SequenceDemo DROP CONSTRAINT PK_SD_Identity; -- махнете старата колона и преименувайте новата:ALTER TABLE dbo.SequenceDemo DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN'; -- сега поставете PK резервно копие:ALTER TABLE dbo.SequenceDemo ДОБАВЯНЕ НА ОГРАНИЧЕНИЕ PK_SD_Identity ПЪРВИЧЕН КЛУСТЕР (ID); ИЗВЪРШВАНЕ НА ТРАНЗАКЦИЯ;В този случай следващото вмъкване ще даде следните резултати (обърнете внимание, че
SCOPE_IDENTITY()
вече не връща валидна стойност):INSERT dbo.SequenceDemo(x) VALUES('y');SELECT Si =SCOPE_IDENTITY();SELECT ID, x FROM dbo.SequenceDemo; /* резултати Si----NULL ID x---------- -1 x2147483648 y */Ако таблицата е голяма и трябва да актуализирате новата колона на партиди вместо горната еднократна транзакция, както описах тук – позволявайки на потребителите да взаимодействат с таблицата междувременно – ще трябва да имате тригер на място, за да замените
SEQUENCE
стойност за всички нови редове, които се вмъкват, така че те да продължат да съответстват на това, което се извежда към всеки извикващ код. (Това също така предполага, че все още имате място в целочисления диапазон, за да продължите да приемате някои актуализации; в противен случай, ако вече сте изчерпали диапазона, ще трябва да отделите малко време на престой – или да използвате лесното решение по-горе в краткосрочен план .)Нека пуснем всичко и да започнем отначало, след което просто добавим новата колона:
DROP TABLE dbo.SequenceDemo;DROP SEQUENCE dbo.BeyondInt;GO CREATE TABLE dbo.SequenceDemo( ID int IDENTITY(1,1), x char(1), ОГРАНИЧЕНИЕ PK_SD_Identity PRIMARY KLUSTERED (ID));GO INSERT dbo .SequenceDemo(x) VALUES('x');GO CREATE SEQUEENCE dbo.BeyondIntAS bigintSTART С 2147483648 НАРАЩАНЕ С 1;GO ALTER TABLE dbo.SequenceDemo ДОБАВИ ID2 bigint;GOИ ето спусъка, който ще добавим:
CREATE TRIGGER dbo.After_SequenceDemoON dbo.SequenceDemoAFTER INSERTASBEGIN UPDATE sd SET sd.ID2 =sd.ID FROM dbo.SequenceDemo AS sd INNER JOIN вмъкнат AS i ON sd.ID =i.ID;ENDТози път следващото вмъкване ще продължи да генерира редове в долния диапазон от цели числа и за двете колони, докато всички съществуващи стойности не бъдат актуализирани и останалите промени не бъдат приети:
INSERT dbo.SequenceDemo(x) VALUES('y');SELECT Si =SCOPE_IDENTITY();SELECT ID, ID2, x FROM dbo.SequenceDemo; /* резултати Si----2 ID ID2 x---- ---- --1 NULL x2 2 y */Сега можем да продължим да актуализираме съществуващия
ID2
стойности, докато новите редове продължават да се вмъкват в долния диапазон:ЗАДАДЕТЕ NOCOUNT ON; ДЕКЛАРИРАНЕ @r INT =1; WHILE @r> 0ЗАПОЧВАНЕ НА ТРАНЗАКЦИЯТА; UPDATE TOP (10000) dbo.SequenceDemo SET ID2 =ID, КЪДЕТО ID2 Е NULL; SET @r =@@ROWCOUNT; ИЗВЪРШВАНЕ НА ТРАНЗАКЦИЯ; -- КОНТРОЛНА ТОЧКА; -- ако е просто -- РЕЗЕРВЕН ДВИГАТЕЛ ... -- ако е пъленENDСлед като актуализираме всички съществуващи редове, можем да продължим с останалите промени и след това да пуснем тригера:
BEGIN TRANSACTION;ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 BIGINT NOT NULL;ALTER TABLE dbo.SequenceDemo ДОБАВЯНЕ НА ОГРАНИЧЕНИЕ DF_SD_Identity DEFAULT СЛЕДВАЩА СТОЙНОСТ ЗА dbo.BeyondInt FOR ID2;ALTER TABLEQUENTRANSEBO DBO. DROP COLUMN ID;EXEC sys.sp_rename N'dbo.SequenceDemo.ID2', N'ID', 'COLUMN';ALTER TABLE dbo.SequenceDemo ДОБАВЯНЕ НА ОГРАНИЧЕНИЕ PK_SD_Identity ПЪРВИЧЕН КЛЮЧ КЛУСТРИРАН (ID);DROP TRIGGER 'COLUMN'; предварително>Сега следващото вмъкване ще генерира тези стойности:
INSERT dbo.SequenceDemo(x) VALUES('z');SELECT Si =SCOPE_IDENTITY();SELECT ID, x FROM dbo.SequenceDemo; /* резултати Si----NULL ID x---------- -1 x2 y2147483648 z */Ако имате код, който разчита на
SCOPE_IDENTITY()
,@@IDENTITY
илиIDENT_CURRENT()
, също би трябвало да се промени, тъй като тези стойности вече не се попълват след вмъкване – въпреки чеOUTPUT
клаузата трябва да продължи да работи правилно в повечето сценарии. Ако имате нужда от вашия код, за да продължите да вярвате, че таблицата генерираIDENTITY
стойност, тогава бихте могли да използвате тригер, за да фалшифицирате това – но той ще може да попълни само@@IDENTITY
при вмъкване, а неSCOPE_IDENTITY()
. Това все пак може да изисква промени, тъй като в повечето случаи не искате да разчитате на@@IDENTITY
за каквото и да е (така че, ако ще правите промени, премахнете всички предположения заIDENTITY
колона изобщо).СЪЗДАДЕТЕ TRIGGER dbo.FakeIdentityON dbo.SequenceDemo ВМЕСТО ДА INSERTASBEGIN SET NOCOUNT ON; DECLARE @lowestID bigint =(ИЗБЕРЕТЕ MIN(id) FROM вмъкнат); DECLARE @sql nvarchar(max) =N'DECLARE @foo TABLE(ID bigint IDENTITY(' + CONVERT(varchar(32), @lowestID) + N',1));'; SELECT @sql +=N'INSERT @foo СТОЙНОСТИ ПО ПОДРАЗБИРАНЕ;' ОТ вмъкнат; EXEC sys.sp_executesql @sql; INSERT dbo.SequenceDemo(ID, x) SELECT ID, x FROM вмъкнат;ENDСега следващото вмъкване ще генерира тези стойности:
INSERT dbo.SequenceDemo(x) VALUES('a');SELECT Si =SCOPE_IDENTITY(), Ident =@@IDENTITY;SELECT ID, x FROM dbo.SequenceDemo; /* резултати Si Ident---- -----NULL 2147483649 ID x---------- -1 x2 y2147483648 z2147483649 a */С това заобиколно решение все още ще трябва да се справяте с други ограничения, индекси и таблици с входящи външни ключове. Локалните ограничения и индекси са доста ясни, но ще се занимавам с по-сложната ситуация с външните ключове в следващата част от тази поредица.
Такой, който няма да работи, но ми се иска да стане
ALTER TABLE SWITCH
може да бъде много мощен начин да направите някои промени в метаданните, които са трудни за изпълнение по друг начин. И противно на общоприетото схващане, това не включва само разделяне и не е ограничено до Enterprise Edition. Следният код ще работи на Express и е метод, който хората са използвали за добавяне или премахване наIDENTITY
свойство на маса (отново без отчитане на външни ключове и всички онези други досадни блокери).СЪЗДАВАНЕ НА ТАБЛИЦА dbo.WithIdentity( ID int IDENTITY(1,1) НЕ НУЛЕВО); CREATE TABLE dbo.WithoutIdentity( ID int NOT NULL); ПРОМЕНИ ТАБЛИЦА dbo.WithIdentity ПРЕВЪКНЕТЕ КЪМ dbo.WithoutIdentity;GO DROP TABLE dbo.WithIdentity;EXEC sys.sp_rename N'dbo.WithoutIdentity', N'dbo.WithIdentity', 'OBJECT';Това работи, защото типовете данни и възможността за нула съвпадат точно и не се обръща внимание на
IDENTITY
атрибут. Опитайте обаче да смесвате типове данни и нещата не работят толкова добре:СЪЗДАВАНЕ НА ТАБЛИЦА dbo.SourceTable( ID int IDENTITY(1,1) НЕ НУЛЕВО); CREATE TABLE dbo.TrySwitch( ID bigint IDENTITY(1,1) NOT NULL); ПРОМЕНИ ТАБЛИЦА dbo.SourceTable ПРЕВЪКНЕТЕ КЪМ dbo.TrySwitch;Това води до:
Съобщение 4944, ниво 16, състояние 1
Изразът ALTER TABLE SWITCH не успя, тъй като колоната „ID“ има тип данни int в изходната таблица „dbo.SourceTable“, който е различен от типа bigint в целевата таблица „dbo.TrySwitch“.Би било фантастично, ако
SWITCH
операцията може да се използва в сценарий като този, където единствената разлика в схемата всъщност не *изисква* никакви физически промени, за да се приспособят (отново, както показах в част 1, данните се записват отново на нови страници, въпреки че няма нужда да го правите).Заключение
Тази публикация проучи две възможни решения, за да ви спечели време, преди да промените съществуващата си
IDENTITY
колона или изоставяне наIDENTITY
напълно точно сега в полза наSEQUENCE
. Ако нито едно от тези решения не е приемливо за вас, моля, гледайте част 4, където ще се справим директно с този проблем.—
[ Част 1 | Част 2 | Част 3 | Част 4 ]