[ Част 1 | Част 2 | Част 3 | Част 4 ]
В първата част на тази серия показах какво се случва с физическа страница при промяна на колона IDENTITY от int на bigint. За да опростя нещата, създадох много проста купчина без индекси или ограничения. За съжаление, повечето от нас нямат този вид лукс – важна маса, която трябва да се промени, но не може просто да бъде пресъздадена от нулата, вероятно има множество атрибути, стоящи директно на пътя ни. В тази публикация исках да покажа по-често срещаните, без дори да навлизам в екзотични неща като In-Memory OLTP и Columnstore.
Първичен ключ
Надяваме се, че всичките ви таблици имат първичен ключ; ако колоната IDENTITY е включена обаче, няма да е толкова лесно да се промени основният тип данни. Вземете тези прости примери, както клъстерирани, така и неклъстерни първични ключове:
CREATE TABLE dbo.Test1 ( ID INT IDENTITY(1,1), CONSTRAINT PK_1 PRIMARY KEY NONCLUSTERED (ID) ); CREATE TABLE dbo.Test2 ( ID INT IDENTITY(1,1), CONSTRAINT PK_2 PRIMARY KEY CLUSTERED (ID) );
Ако се опитам да променя колоната:
ALTER TABLE dbo.Test1 ALTER COLUMN ID BIGINT; GO ALTER TABLE dbo.Test2 ALTER COLUMN ID BIGINT;
Получавам двойка съобщения за грешка за всяка ALTER (показвам само първата двойка):
Msg 5074, Level 16, State 1Обектът 'PK_1' зависи от колона 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID неуспешен, защото едно или повече обекти имат достъп до тази колона.
Резюме:Ще трябва да премахнем първичния ключ , независимо дали е или не е клъстерирано.
Индекси
Първо нека вземем няколко таблици като по-горе и използвайки уникален индекс вместо първичен ключ:
CREATE TABLE dbo.Test3 ( ID INT IDENTITY(1,1), INDEX IX_3 UNIQUE NONCLUSTERED (ID) ); CREATE TABLE dbo.Test4 ( ID INT IDENTITY(1,1), INDEX IX_4 UNIQUE CLUSTERED (ID) );
Изпълнението на подобни команди ALTER по-горе води до същите съобщения за грешка. Това остава вярно, дори ако деактивирам индексите:
ALTER INDEX IX_3 ON dbo.Test3 DISABLE; GO ALTER INDEX IX_4 ON dbo.Test4 DISABLE;
Подобни резултати за различни други типове комбинации от индекси, като включена колона или филтър:
CREATE TABLE dbo.Test5 ( ID INT IDENTITY(1,1), x CHAR(1) ); CREATE INDEX IX_5 ON dbo.Test5(x) INCLUDE(ID); CREATE TABLE dbo.Test6 ( ID INT IDENTITY(1,1), x CHAR(1) ); CREATE INDEX IX_6 ON dbo.Test6(x) WHERE ID > 0;
Резюме:Ще трябва да премахнем и да създадем отново всички индекси , групирани или не, които препращат към колоната IDENTITY – в ключа или INCLUDE. Ако колоната IDENTITY е част от клъстерирания индекс, това означава всички индекси , тъй като всички те ще се позовават на ключа за клъстериране по дефиниция. И деактивирането им не е достатъчно.
Изчислени колони
Въпреки че това би трябвало да е сравнително рядко, аз съм виждал изчислени колони въз основа на колоната IDENTITY. Например:
CREATE TABLE dbo.Test7 ( ID INT IDENTITY(1,1), NextID AS (ID + 1) );
Този път, когато се опитаме да променим, получаваме същата двойка грешки, но с малко по-различен текст:
Msg 5074, Level 16, State 1Колоната 'NextID' зависи от колона 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID неуспешна, защото едно или повече обекти имат достъп до тази колона.
Това е вярно дори, ако променим дефиницията на изчислената колона, за да съответства на целевия тип данни:
CREATE TABLE dbo.Test8 ( ID INT IDENTITY(1,1), NextID AS (CONVERT(BIGINT, ID) + 1) );
Резюме:Ще трябва да променим дефинициите на изчислените колони или да ги премахнем напълно.
Индексирани изгледи
Индексираните изгледи също виждат справедливия им дял от използването. Нека изградим индексиран изглед, който дори не препраща към колоната IDENTITY (обърнете внимание, че няма други индекси или ограничения в основната таблица):
CREATE TABLE dbo.Test9 ( ID INT IDENTITY(1,1), x CHAR(1) ); GO CREATE VIEW dbo.vTest9A WITH SCHEMABINDING AS SELECT x, c = COUNT_BIG(*) FROM dbo.Test9 GROUP BY x; GO CREATE UNIQUE CLUSTERED INDEX IX_9A ON dbo.vTest9A(x);
Още веднъж ще опитаме ALTER и този път успешно . Ще призная, че бях изненадан от това, тъй като SCHEMABINDING трябва да предотвратява всякакви промени в основната таблица, но в този случай се прилага само за колони, изрично посочени в изгледа. Ако създадем малко по-различен изглед:
CREATE VIEW dbo.vTest9B WITH SCHEMABINDING AS SELECT ID, c = COUNT_BIG(*) FROM dbo.Test9 GROUP BY ID; GO CREATE UNIQUE CLUSTERED INDEX IX_9B ON dbo.vTest9B(ID);
Сега ще се провалим поради зависимостта на колоната:
Msg 5074, Level 16, State 1Обектът 'vTest9B' зависи от колона 'ID'.
Msg 4922, Level 16, State 9
ALTER TABLE ALTER COLUMN ID неуспешен, защото един или повече обекти имат достъп до тази колона.
Резюме:Ще трябва да премахнем всички индекси във всички изгледи, които изрично препращат към колоната IDENTITY , както и всички индекси във всеки изглед, който препраща към колоната IDENTITY в своя клъстериран индекс.
Входящи външни ключове
Вероятно най-проблемният аспект на първичните ключове на IDENTITY е, че поради самото естество на сурогатите целият смисъл често е да се използва този сурогатен ключ в множество свързани таблици. Сега, нямам намерение да се застъпвам за избягване на референтната цялост, но тя потенциално ще застане малко на пътя ни и тук. Знаем отгоре, че не можем да променим колона, която е част от първичен ключ или уникално ограничение и за да може друга таблица да сочи тук с ограничение за външен ключ, едно от тези две неща трябва да съществува. Така че да кажем, че имаме следните две таблици:
CREATE TABLE dbo.TestParent ( ID INT IDENTITY(1,1), CONSTRAINT PK_Parent PRIMARY KEY CLUSTERED(ID) ); GO CREATE TABLE dbo.TestChild ( ParentID INT NOT NULL, CONSTRAINT FK_Parent FOREIGN KEY(ParentID) REFERENCES dbo.TestParent(ID) );
Преди дори да обмислим промяна на типа данни на колоната, трябва да премахнем ограничението:
ALTER TABLE dbo.TestParent DROP CONSTRAINT PK_Parent;
И разбира се, не можем, без също така да премахнем ограничението за външния ключ, защото това дава следното съобщение за грешка:
Msg 3725, Level 16, State 0Ограничението 'PK_Parent' се препраща от таблица 'TestChild', ограничение за външен ключ 'FK_Parent'.
Съобщение 3727, ниво 16, състояние 0
Може не отпадайте ограничението. Вижте предишни грешки.
Тази грешка остава дори ако първо деактивираме ограничението за външния ключ:
ALTER TABLE dbo.TestChild NOCHECK CONSTRAINT FK_Parent;
Освен това имайте предвид, че ще ви трябват референтните колони, за да промените и техния тип данни. Освен това тези колони вероятно участват в някои от горните елементи, които по подобен начин могат да предотвратят промяната в дъщерните таблици. За да направим нещата напълно съвместими и синхронизирани, ще трябва да:
- изхвърлете съответните ограничения и индекси в родителската таблица
- изхвърлете съответните ограничения на външния ключ върху дъщерните таблици
- изпускане на всички индекси върху дъщерни таблици, които препращат към FK колоната (и се справят с всички подходящи изчислени колони/индексирани изгледи)
- променете типа данни на родителските и всички дъщерни таблици
- създайте отново всичко
Резюме:Ще трябва да премахнем входящите външни ключове и потенциално това ще има цял набор от каскадни ефекти. Простото деактивиране на външните ключове не е достатъчно и така или иначе не би било постоянно решение, тъй като типът данни в крайна сметка също ще трябва да се промени в дъщерните таблици.
Заключение
Знам, че изглежда, че се движим бавно и признавам, че в тази публикация изглежда се отдръпвам от решение, а не към такова. Ще стигна до там, просто има много информация за представяне първо, включително нещата, които затрудняват този тип промяна. Извадени от обобщенията по-горе, ще трябва да:
- пуснете и създайте отново съответните индекси в главната таблица
- променете или пуснете изчислени колони, които включват колоната IDENTITY
- изпускане на индекси в индексирани изгледи, които препращат към колоната IDENTITY
- работа с входящи външни ключове, които сочат към колоната IDENTITY
За съжаление, много от тези неща са catch-22. Не можете да промените колона, защото индексът разчита на нея, и не можете да промените индекса, докато колоната не се промени. Не би ли било чудесно, ако ALTER INDEX поддържа REBUILD WITH (ONLINE = ON, CHANGE_COLUMN (COLUMN = ID, NEW_TYPE = BIGINT))
? И CASCADE_CHANGE_TO_REFERENCING_KEYS,COLUMNS,INDEXES,VIEWS,ETC
? Е, не е (проверих). Така че трябва да намерим начини да улесним тези неща. Моля, следете за част 3.
—
[ Част 1 | Част 2 | Част 3 | Част 4 ]