ALTER TABLE ... ALTER COLUMN
kommandot är mycket kraftfullt. Du kan använda den för att ändra en kolumns datatyp, längd, precision, skala, nollbarhet, sortering ... och många andra saker förutom.
Det är säkert bekvämare än alternativet:Skapa en ny tabell och migrera data varje gång en ändring är nödvändig. Ändå finns det bara så mycket som kan göras för att dölja den underliggande komplexiteten. Tillsammans med ett stort antal restriktioner för vad som ens är möjligt med detta kommando, finns det alltid frågan om prestanda.
I slutändan lagras tabeller som en sekvens av byte med viss metadata någon annanstans i systemet för att beskriva vad var och en av dessa byte betyder och hur de relaterar till var och en av tabellens olika kolumner. När vi ber SQL Server att ändra någon aspekt av en kolumns definition måste den kontrollera att befintliga data är kompatibla med den nya definitionen. Det måste också avgöra om den nuvarande fysiska layouten behöver ändras.
Beroende på typen av ändring och konfigurationen av databasen visas en ALTER COLUMN
kommandot måste utföra en av följande åtgärder:
- Ändra metadata endast i systemtabeller.
- Kontrollera all befintlig data för kompatibilitet och ändra sedan metadata.
- Skriv om några eller alla lagrade data för att matcha den nya definitionen.
Alternativ 1 representerar det idealiska fallet ur prestationssynpunkt. Det kräver bara några få ändringar av systemtabellerna och en minimal mängd loggning. Operationen kommer fortfarande att kräva en restriktiv schemaändring Sch-M
lås, men själva metadataändringarna kommer att slutföras mycket snabbt, oavsett storleken på tabellen.
Ändringar endast för metadata
Det finns ett antal specialfall att se upp för, men som en allmän sammanfattning kräver följande åtgärder endast ändringar av metadata:
- Gå från
NOT NULL
tillNULL
för samma datatyp. - Öka den maximala storleken för en
varchar
,nvarchar
, ellervarbinary
kolumn (förutommax
).
Förbättringar i SQL Server 2016
Ämnet för det här inlägget är de ytterligare ändringar som är aktiverade för enbart metadata från SQL Server 2016 och framåt . Inga ändringar av syntax behövs och inga konfigurationsinställningar behöver ändras. Du får dessa odokumenterade förbättringar gratis.
De nya funktionerna är inriktade på en delmängd av fast längd datatyper. De nya förmågorna gäller för radbutikstabeller under följande omständigheter:
- Komprimering måste vara aktiverad:
- På alla index och partitioner , inklusive bashögen eller klustrade index.
- Antingen
ROW
ellerPAGE
komprimering. - Index och partitioner kan använda en blandning av dessa kompressionsnivåer. Det viktiga är att det inte finns några okomprimerade index eller partitioner.
- Ändras från
NULL
tillNOT NULL
är inte tillåtet . - Följande heltalstyp ändras stöds:
smallint
tillinteger
ellerbigint
.integer
tillbigint
.smallmoney
tillmoney
(använder heltalsrepresentation internt).
- Följande ändringar av sträng och binär typ stöds:
char(n)
tillchar(m)
ellervarchar(m)
nchar(n)
tillnchar(m)
ellernvarchar(m)
binary(n)
tillbinary(m)
ellervarbinary(m)
- Allt ovanstående endast för
n < m
ochm != max
- Sorteringsändringar är inte tillåtna
Dessa ändringar kan endast vara metadata eftersom den underliggande binära datalayouten inte ändras när kolumnbeskrivning radformat används (därav behovet av komprimering). Utan komprimering använder radlagring den ursprungliga FixedVar representation, som inte kan hantera dessa datatypsändringar med fast längd utan att skriva om den fysiska layouten.
Du kanske märker att tinyint
utelämnas från listan med heltalstyper. Detta beror på att den är osignerad, medan de andra heltalstyperna alla är signerade, så en ändring av enbart metadata är inte möjlig. Till exempel kan ett värde på 255 rymmas i en byte för tinyint
, men kräver två byte i något av de signerade formaten. De signerade formaten kan hålla -128 till +127 i en byte när de komprimeras.
Heltalsexempel
En mycket praktisk tillämpning av denna förbättring är att ändra datatypen för en kolumn med IDENTITY
egendom.
Säg att vi har följande heaptabell med radkomprimering (sidkomprimering skulle också fungera):
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);
Låt oss lägga till 5 miljoner rader med data. Detta kommer att räcka för att göra det uppenbart (ur prestandasynpunkt) huruvida att ändra kolumndatatypen är en operation av endast metadata eller inte:
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;
Därefter kommer vi att se om IDENTITY
för att få det att verka som om vi nästan är på väg att ta slut på värden som passar i ett integer
:
DBCC CHECKIDENT ( N'dbo.Test', RESEED, 2147483646 );
Vi kan lägga till ytterligare en rad framgångsrikt:
INSERT dbo.Test (some_value) VALUES (123456);
Men försöker lägga till en annan rad:
INSERT dbo.Test (some_value) VALUES (7890);
Resultatet blir ett felmeddelande:
Msg 8115, Level 16, State 1, Line 1Aritmetiskt spillfel vid konvertering av IDENTITY till datatyp int.
Vi kan fixa det genom att konvertera kolumnen till bigint
:
ALTER TABLE dbo.Test ALTER COLUMN id bigint NOT NULL;
Tack vare förbättringarna i SQL Server 2016 ändrar detta kommando endast metadata , och slutförs omedelbart. Den föregående INSERT
satsen (den som orsakade det aritmetiska spillfelet) slutförs nu framgångsrikt.
Den här nya förmågan löser inte alla problem kring att ändra kolumnstyp med IDENTITY
fast egendom. Vi kommer fortfarande att behöva släppa och återskapa alla index på kolumnen, återskapa eventuella refererande främmande nycklar och så vidare. Det är lite utanför ramen för detta inlägg (även om Aaron Bertrand har skrivit om det tidigare). Att kunna ändra typen som enbart metadata-operation skadar verkligen inte. Med noggrann planering kan de andra stegen som krävs göras så effektiva som möjligt, till exempel genom att använda minimalt loggade eller ONLINE
operationer.
Var försiktig med syntax
Se till att alltid ange NULL
eller NOT NULL
när du ändrar datatyper med ALTER COLUMN
. Säg till exempel att vi också ville ändra datatypen för some_value
kolumn i vår testtabell från integer NOT NULL
till bigint NOT NULL
.
När vi skriver kommandot utelämnar vi NULL
eller NOT NULL
kval:
ALTER TABLE dbo.Test ALTER COLUMN some_value bigint;
Det här kommandot slutförs framgångsrikt som en ändring av endast metadata, men tar också bort NOT NULL
begränsning. Kolumnen är nu bigint NULL
, vilket inte var vad vi hade tänkt oss. Detta beteende är dokumenterat, men det är lätt att förbise.
Vi kan försöka åtgärda vårt misstag med:
ALTER TABLE dbo.Test ALTER COLUMN some_value bigint NOT NULL;
Detta är inte en ändring av endast metadata. Vi får inte ändra från NULL
till NOT NULL
(se tillbaka till den tidigare tabellen om du behöver en uppfräschning om villkoren). SQL Server måste kontrollera alla befintliga värden för att säkerställa att inga nollvärden finns. Den kommer sedan fysiskt att skriva om varje rad av bordet. Förutom att de är långsamma i sig, genererar dessa åtgärder en hel del transaktionsloggar, vilket kan få utslagseffekter.
Som en sidoanteckning är samma misstag inte möjligt för kolumner med IDENTITY
fast egendom. Om vi skriver en ALTER COLUMN
uttalande utan NULL
eller NOT NULL
i så fall antar motorn att vi menade NOT NULL
eftersom identitetsegenskapen inte är tillåten på nullbara kolumner. Det är fortfarande en bra idé att inte lita på detta beteende.
Ange alltid NULL
eller NOT NULL
med ALTER COLUMN
.
Sortering
Särskild försiktighet krävs när du ändrar en strängkolumn som har en sortering som inte matchar standardinställningen för databasen.
Säg till exempel att vi har en tabell med en skiftläges- och accentkänslig sortering (antag att databasens standard är annorlunda):
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);
Lägg till 5 miljoner rader med data:
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;
Dubbla längden på strängkolumnen med följande kommando:
ALTER TABLE dbo.Test2 ALTER COLUMN some_string char(16) NOT NULL;
Vi kom ihåg att ange NOT NULL
, men glömde bort den icke-standardiserade sorteringen. SQL Server antar att vi tänkte ändra sortering till databasstandarden (Latin1_General_CI_AS
för min testdatabas). Genom att ändra sortering förhindrar åtgärden att endast vara metadata, så åtgärden körs i flera minuter, vilket genererar högar av loggar.
Återskapa tabellen och data med det föregående skriptet och försök sedan ALTER COLUMN
kommandot igen, men ange den befintliga icke-standardsorteringen som en del av kommandot:
ALTER TABLE dbo.Test2 ALTER COLUMN some_string char(16) COLLATE Latin1_General_100_CS_AS NOT NULL;
Ändringen slutförs nu omedelbart, som enbart metadata. Som med NULL
och NOT NULL
syntax, lönar det sig att vara tydlig för att undvika olyckor. Detta är ett bra råd i allmänhet, inte bara för ALTER COLUMN
.
Kompression
Var medveten om att komprimering måste anges uttryckligen för varje index, och separat för bastabellen om det är en hög. Detta är ytterligare ett exempel där användning av förkortad syntax eller genvägar kan förhindra det önskade resultatet.
Följande tabell anger till exempel inte explicit komprimering för varken primärnyckeln eller in-line indexdefinition:
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
kommer att ha ett namn tilldelat, standard till CLUSTERED
,och vara PAGE
komprimerad. In-line-indexet kommer att vara NONCLUSTERED
och inte alls komprimerad. Den här tabellen kommer inte att aktiveras för någon av de nya optimeringarna eftersom inte alla index och partitioner är komprimerade.
En mycket bättre och mer explicit tabelldefinition skulle vara:
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) );
Den här tabellen kommer att kvalificera sig för de nya optimeringarna eftersom alla index och partitioner är komprimerade. Som nämnts tidigare går det bra att blanda komprimeringstyper.
Det finns en mängd olika sätt att skriva denna CREATE TABLE
uttalande på ett explicit sätt, så det finns ett inslag av personlig preferens. Den viktiga punkten är att alltid vara tydlig om vad du vill. Detta gäller separat CREATE INDEX
uttalanden också.
Utökade händelser och spårningsflagga
Det finns en utökad händelse specifikt för den nya ALTER COLUMN
endast för metadata operationer som stöds i SQL Server 2016 och framåt.
Den utökade händelsen är compressed_alter_column_is_md_only
i Felsökning kanal. Dess händelsefält är object_id
, column_id
och is_md_only
(sant/falskt).
Den här händelsen indikerar bara om en operation är enbart metadata på grund av de nya funktionerna i SQL Server 2016. Kolumnändringar som endast var metadata före 2016 kommer att visa is_md_only = false
trots att det fortfarande bara är metadata.
Andra utökade händelser som är användbara för att spåra ALTER COLUMN
operationer inkluderar metadata_ddl_alter_column
och alter_column_event
, båda i Analytisk kanal.
Om du behöver inaktivera de nya SQL Server 2016-funktionerna av någon anledning, odokumenterad global (eller start-) spårningsflagga 3618 kan användas. Denna spårningsflagga är inte effektiv när den används på sessionsnivå. Det finns inget sätt att ange en spårningsflagga på frågenivå med en ALTER COLUMN
kommando.
Slutliga tankar
Att kunna ändra vissa heltalsdatatyper med fast längd med en ändring av enbart metadata är en mycket välkommen produktförbättring. Det kräver visserligen att bordet redan är helt komprimerat, men det börjar bli mer vanligt ändå. Detta gäller särskilt eftersom komprimering var aktiverad i alla utgåvor från och med SQL Server 2016 Service Pack 1.
Kolumner av strängtyp med fast längd är förmodligen mycket mindre vanliga. En del av detta kan bero på något inaktuella överväganden som utrymmesanvändning. När de är komprimerade lagrar inte strängkolumner med fast längd efterföljande ämnen, vilket gör dem lika effektiva som strängkolumner med variabel längd ur lagringssynpunkt. Det kan vara irriterande att trimma utrymmen för manipulering eller visning, men om data vanligtvis upptar större delen av maxlängden kan typer av fast längd ha viktiga fördelar, inte minst vad gäller minnesanslag för saker som sortering och hash.
Det är inte alla goda nyheter med komprimering aktiverad. Jag nämnde tidigare att SQL Server ibland kan utföra en ändring av enbart metadata efter att ha kontrollerat att alla befintliga värden kommer att konverteras framgångsrikt till den nya typen. Detta är fallet när du använder ALTER COLUMN
för att ändra från integer
till smallint
till exempel. Tyvärr är dessa operationer för närvarande inte enbart metadata för komprimerade objekt.
Bekräftelser
Särskilt tack till Panagiotis Antonopoulos (Principal Software Engineer) och Mirek Sztajno (Senior Program Manager) från SQL Server-produktteamet för deras hjälp och vägledning under forskningen och skrivningen av denna artikel.
Ingen av detaljerna i detta arbete ska betraktas som officiell Microsoft-dokumentation eller produktutlåtanden.