表結構修改的內幕

經常會人有這樣問我:DBA,我需要修改表結構,你評估一下。往往針對不同的修改,都需要去審覈和給出建議。表結構修改的類型是很多種,下文總結一些基本的點。


並不是任何一個ALTER TABLE操作都需要將所有的數據行修改一遍。ALTER TABLE三種基本的實現方式:

  • 只修改元數據

  • 爲了保證修改的兼容性需要驗證每一行數據,然後只修改元數據

  • 物理性的修改每一行


很多時候,SQL Server只需要通過修改元數據去完成表結構的修改,而不需要修改行數據。比如:

  • 刪除列

  • 新增允許爲NULL的列

  • 不允許爲NULL的列修改成允許爲NULL的列

  • 變長列增加寬度

需要注意:刪除列只修改元數據,也就意味着列所使用的存儲空間不會被回收。可以通過在表上創建或重建聚集索引來回收,或ALTER TABLE REBUILD來回收。

有些修改表結構的操作只需要驗證被修改的數據,然後修改元數據,而不需要修改實際數據。例如:

  • NULL列修改爲非NULL列,需要驗證所有數據是否有NULL值存在。如果有,則修改失敗。

  • 減少變長列的寬度,需要驗證所數據是否符合新的寬度。不符合,則修改失敗。

  • 減少定長列的寬度,也需要驗證所數據是否符合新的寬度。減少定長列的寬度後,只是從邏輯上限制了寫入此列的數據範圍,但是實際佔用仍然是按之前列寬度來計算的。除非重建表,纔會將數據縮減爲新的較窄的列寬度存儲。

如果表的數據量比較大,數據驗證也是一種資源敏感型操作,需要一定的時間。

   

其它的修改表結構的操作,需要物理修改數據。

  既然修改了數據,當然還要寫事務日誌。比如修改列的類型爲其它存儲格式的類型(如int改成varchar)。

還有一種情況需要注意:增加列的寬度時,實際是增加一個新列,將舊列的數據複製到新列,原來列仍然會存在。

CREATE TABLE change
(col1 smallint, col2 char(10), col3 char(5));
go
SELECT c.name AS column_name, column_id, max_inrow_length, pc.system_type_id, leaf_offset
 FROM sys.system_internals_partition_columns pc
 JOIN sys.partitions p
 ON p.partition_id = pc.partition_id
 JOIN sys.columns c
 ON column_id = partition_column_id
 AND c.object_id = p.object_id
WHERE p.object_id=object_id('change');
go
ALTER TABLE change
 ALTER COLUMN col1 int;
 go
 SELECT c.name AS column_name, column_id, max_inrow_length, pc.system_type_id, leaf_offset
 FROM sys.system_internals_partition_columns pc
 JOIN sys.partitions p
 ON p.partition_id = pc.partition_id
 JOIN sys.columns c
 ON column_id = partition_column_id
 AND c.object_id = p.object_id
WHERE p.object_id=object_id('change');
go


在上面中示例中,將列col1從smallint修改爲int後,col1的偏移量從4變成21。(4,6]區間仍然存原來的smallint列,修改後的列從21開始。

刪除列,不會實際刪除數據,只是修改元數據,所以原來列還佔據行容量(最大行容量=8KB-96B(行頭)-36B(行偏移矩陣最小值)=8060)。如果列的寬度總合超過了最大行容量,則會報錯。

CREATE TABLE bigchange
(col1 smallint, col2 char(2000), col3 char(1000));
ALTER TABLE bigchange
 ALTER COLUMN col2 char(3000);
--上面修改成功執行,下面的修改會失敗
Msg 1701, Level 16, State 1, Line 1
Creating or altering table 'bigchange' failed because the minimum row size would be 9009, including 7 bytes of internal overhead. This exceeds the maximum allowable table row size of 8060 bytes.



第一次修改後行容量=2+2000+1000+3000=60002,第二次修改需要的容量:2+2000+1000+3000+3000+7=9009超過了8060,報錯。修改表時不允許超過8060,但是在創建表時可以超過此限制:

CREATE TABLE nochange
(col1 smallint, col2 char(3000), col3 char(1000), col4 char(3000));


增加新列時,新列會使用新的更大的列號(column_id),SQL Server輸出字段時的順序是使用列的邏輯順序,也就是column_id從小到大。所以如果需要按特定順序輸出列,可以:

  • 不要使用Select *,Select中指定需要的列順序

  • 創建一個視圖並在視圖中指定列順序,然後Select * from 視圖

  • 創建一個新表,將原表的數據導入新表,刪除舊錶,交換表名


引用:

  《Microsoft SQL Server Internals》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章