在生产机中修改字段长度的注意事项
除了一些限制条件之外,参见
ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.zh-CHS/tsqlref9/html/f1745145-182d-4301-a334-18f799d361d1.htm关于修改列的限制。对可进行修改的列进行修改时,应注意下面两个问题:
- 在对字段长度进行调整时,会占用大量系统资源。因此,不能在生产时间进行调整。
在减小字段长度时,会首先检查原字段中所有的记录是否都在允许的范围内,会进行表扫描。在增大字段长度时,数据库引擎会先创建一个物理表,把原表的记录导入该新建表中,然后删除原表,并重新命名此物理表。再在该表中建立约束、索引等对象,这同样也是一个很耗资源的操作。不但对修改的表有影响,还会对有外键引用的表造成影响。
这个可以在图形管理界面进行修改时,看到相应的脚本。这个更占用系统资源。 - 不管是增大还是减小字段长度都会造成磁盘空间的浪费。
这也是为什么使用图形管理界面时,在增加字段长度时会生成如此多的脚本?
下面的脚本演示了直接通过alter column对字段进行调整时,会带来的磁盘空间浪费情况。直到你重新创建聚集索引时,这些浪费的空间才会被真正重新利用。
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存储引擎的数据存储内幕与记录修改的内部工作方式。