除了一些限制条件之外,参见
ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.zh-CHS/tsqlref9/html/f1745145-182d-4301-a334-18f799d361d1.htm关于修改列的限制。对可进行修改的列进行修改时,应注意下面两个问题:
use tempdb;
GO
CREATE TABLE change
(col1 smallint, col2 char(10), col3 char(5));
GO
--查看各字段在页面中的偏移量,leaf_offset字段现在是递增的。
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
为什么会是这个结果?首先,我们看一下记录在表中的存储方式。
对于一个有变长与定长列的记录,在内部会先存储其定长字段的内容,后面存储变长列的字段内容。从上图可以看到,前面的4个字节分别是两个状态位及记录总共定长字段的长度(占用2个字节)。因此第一个定长列是从第4个偏移位开始的,因为col1是smallint占用2个字节,因此col2从第6个字节开始,col2占用10个字节,所以col3从第16个字节开始记录,占用5个字节。我们现在先看一下记录在磁盘中的分布:
SELECT object_name(p.object_id) as tablename,i.name as indexname,
rows,a.type_desc as page_type_desc,
total_pages as pages,first_page
FROM sys.partitions p
JOIN sys.system_internals_allocation_units a
ON p.partition_id = a.container_id
JOIN sys.indexes i
ON p.index_id = i.index_id AND p.object_id = i.object_id
WHERE p.object_id=object_id(N'change')
在我的机器上我得到了如下的结果:
经过转换之后,我得到change表的第一页在第一个数据文件的第120上。
DBCC TRACEON(3604)
GO
DBCC PAGE(tempdb,1,120,3)
GO
下面我们修改列的长度:
--增加字段长度,从smallint改为int
ALTER TABLE change
ALTER COLUMN col1 int;
GO
再次运行上面的脚本,得到下面的结果:
我们看到这时col1从第21个字节开始记录了,col2开始之前的2个字节没有使用到。为了能证实这一点,我们把col1的值先改为3。
UPDATE change SET col1=3 WHERE col1=2;
GO
如果这时,我们查看磁盘中的数据分布情况,发现原有的2个字节内容还在那里。
这时只有能过在表上创建聚集索引之后,才能把这部分浪费的空间真正的释放掉。
CREATE UNIQUE CLUSTERED INDEX pk_change ON change(col1);
GO
这时你再次查看change表的数据分布情况时,发现它的第一页已经不是原来的页了。再次查看它的第一页的内容后得到如下的结果:
如果你细心观察第三个字节的话(记录定长字段总长度)现在也从19(十进制为25)变成了17(十进制为23),这说明已经释放掉了浪费掉的2个字节了。这时,你可以把聚集索引再删除掉,存储方式与有索引时是一样的。
那当你增加字段长度时,同样会面临上面的问题。试验方法同上面的一样,在此不再试验。因此,这就是为什么你在使用图形界面修改字段长度时,SQL Serer会生成如此之多的脚本的原因。它的做法就是新建一个物理表,把原来的记录导入后再删除原表,把新表重命名成原表的名称。最后,为此表创建索引、约束等相关的对象。
更多内容,详见SQLServer存储引擎的数据存储内幕与记录修改的内部工作方式。