ALTER TABLE ... ALTER COLUMN
polecenie jest bardzo potężne. Możesz go użyć do zmiany typu danych, długości, precyzji, skali, wartości null, sortowania kolumny… i wielu innych rzeczy.
Jest to z pewnością wygodniejsze niż alternatywa:tworzenie nowej tabeli i migracja danych za każdym razem, gdy konieczna jest zmiana. Niemniej jednak niewiele można zrobić, aby ukryć leżącą u podstaw złożoność. Wraz z dużą liczbą ograniczeń dotyczących tego, co jest możliwe dzięki temu poleceniu, zawsze pojawia się kwestia wydajności.
Ostatecznie tabele są przechowywane jako sekwencja bajtów z pewnymi metadanymi w innym miejscu systemu, aby opisać, co każdy z tych bajtów oznacza i jak odnoszą się do poszczególnych kolumn tabeli. Kiedy prosimy SQL Server o zmianę jakiegoś aspektu definicji kolumny, musi sprawdzić, czy istniejące dane są zgodne z nową definicją. Musi również określić, czy obecny układ fizyczny musi się zmienić.
W zależności od typu zmiany i konfiguracji bazy danych, ALTER COLUMN
polecenie będzie musiało wykonać jedną z następujących czynności:
- Zmień metadane tylko w tabelach systemowych.
- Sprawdź wszystkie istniejące dane pod kątem zgodności, a następnie zmień metadane.
- Przepisz niektóre lub wszystkie zapisane dane, aby pasowały do nowej definicji.
Opcja 1 reprezentuje idealny przypadek z punktu widzenia wydajności. Wymaga tylko kilku zmian w tabelach systemowych i minimalnej ilości rejestrowania. Operacja nadal będzie wymagała restrykcyjnej modyfikacji schematu Sch-M
blokada, ale zmiany metadanych zakończą się bardzo szybko, niezależnie od rozmiaru tabeli.
Zmiany tylko dla metadanych
Istnieje wiele specjalnych przypadków, na które należy zwrócić uwagę, ale ogólnie rzecz biorąc, następujące działania wymagają jedynie zmiany metadanych:
- Przechodzę z
NOT NULL
naNULL
dla tego samego typu danych. - Zwiększanie maksymalnego rozmiaru
varchar
,nvarchar
lubvarbinary
kolumna (z wyjątkiemmax
).
Ulepszenia w SQL Server 2016
Tematem tego posta są dodatkowe zmiany, które są włączone tylko dla metadanych od SQL Server 2016 i nowszych . Nie są potrzebne żadne zmiany składni i nie trzeba modyfikować ustawień konfiguracyjnych. Otrzymujesz te nieudokumentowane ulepszenia za darmo.
Nowe możliwości są ukierunkowane na podzbiór stałej długości typy danych. Nowe zdolności mają zastosowanie do tabel magazynu wierszy w następujących okolicznościach:
- Kompresja musi być włączona:
- Na wszystkich indeksach i partycjach , w tym stertę bazową lub indeks klastrowy.
- Albo
ROW
lubPAGE
kompresja. - Indeksy i partycje mogą używać mieszaniny tych poziomów kompresji. Ważną rzeczą jest to, że nie ma nieskompresowanych indeksów ani partycji.
- Zmiana z
NULL
naNOT NULL
jest niedozwolone . - Następujące zmiany typu liczb całkowitych są obsługiwane:
smallint
nainteger
lubbigint
.integer
dobigint
.smallmoney
namoney
(używa wewnętrznie reprezentacji liczb całkowitych).
- Następujące zmiany typu łańcuchowego i binarnego są obsługiwane:
char(n)
dochar(m)
lubvarchar(m)
nchar(n)
donchar(m)
lubnvarchar(m)
binary(n)
dobinary(m)
lubvarbinary(m)
- Wszystkie powyższe tylko dla
n < m
im != max
- Zmiany sortowania są niedozwolone
Te zmiany mogą dotyczyć tylko metadanych, ponieważ podstawowy układ danych binarnych nie zmienia się, gdy Deskryptor kolumny używany jest format wiersza (stąd potrzeba kompresji). Bez kompresji magazyn wierszy używa oryginalnej FixedVar reprezentacja, która nie może pomieścić tych zmian typu danych o stałej długości bez przepisania fizycznego układu.
Możesz zauważyć, że tinyint
jest pomijany na liście typów liczb całkowitych. Dzieje się tak, ponieważ nie ma znaku, a wszystkie inne typy liczb całkowitych są ze znakiem, więc zmiana tylko metadanych nie jest możliwa. Na przykład wartość 255 może zmieścić się w jednym bajcie dla tinyint
, ale wymaga dwóch bajtów w dowolnym z podpisanych formatów. Podpisane formaty mogą pomieścić od -128 do +127 w jednym bajcie po kompresji.
Przykład liczby całkowitej
Jednym z bardzo przydatnych zastosowań tego ulepszenia jest zmiana typu danych kolumny za pomocą IDENTITY
właściwość.
Załóżmy, że mamy następującą tabelę sterty używającą kompresji wierszy (kompresja strony również zadziała):
DROP TABLE IF EXISTS dbo.Test; GO CREATE TABLE dbo.Test ( id integer IDENTITY NOT NULL, some_value integer NOT NULL ) WITH (DATA_COMPRESSION = ROW);
Dodajmy 5 milionów wierszy danych. To wystarczy, aby było oczywiste (z punktu widzenia wydajności), czy zmiana typu danych kolumny jest operacją tylko na metadanych, czy nie:
WITH Numbers AS ( SELECT n = ROW_NUMBER() OVER (ORDER BY @@SPID) FROM sys.all_columns AS AC1 CROSS JOIN sys.all_columns AS AC2 ORDER BY n OFFSET 0 ROWS FETCH FIRST 5 * 1000 * 1000 ROWS ONLY ) INSERT dbo.Test WITH (TABLOCKX) ( some_value ) SELECT N.n FROM Numbers AS N;
Następnie ponownie wstawimy IDENTITY
aby wyglądało na to, że prawie kończy się nam wartość, która zmieści się w integer
:
DBCC CHECKIDENT ( N'dbo.Test', RESEED, 2147483646 );
Możemy pomyślnie dodać jeszcze jeden wiersz:
INSERT dbo.Test (some_value) VALUES (123456);
Ale próbując dodać kolejny wiersz:
INSERT dbo.Test (some_value) VALUES (7890);
Powoduje wyświetlenie komunikatu o błędzie:
Msg 8115, Poziom 16, Stan 1, Wiersz 1Błąd przepełnienia arytmetycznego podczas konwersji IDENTITY na typ danych int.
Możemy to naprawić, konwertując kolumnę na bigint
:
ALTER TABLE dbo.Test ALTER COLUMN id bigint NOT NULL;
Dzięki ulepszeniom w SQL Server 2016 to polecenie zmienia tylko metadane i kończy się natychmiast. Poprzednia INSERT
instrukcja (ta, która spowodowała błąd przepełnienia arytmetycznego) teraz kończy się pomyślnie.
Ta nowa możliwość nie rozwiązuje wszystkich problemów związanych ze zmianą typu kolumny za pomocą IDENTITY
własność. Nadal będziemy musieli usunąć i odtworzyć wszystkie indeksy w kolumnie, odtworzyć wszelkie odwołujące się klucze obce i tak dalej. To trochę wykracza poza zakres tego postu (choć Aaron Bertrand pisał o tym wcześniej). Możliwość zmiany typu jako operacji tylko na metadanych z pewnością nie zaszkodzi. Dzięki starannemu planowaniu pozostałe wymagane kroki można wykonać tak wydajnie, jak to tylko możliwe, na przykład przy użyciu minimalnie rejestrowanych lub ONLINE
operacje.
Uważaj na składnię
Pamiętaj, aby zawsze określ NULL
lub NOT NULL
podczas zmiany typów danych za pomocą ALTER COLUMN
. Powiedzmy na przykład, że chcieliśmy również zmienić typ danych some_value
kolumna w naszej tabeli testowej z integer NOT NULL
na bigint NOT NULL
.
Kiedy piszemy polecenie, pomijamy NULL
lub NOT NULL
kwalifikator:
ALTER TABLE dbo.Test ALTER COLUMN some_value bigint;
To polecenie kończy się pomyślnie jako zmiana tylko metadanych, ale usuwa również NOT NULL
ograniczenie. Kolumna jest teraz bigint NULL
, co nie było naszym zamiarem. To zachowanie jest udokumentowane, ale łatwo je przeoczyć.
Możemy spróbować naprawić nasz błąd za pomocą:
ALTER TABLE dbo.Test ALTER COLUMN some_value bigint NOT NULL;
To nie zmiana obejmująca tylko metadane. Nie możemy zmienić z NULL
na NOT NULL
(odnieś się do wcześniejszej tabeli, jeśli potrzebujesz odświeżenia warunków). SQL Server będzie musiał sprawdzić wszystkie istniejące wartości, aby upewnić się, że nie ma wartości null. Następnie fizycznie przepisze każdy wiersz tabeli. Oprócz tego, że same w sobie są powolne, działania te generują dużo dzienników transakcji, co może mieć efekt domina.
Na marginesie, ten sam błąd nie jest możliwy w przypadku kolumn z IDENTITY
własność. Jeśli napiszemy ALTER COLUMN
instrukcja bez NULL
lub NOT NULL
w takim przypadku silnik założy, że chodziło nam o NOT NULL
ponieważ właściwość tożsamości nie jest dozwolona w kolumnach dopuszczających wartość null. Nadal dobrym pomysłem jest nie poleganie na takim zachowaniu.
Zawsze określaj NULL
lub NOT NULL
z ALTER COLUMN
.
Składanie
Szczególna ostrożność jest wymagana podczas zmiany kolumny ciągu znaków, która ma sortowanie niezgodne z wartością domyślną dla bazy danych.
Załóżmy na przykład, że mamy tabelę z sortowaniem uwzględniającym wielkość liter i akcent (załóżmy, że domyślna baza danych jest inna):
DROP TABLE IF EXISTS dbo.Test2; GO CREATE TABLE dbo.Test2 ( id integer IDENTITY NOT NULL, some_string char(8) COLLATE Latin1_General_100_CS_AS NOT NULL ) WITH (DATA_COMPRESSION = ROW);
Dodaj 5 milionów wierszy danych:
WITH Numbers AS ( SELECT n = ROW_NUMBER() OVER (ORDER BY @@SPID) FROM sys.all_columns AS AC1 CROSS JOIN sys.all_columns AS AC2 ORDER BY n OFFSET 0 ROWS FETCH FIRST 5 * 1000 * 1000 ROWS ONLY ) INSERT dbo.Test2 WITH (TABLOCKX) ( some_string ) SELECT CONVERT(char(8), N.n) COLLATE Latin1_General_100_CS_AS FROM Numbers AS N;
Podwój długość kolumny ciągu za pomocą następującego polecenia:
ALTER TABLE dbo.Test2 ALTER COLUMN some_string char(16) NOT NULL;
Pamiętaliśmy o określeniu NOT NULL
, ale zapomniałem o sortowaniu innym niż domyślny. SQL Server zakłada, że zamierzaliśmy zmienić sortowanie na domyślne wartości bazy danych (Latin1_General_CI_AS
dla mojej testowej bazy danych). Zmiana sortowania sprawia, że operacja nie jest oparta tylko na metadanych, więc operacja działa przez kilka minut, generując mnóstwo dzienników.
Odtwórz tabelę i dane za pomocą poprzedniego skryptu, a następnie wypróbuj ALTER COLUMN
ponownie, ale podając istniejące, niedomyślne sortowanie jako część polecenia:
ALTER TABLE dbo.Test2 ALTER COLUMN some_string char(16) COLLATE Latin1_General_100_CS_AS NOT NULL;
Zmiana jest teraz wykonywana natychmiast, jako operacja tylko na metadanych. Podobnie jak w przypadku NULL
i NOT NULL
składni, opłaca się być jawnym, aby uniknąć wypadków. Ogólnie jest to dobra rada, nie tylko w przypadku ALTER COLUMN
.
Kompresja
Należy pamiętać, że kompresja musi być wyraźnie określona dla każdego indeksu i osobno dla tabeli bazowej, jeśli jest to sterta. To kolejny przykład, w którym użycie skróconej składni lub skrótów może zapobiec pożądanemu wynikowi.
Na przykład poniższa tabela nie określa jawnej kompresji ani klucza podstawowego, ani definicji indeksu wbudowanego:
CREATE TABLE dbo.Test ( id integer IDENTITY NOT NULL PRIMARY KEY, some_value integer NOT NULL INDEX [IX dbo.Test some_value] ) WITH (DATA_COMPRESSION = PAGE);
PRIMARY KEY
będzie miał przypisaną nazwę, domyślnie CLUSTERED
i być PAGE
sprężony. Wbudowany indeks będzie NONCLUSTERED
i wcale nie skompresowane. Ta tabela nie zostanie włączona dla żadnej z nowych optymalizacji, ponieważ nie wszystkie indeksy i partycje są skompresowane.
O wiele lepsza i bardziej precyzyjna definicja tabeli to:
CREATE TABLE dbo.Test ( id integer IDENTITY NOT NULL CONSTRAINT [PK dbo.Test id] PRIMARY KEY CLUSTERED WITH (DATA_COMPRESSION = PAGE), some_value integer NOT NULL INDEX [IX dbo.Test some_value] NONCLUSTERED WITH (DATA_COMPRESSION = ROW) );
Ta tabela kwalifikuje się do nowych optymalizacji, ponieważ wszystkie indeksy i partycje są skompresowane. Jak wspomniano wcześniej, mieszanie typów kompresji jest w porządku.
Istnieje wiele sposobów na napisanie tego CREATE TABLE
oświadczenie w sposób jednoznaczny, więc istnieje element osobistych preferencji. Ważnym punktem na wynos jest zawsze być jednoznacznym o tym, czego chcesz. Dotyczy to oddzielnego CREATE INDEX
również oświadczenia.
Rozszerzone zdarzenia i flaga śledzenia
Istnieje rozszerzone zdarzenie przeznaczone specjalnie dla nowej kolumny ALTER COLUMN
zawierającej tylko metadane operacje obsługiwane w SQL Server 2016 i nowsze.
Rozszerzone zdarzenie to compressed_alter_column_is_md_only
w Debugowaniu kanał. Jego pola zdarzeń to object_id
, column_id
i is_md_only
(prawda/fałsz).
To zdarzenie wskazuje tylko, czy operacja obejmuje tylko metadane ze względu na nowe możliwości SQL Server 2016. Zmiany kolumn, które przed 2016 r. były tylko metadane, pokażą is_md_only = false
pomimo tego, że nadal zawiera tylko metadane.
Inne rozszerzone zdarzenia przydatne do śledzenia ALTER COLUMN
operacje obejmują metadata_ddl_alter_column
i alter_column_event
, oba w sekcji Analiza kanał.
Jeśli musisz wyłączyć nowe możliwości programu SQL Server 2016 z dowolnego powodu można użyć nieudokumentowanej globalnej (lub startowej) flagi 3618 śledzenia. Ta flaga śledzenia nie jest skuteczna, gdy jest używana na poziomie sesji. Nie ma możliwości określenia flagi śledzenia na poziomie zapytania za pomocą ALTER COLUMN
polecenie.
Końcowe myśli
Możliwość zmiany niektórych typów danych całkowitych o stałej długości za pomocą zmiany samych metadanych jest bardzo pożądanym ulepszeniem produktu. Wymaga to, aby tabela była już w pełni skompresowana, ale i tak staje się to coraz bardziej powszechne. Jest to szczególnie ważne, ponieważ kompresja była włączona we wszystkich edycjach, począwszy od dodatku Service Pack 1 dla SQL Server 2016.
Kolumny typu string o stałej długości są prawdopodobnie znacznie mniej popularne. Niektóre z nich mogą wynikać z nieco nieaktualnych kwestii, takich jak wykorzystanie przestrzeni. Po skompresowaniu kolumny ciągów o stałej długości nie przechowują końcowych spacji, dzięki czemu są tak samo wydajne, jak kolumny ciągów o zmiennej długości z punktu widzenia przechowywania. Przycinanie przestrzeni do manipulacji lub wyświetlania może być denerwujące, ale jeśli dane zwykle zajmują większość maksymalnej długości, typy o stałej długości mogą mieć ważne zalety, nie tylko w odniesieniu do przyznawania pamięci na takie rzeczy, jak sortowanie i mieszanie.
To nie wszystkie dobre wieści z włączoną kompresją. Wspomniałem wcześniej, że SQL Server może czasami dokonać zmiany tylko w metadanych po sprawdzeniu, czy wszystkie istniejące wartości zostaną pomyślnie przekonwertowane na nowy typ. Tak jest w przypadku użycia ALTER COLUMN
zmienić z integer
na smallint
na przykład. Niestety, te operacje nie dotyczą obecnie tylko metadanych dla skompresowanych obiektów.
Podziękowania
Specjalne podziękowania dla Panagiotis Antonopoulos (główny inżynier oprogramowania) i Mirek Sztajno (Senior Program Manager) z zespołu ds. produktu SQL Server za pomoc i wskazówki podczas badania i pisania tego artykułu.
Żadne szczegóły podane w tej pracy nie powinny być traktowane jako oficjalna dokumentacja firmy Microsoft lub oświadczenia dotyczące produktu.