如何释放表的未使用空间

Sae*_*ati 8 sql-server shrink dbcc database-size

这个问题被问了几十次,令我惊讶的是,这样一个简单的要求变得如此困难。然而我无法解决这个问题。

我使用 SQL Server 2014 Express 版,数据库大小限制为 10GB(不是文件组大小,数据库大小)。

我抓取了新闻,并将 HTML 插入到表格中。表的架构是:

Id bigint identity(1, 1) primary key,
Url varchar(250) not null,
OriginalHtml nvarchar(max),
...
Run Code Online (Sandbox Code Playgroud)

数据库容量不足,我收到了 insufficient disk space

当然缩小数据库和文件组没有帮助。DBCC SHRINKDATABASE没有帮助。所以我写了一个简单的应用程序来读取每条记录,去掉一些不需要的部分,OriginalHtml比如 head section 和 aside 和 footer 以只保留主体,现在我在获取顶级表的磁盘使用情况报告时看到这个图像:

在此处输入图片说明

据我了解这张图片,未使用的空间现在占总大小的 50%。也就是说,现在我有 5GB 未使用的空间。但我无法收回它。重建索引没有帮助。该truncateonly选项无济于事,因为据我所知,没有记录被删除,只有每条记录的大小减少。

我被困在这一点上。请帮忙,我该怎么办?

聚集索引在列上Id

这是结果 EXECUTE sys.sp_spaceused @objname = N'dbo.Articles', @updateusage = 'true';

name        rows     reserved     data        index_size   unused
----------- -------- ------------ ----------- ------------ -----------
Articles    112258   8079784 KB   5199840 KB  13360 KB     2866584 KB 
Run Code Online (Sandbox Code Playgroud)

Pau*_*ite 10

在所有条件相同的情况下,压缩大对象 (LOB) 列应该足够了OriginalHTML。您没有在问题中指定聚集索引名称,因此:

ALTER INDEX ALL
ON dbo.Articles
REORGANIZE 
WITH (LOB_COMPACTION = ON);
Run Code Online (Sandbox Code Playgroud)

ALTER INDEX (Transact-SQL)

如果您有聚集索引名称(不仅仅是聚集列),请ALL用该名称替换以上内容。

LOB_COMPACTION选项默认为ON,但明确表示没有坏处。您可能需要REORGANIZE重复运行以完成回收所有未使用的空间。

不幸的是,LOB 数据的组织方式和 LOB 压缩的实现方式意味着无论您运行多少次,此方法可能并不总是能够回收所有未使用的空间。它也可能非常缓慢。

您也可以尝试相关问答中释放未使用空间 SQL Server 表中的方法

如果出于某种原因,上述方法对您不起作用,请将数据导出到文件,截断表,然后重新加载它。有几种方法可以实现这一点,例如bcp 实用程序

例子

下面创建一个包含 10,000 行宽的表:

CREATE TABLE dbo.Test 
(
    c1 bigint IDENTITY NOT NULL, 
    c2 nvarchar(max) NOT NULL,

    CONSTRAINT PK_dbo_Test
        PRIMARY KEY CLUSTERED (c1)
);

-- Load 10,000 wide rows
INSERT dbo.Test WITH (TABLOCKX)
    (c2)
SELECT TOP (10000)
    REPLICATE(CONVERT(nvarchar(max), 'X'), 50000)
FROM master.sys.columns AS C1
CROSS JOIN master.sys.columns AS C2;
Run Code Online (Sandbox Code Playgroud)

我们可以使用sys.dm_db_index_physical_statsDMV查看空间使用情况:

SELECT
    DDIPS.index_id,
    DDIPS.partition_number,
    DDIPS.index_type_desc,
    DDIPS.index_depth,
    DDIPS.index_level,
    DDIPS.page_count,
    DDIPS.avg_page_space_used_in_percent
FROM sys.dm_db_index_physical_stats
(
    DB_ID(),
    OBJECT_ID(N'dbo.Test', N'U'),
    1,
    NULL,
    'DETAILED'
) AS DDIPS
WHERE 
    DDIPS.alloc_unit_type_desc = N'LOB_DATA';
Run Code Online (Sandbox Code Playgroud)

车管所输出

我们现在将 LOB 内容更新为较小的大小(但仍需要行外存储):

-- Change LOB data to a smaller value (that will not move in-row)
UPDATE dbo.Test WITH (TABLOCKX)
SET c2 = REPLICATE(CONVERT(nvarchar(max), 'Y'), 5000);
Run Code Online (Sandbox Code Playgroud)

车管所输出

请注意,一些空间已被回收,但剩余的页面比以前少了很多。

我们可以使用以下方法压缩 LOB 空间:

ALTER INDEX PK_dbo_Test ON dbo.Test 
REORGANIZE 
WITH (LOB_COMPACTION = ON);
Run Code Online (Sandbox Code Playgroud)

车管所输出

这会导致一些压缩和空间节省,但它并不完美。再次运行压缩可能会也可能不会改善这种情况。在我的测试中,无论我重新运行多少次,它都没有。

导出、截断、重新加载

完全从 Management Studio 执行此操作的一种方法涉及使用xp_cmdshell将表数据导出到文件。如果xp_cmdshell当前未启用,则执行以下操作:

-- Enable xp_cmdshell if necessary
EXECUTE sys.sp_configure
    @configname = 'show advanced options',
    @configvalue = 1;

RECONFIGURE;

EXECUTE sys.sp_configure
    @configname = 'xp_cmdshell',
    @configvalue = 1;

RECONFIGURE;
Run Code Online (Sandbox Code Playgroud)

现在我们可以执行导出:

-- Export table
EXECUTE sys.xp_cmdshell
    'bcp Sandpit.dbo.Test out c:\temp\Test.bcp -n -S .\SQL2017 -T';
Run Code Online (Sandbox Code Playgroud)

请注意,您需要更改路径和-S服务器名称,并可能需要提供登录凭据。

我们如何截断表,并使用BULK INSERT以下命令重新加载它:

-- Truncate
TRUNCATE TABLE dbo.Test;

-- Switch to BULK_LOGGED recovery model if currently set to FULL
-- Bulk load
BULK INSERT dbo.Test
FROM 'c:\temp\Test.bcp' 
WITH 
(
    DATAFILETYPE = 'widenative', 
    ORDER (c1), 
    TABLOCK,
    KEEPIDENTITY
);
Run Code Online (Sandbox Code Playgroud)

最后一步是重置身份种子:

-- Check and reseed identity
DBCC CHECKIDENT('dbo.Test', RESEED);
Run Code Online (Sandbox Code Playgroud)

此操作序列通常比 LOB 压缩更快,并且应始终产生最佳结果:

车管所输出

由于一个长期存在的错误,上面的效率并不高:BULK INSERT with IDENTITY 列创建了带有 SORT 的查询计划。那里列出的解决方法是有效的,但如果表非常大,我只会打扰它。

不要忘记删除用于保存导出数据的临时文件。

您当然可以自由使用最方便的批量导出/导入方法。不需要使用xp_cmdshellbcp

补充说明:

  • FILLFACTOR仅适用于索引页。它不会影响行外 LOB 存储(不存储在索引页上)。

  • 行和页压缩不适用于行外存储。

  • 作为替代方法,您可以使用SQL Server 2016 中提供的COMPRESSDECOMPRESS函数显式压缩和解压缩数据。

    对于那些使用 SQL Server 2014(这里就是这种情况)或更早版本(直到 SQL Server 2005)获得COMPRESSDECOMPRESS内置函数提供的相同压缩功能的人来说,一个选项是使用 SQLCLR。由Solomon Rutzky编写的免费版SQL#中提供了执行此操作的预构建函数。的Util_GZipUtil_GUnzip功能应相当于和,分别。而且,任何使用 SQL Server 2012 或更高版本的人都应确保使用 .NET Framework 4.5 或更高版本更新运行 SQL Server 的服务器,以便使用大大改进的压缩算法。COMPRESSDECOMPRESS