SQL单值列610万行存储疑问:修改后空间节省为何有限?
问题背景
我有一个SQL表,通过以下SQL语句查询表大小与行数:
SELECT t.name AS TableName, s.name AS SchemaName, p.rows, CAST(ROUND(((SUM(a.total_pages) * 8) / 1024.00), 2) AS NUMERIC(36, 2)) AS TotalSpaceMB, getdate() as [Time] into #temp FROM sys.tables t INNER JOIN sys.indexes i ON t.object_id = i.object_id INNER JOIN sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id LEFT OUTER JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.name NOT LIKE 'dt%' AND t.is_ms_shipped = 0 AND i.object_id > 255 GROUP BY t.name, s.name, p.rows ORDER BY TotalSpaceMB DESC, t.name select * from #temp
查询结果显示目标表有610万行,表大小2384.77MB。该表某列的610万行均为33字符的相同值,我将数据复制到新表后执行如下修改:
select * into table_of_interest_temp from table_of_interest update table_of_interest_temp set Key = 1 where Key = '6cd191449a764432ac3e-ab1c12f900dd'
重新运行表大小查询后,新表仅减少6.31MB,变为2378.46MB。我原本预期能节省更多空间,因为修改另一含约2000个不同值的列时,空间节省效果显著,请问这一现象的原因是什么?
原因分析
- 列数据类型的限制:如果
Key列是固定长度类型(比如CHAR(33)),不管实际存储的字符长度是多少,都会占用定义的33字符空间,修改为1后单条记录的存储空间没有变化;如果是可变长度类型(比如VARCHAR(33)),单条记录确实能省空间,但SQL Server不会自动释放数据页中的空闲空间,这些空闲空间仍会被计入表的总分配空间,只有当页被完全清空时才会回收。 - 索引的差异影响:之前修改的含2000个不同值的列大概率带有索引,修改后重复值增多,索引的叶节点会因为重复值的高压缩率大幅减少空间占用;而这次的
Key列如果没有索引,或者原本全相同值的索引已经被压缩到极致,修改后索引空间几乎无变化,自然总空间节省有限。 - 页压缩的特性:如果表开启了页压缩,原来全相同的33字符值已经被压缩到最优状态——页压缩会把重复值只存储一次,其余行用引用指向该值。改成
1后,压缩率提升的空间极小,因此整体空间节省微乎其微。而之前的列修改前重复率低,修改后重复率大幅提升,压缩效果显著,所以空间节省明显。 - 空间统计的逻辑:通过
sys.allocation_units查询到的是表已分配的空间,而非实际使用的空间。SELECT * INTO创建的新表继承了原表的页分配,修改后的数据页并未被重新整理,已分配的页即使有空余空间,也会被计入总大小,不会立即减少。
内容的提问来源于stack exchange,提问作者frank
相关产品推荐
相关产品推荐

