Database
 sql >> база данни >  >> RDS >> Database

Минимизиране на въздействието от разширяване на колона IDENTITY – част 3

[ Част 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 ]


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да изтриете колона в таблицата

  2. Използване на ODBC със Salesforce и услуги за федерация на Active Directory (ADFS) за единичен вход (SSO)

  3. Как се стартират паралелни планове – част 1

  4. Свързване на MS SQL към IRI Workbench

  5. Как да разрешим грешката `prisma/client все още не е инициализиран` ​​на Vercel