【技术分享】金蝶云星空数据库深度瘦身:460G 压缩至 160G 实战

1. 为什么要做这次优化?

随着金蝶云星空系统(产品版本号:9.0.0.20240529)运行时间的增长,数据库由于索引碎片、冗余日志以及未压缩的数据表,会导致空间占用虚高。这不仅浪费磁盘空间,还会拖慢备份速度和查询效率。

实战数据:

  • 瘦身前备份: 479 GB
  • 瘦身后备份: 164 GB
  • 释放空间: ~315 GB (约 65%)

2. 核心代码(T-SQL)

该脚本由深圳周少斌原创,逻辑涵盖了索引重建、行压缩、统计信息更新及物理收缩。

SQL

/* 执行建议:由于涉及全库扫描与收缩,耗时较长并会影响性能,请务必在系统空闲时执行。
作者:深圳周少斌
*/
SET NOCOUNT ON 
declare @StepStartTime datetime,@GlobalStartTime datetime 
select @StepStartTime=getdate(),@GlobalStartTime=getdate()
declare @TableName TABLE(name nvarchar(100) NOT NULL,UNIQUE CLUSTERED(name)) 
declare @TableNameForLoop TABLE(name nvarchar(100) NOT NULL,UNIQUE CLUSTERED(name)) 
declare @name nvarchar(100),@SqlStr nvarchar(max) 

-- 初始化表清单,排除金蝶系统的临时表 (TMP开头)
Insert into @TableName SELECT name from sysobjects where xtype='U' and (name not like'TMP%') 

-- 第1步:重建并开启行压缩索引(ROW_COMPRESSION)
Insert into @TableNameForLoop SELECT * from @TableName 
WHILE EXISTS(SELECT top 100 percent name FROM @TableNameForLoop order by Name)
begin  
  select top 1 @name=name,@SqlStr='' FROM @TableNameForLoop order by Name 
  delete from @TableNameForLoop WHERE Name=@name;
  set @SqlStr='alter index all on ['+@name+ '] rebuild WITH (DATA_COMPRESSION=ROW);' 
  BEGIN TRY exec (@SqlStr) END TRY BEGIN CATCH END CATCH 
end
print '第1步执行时长:'+Convert(Nvarchar(50),Convert(decimal(18,2),ROUND(Convert(decimal(38,20),(datediff(ms,@StepStartTime,getdate())))/60000,2)))+'分钟'; 
set @StepStartTime=getdate();

-- 第2步:压缩数据表
Insert into @TableNameForLoop SELECT * from @TableName 
WHILE EXISTS(SELECT top 100 percent name FROM @TableNameForLoop order by Name)
begin  
  select top 1 @name=name,@SqlStr='' FROM @TableNameForLoop order by Name 
  delete from @TableNameForLoop WHERE Name=@name;
  set @SqlStr='alter table ['+@name+'] rebuild WITH (DATA_COMPRESSION=ROW);' 
  BEGIN TRY exec (@SqlStr) END TRY BEGIN CATCH END CATCH 
end
print '第2步执行时长:'+Convert(Nvarchar(50),Convert(decimal(18,2),ROUND(Convert(decimal(38,20),(datediff(ms,@StepStartTime,getdate())))/60000,2)))+'分钟'; 

-- 第3步:更新统计信息,优化执行计划
Insert into @TableNameForLoop SELECT * from @TableName 
WHILE EXISTS(SELECT top 100 percent name FROM @TableNameForLoop order by Name)
begin  
  select top 1 @name=name,@SqlStr='' FROM @TableNameForLoop order by Name 
  delete from @TableNameForLoop WHERE name=@name;
  set @SqlStr='UPDATE STATISTICS ['+@name+'];' 
  BEGIN TRY exec (@SqlStr) END TRY BEGIN CATCH END CATCH 
end
print '第3步执行时长:'+Convert(Nvarchar(50),Convert(decimal(18,2),ROUND(Convert(decimal(38,20),(datediff(ms,@StepStartTime,getdate())))/60000,2)))+'分钟'; 
set @StepStartTime=getdate();

-- 第4步:收缩数据库,归还硬盘空间
begin
set @SqlStr=N'
  USE [master] 
    ALTER DATABASE ['+DB_NAME()+'] SET RECOVERY SIMPLE WITH NO_WAIT; -- 改为简单模式
    ALTER DATABASE ['+DB_NAME()+'] SET RECOVERY SIMPLE 

  USE ['+DB_NAME()+'] 
    declare @DataFileName nvarchar(128),@LogFileName nvarchar(128) 
    SELECT @DataFileName=FILE_NAME(1), @LogFileName=FILE_NAME(2)     
    DBCC SHRINKFILE (@DataFileName,20); -- 收缩数据文件
    DBCC SHRINKFILE (@LogFileName,20) WITH NO_INFOMSGS; '
exec (@SqlStr);
end
print '第4步执行时长:'+Convert(Nvarchar(50),Convert(decimal(18,2),ROUND(Convert(decimal(38,20),(datediff(ms,@StepStartTime,getdate())))/60000,2)))+'分钟'; 
set @StepStartTime=getdate();

-- 第5步:二次索引维护(解决收缩导致的物理碎片)
Insert into @TableNameForLoop SELECT * from @TableName 
WHILE EXISTS(SELECT top 100 percent name FROM @TableNameForLoop order by Name)
begin  
  select top 1 @name=name,@SqlStr='' FROM @TableNameForLoop order by Name 
  delete from @TableNameForLoop WHERE Name=@name;
  set @SqlStr='alter index all on ['+@name+ '] rebuild WITH (DATA_COMPRESSION=ROW);' 
  BEGIN TRY exec (@SqlStr) END TRY BEGIN CATCH END CATCH 
end
print '第5步执行时长:'+Convert(Nvarchar(50),Convert(decimal(18,2),ROUND(Convert(decimal(38,20),(datediff(ms,@StepStartTime,getdate())))/60000,2)))+'分钟'; 

print '全程总耗时:'+Convert(Nvarchar(50),Convert(decimal(18,2),ROUND(Convert(decimal(38,20),(datediff(ms,@GlobalStartTime,getdate())))/60000,2)))+'分钟。';
SET NOCOUNT OFF 

第1步执行时长: 197.38 分钟 (深度压缩核心)
第2步执行时长: 54.89 分钟
第3步执行时长: 66.15 分钟
第4步执行时长: 0.08 分钟
第5步执行时长: 91.93 分钟
全程总耗时: 355.54 分钟
完成时间: 2025-12-14 04:17:31

3. 执行日志与总结

在 2025 年 12 月 13 日晚间的执行中,全过程历时约 6 小时(355.54 分钟):

  • 第1步耗时最长(197 分钟),因为它在重写所有索引并进行行级压缩。
  • 第4步收缩虽然耗时极短(0.08 分钟),但却直接释放了磁盘物理空间。
  • 第5步是性能的关键,修复了收缩带来的碎片化问题。

注意: 脚本执行期间会将数据库设为 RECOVERY SIMPLE 模式。若您原本使用的是完整恢复模式,请在执行后记得切换回来并立即做一次全备份。

青兴

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注