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