经常会人有这样问我: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》