如何在不使用 SHRINKFILE 的情况下为操作系统释放磁盘空间?

K09*_*K09 4 sql-server disk-space

我们的服务器管理员要求我释放 SQL 服务器上的磁盘空间,因为 SQL 数据文件所在的驱动器的使用率为 97%。

我发现了一张失控的桌子。它现在包含超过 3 亿行,所以我将设置一项工作以在一夜之间删除这些行。

  • 我有什么选择可以释放磁盘空间?
  • 删除这些行会释放空间供其他表使用吗?

我想避免 SHRINKFILE,因为不推荐。

Ore*_*reo 10

首先,我会通过在您的 SQL Server 上运行一些可用空间诊断来确认服务器管理员看到了什么

/* Get SQL Server Drive Usage Stats */
IF OBJECT_ID('master.sys.dm_os_volume_stats') IS NOT NULL
    SELECT vs.volume_mount_point AS Drive, vs.file_system_type AS [Type]
        ,vs.logical_volume_name AS LogicalName
        ,MAX(CAST(  1.0*vs.total_bytes  / 1073741824 AS DECIMAL(18,2)))AS[Drive Size (GB)]
        ,CAST(SUM(  1.0*size) / 128 / 1000 AS DECIMAL(18,2)) AS [SQL Size (GB)]
        ,MAX(CAST(  1.0*vs.available_bytes/1073741824 AS DECIMAL(18,2)))AS[Free Space (GB)]
        ,MIN(CAST(100.0*vs.available_bytes/vs.total_bytes AS DECIMAL(5,1)))AS[Free Space (%)]
    FROM master.sys.master_files AS f WITH (NOLOCK)
    CROSS APPLY sys.dm_os_volume_stats(f.database_id, f.[file_id]) AS vs
    GROUP BY vs.volume_mount_point, vs.file_system_type, vs.logical_volume_name
    ORDER BY 1
    OPTION (RECOMPILE);

SELECT Drive
    ,ISNULL([ROWS], 0) + ISNULL([LOG], 0)
        + ISNULL([TempROWS], 0) + ISNULL([TempLOG], 0)
        AS 'TotalUsed (MB)'
    ,[ROWS] AS 'Data (MB)'
    ,[LOG] AS 'Logs (MB)'
    ,[TempROWS] AS 'TempData (MB)'
    ,[TempLOG] AS 'TempLogs (MB)'
FROM (
    SELECT LEFT(Physical_Name, 3) 'Drive'
        ,CASE WHEN database_id = 2 THEN 'Temp' ELSE '' END + type_desc 'FileType'
        ,SUM(size) / 128 'SizeMB'
    FROM master.sys.master_files
    GROUP BY LEFT(Physical_Name, 3)
        ,CASE WHEN database_id = 2 THEN 'Temp' ELSE '' END + type_desc
    ) Results
PIVOT(SUM(SizeMB) FOR FileType IN ([ROWS], [LOG], [TempROWS], [TempLOG])) pvt
ORDER BY 1
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)

然后检查问题驱动器上哪些文件最大/最差

/* Get Individual Database Stats */
SELECT mf.database_id 'DB_ID'
    ,DB_NAME(mf.database_id) 'DBName'
    ,d.state_desc 'DBState'
    ,d.recovery_model_desc AS RecoveryModel
    ,CASE WHEN Log_ReUse_Wait_Desc = 'NOTHING' THEN ''
          ELSE Log_ReUse_Wait_Desc END AS LogReUseWait
    ,mf.[File_ID]
    ,mf.NAME 'LogicalName'
    ,mf.type_desc 'Type'
    ,mf.Physical_Name
    ,mf.state_desc 'FileState'
    ,CAST(size / 128.0 + 0.5 AS INT) AS SizeMB
    ,CAST(max_size / 128.0 + 0.5 AS INT) AS MaxSizeMB
    ,CASE is_percent_growth
        WHEN 0 THEN CAST(growth / 128 AS VARCHAR(10)) + ' MB'
        ELSE CAST(growth AS VARCHAR(10)) + ' %' END AS 'AutoGrowth'
    ,CASE 
        WHEN d.STATE <> 6 /* 6 = OFFLINE */
        AND mf.type_desc = 'ROWS'
        AND mf.database_id <> 2 /* not TempDB */
            THEN 'USE '+QUOTENAME(DB_NAME(mf.database_id))
               + ';   DBCC SHRINKFILE(' + CAST(file_id AS VARCHAR(2))
               + ',1,TRUNCATEONLY);
GO'
        ELSE '' END AS 'ShrinkTruncateOnlyCommand'
FROM [master].sys.master_files mf
LEFT JOIN [master].sys.databases d ON d.database_id = mf.database_id
WHERE 1 = 1
    AND d.STATE <> 6 /* 6 = OFFLINE */
    AND mf.database_id > 4 --user DBs only
    --AND mf.database_id <= 4 --system DBs only
ORDER BY SizeMB DESC, DB_NAME(mf.database_id), [file_id]
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)

在你走 SHRINKFILE 路线之前,你应该问自己一些更高层次的问题,比如:

  • 为什么服务器管理员需要该驱动器上的更多可用空间?
  • 如果文件占用了整个驱动器并且在需要时无法增长,我的数据库及其数据会发生什么?(提示:这是坏东西。)
  • 服务器管理员是否应该向我的 SQL Server 框添加空间/驱动器,所以我现在不必这样做SHRINKFILE

由于您发现了一个包含可以(希望)删除数据的表(请与使用该数据的人核对!),请继续删除数据,如果您担心长时间锁定表,可以分批删除

DELETE FROM <table> WHERE <pick the primary keys to delete in this batch>
Run Code Online (Sandbox Code Playgroud)

如果在删除旧的/无用的行后,表中还剩下有用的行,我会重建表的主键

USE YourDB;

SELECT s.[Name] + '.' + t.[Name] AS ObjectName
    ,'ALTER INDEX [' + i.[Name] + '] ON [' + s.[Name] + '].[' + t.[Name] + '] REBUILD'
FROM sys.indexes i
JOIN sys.tables t ON i.object_id = t.object_id
    AND i.type = 1 --clustered index
    AND OBJECTPROPERTY(t.[object_id], 'IsUserTable') = 1
JOIN sys.schemas s ON s.schema_id = t.schema_id
    --AND s.[Name] = 'YourSchema'
    AND t.[Name] = 'YourTable'
ORDER BY 1
Run Code Online (Sandbox Code Playgroud)

...最后缩小数据库文件。我会先尝试使用TRUNCATEONLY选项,因为它比在 DB 文件中重新排列页面的普通 SHRINKFILE 更快/更安全

/* Get Individual Database Stats */
SELECT d.Name DB
    ,mf.type_desc
    ,CASE 
        WHEN d.STATE <> 6 /* 6 = OFFLINE */
        AND mf.type_desc = 'ROWS'
        AND mf.database_id <> 2 /* not TempDB */
            THEN 'USE '+QUOTENAME(DB_NAME(mf.database_id))
               + '; DBCC SHRINKFILE(' + CAST(file_id AS VARCHAR(2))
               + ',1,TRUNCATEONLY);'
        ELSE ''
        END AS 'ShrinkTruncateOnlyCommand'
FROM sys.master_files mf
LEFT JOIN sys.databases d ON d.database_id = mf.database_id
WHERE d.Name = 'YourDB'
    AND mf.type_desc = 'ROWS' --data only
ORDER BY DB_NAME(mf.database_id), [file_id]
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)

  • 细节水平真棒!OP 可能想先备份数据库,以防万一该表很重要。 (2认同)