SQL Server 又把 C 盘写满了,这次找到原因了

举报
Lucifer三思而后行 发表于 2026/11/06 14:07:41 2026/11/06
【摘要】 前言 最近处理了一套 SQL Server 测试数据库,Windows 系统盘突然告警,C 盘只剩下不到 200MB。 检查后发现:数据库的数据文件只有 30GB 左右,事务日志文件却增长到了 348GB。 继续排查,发现数据库一直使用 FULL 恢复模式,但已经很长时间没有执行事务日志备份。 最后

👋 我是三笠,一名专注数据库运维与架构的 DBA,平时分享数据库实战经验与技术笔记,个人网站:ora100.com

前言

最近处理了一套 SQL Server 测试数据库,Windows 系统盘突然告警,C 盘只剩下不到 200MB。

检查后发现:数据库的数据文件只有 30GB 左右,事务日志文件却增长到了 348GB。

继续排查,发现数据库一直使用 FULL 恢复模式,但已经很长时间没有执行事务日志备份。

最后通过调整恢复模式、截断日志和收缩文件,成功释放了 300 多 GB 磁盘空间,顺便还做了一个自动维护任务。

本文记录一下整个处理过程。

C 盘只剩 199MB,空间被谁占用了?

接到告警后,先登录 Windows 服务器查看磁盘空间。

查看磁盘使用情况:

磁盘 总容量 剩余空间
C 盘 499GB 199MB
D 盘 299GB 52.2GB

可以发现 C 盘基本已经满了。

使用 TreeSize 扫描磁盘,发现 SQL Server 安装目录占用了大量空间。

继续往下看,其中一个数据库的 LDF 文件达到了 348.6GB,而对应的 MDF 数据文件只有 30.4GB,一个事务日志文件,竟然比数据文件大了十多倍。

这显然不太正常。

日志文件为什么会增长到 348GB?

先连接 SQL Server,检查所有数据库的事务日志使用情况:

DBCC SQLPERF(LOGSPACE);

重点关注异常数据库:

Database Name Log Size (MB) Log Space Used (%)
APPDB 357032.1 99.9821

日志文件已经达到约 348GB,内部使用率接近 100%。

说明当前不仅 LDF 文件很大,内部也几乎没有可复用的空间。

接着检查数据库的恢复模式和日志重用等待原因:

SELECT
    name,
    recovery_model_desc,
    log_reuse_wait_desc,
    state_desc
FROM sys.databases
WHERE name = 'APPDB';

结果:

name    recovery_model_desc  log_reuse_wait_desc  state_desc
APPDB   FULL                 LOG_BACKUP            ONLINE

这里重点看两个字段:

  • recovery_model_desc = FULL:说明数据库使用完整恢复模式。
  • log_reuse_wait_desc = LOG_BACKUP:说明当前需要事务日志备份才能推进日志截断。

看到这里,基本有方向了。

再查一下最近一次事务日志备份是什么时候:

SELECT
    database_name,
    MAX(backup_finish_date) AS LastLogBackup
FROM msdb.dbo.backupset
WHERE database_name = 'APPDB'
  AND type = 'L'
GROUP BY database_name;

结果发现,上一次记录的事务日志备份已经是两年多以前。

也就是说,这套数据库长期处于 FULL 恢复模式,却没有正常执行事务日志备份。

这就能解释为什么日志一直增长。

在 FULL 恢复模式下,事务日志通常需要通过日志备份推进截断。如果长期不备份,已提交事务产生的日志记录也可能一直无法重用。

随着业务不断写入,LDF 文件就会持续增长。

当然,FULL 模式本身并不会导致日志无限增长,真正的问题是恢复策略与备份策略没有配套。

测试库不需要时间点恢复,怎么处理?

确认这是一套测试数据库后,处理思路就比较明确了。

这套数据库没有时间点恢复要求,也没有依赖原有日志备份链的高可用或日志传送配置。因此,决定将数据库从 FULL 恢复模式调整为 SIMPLE。

如果是生产数据库,这一步就不能这么随意。

生产库通常应该先确认日志备份链、恢复目标和日志重用阻塞原因,再决定处理方式。

首先修改恢复模式:

USE master;
GO

ALTER DATABASE [APPDB]
SET RECOVERY SIMPLE;
GO

执行成功后,切换到目标数据库执行 CHECKPOINT:

USE [APPDB];
GO

CHECKPOINT;
GO

这里有一点需要注意:

日志截断不等于日志文件收缩。

日志截断只是让不再需要的日志记录所占用的空间可以重新利用,并不会自动将 LDF 文件缩小。

如果希望把空间还给操作系统,还需要执行 SHRINKFILE。

348GB 的 LDF,直接收缩到 10GB

先确认日志文件的逻辑名称:

USE [APPDB];
GO

SELECT
    name,
    type_desc,
    physical_name,
    size * 8.0 / 1024 AS SizeMB
FROM sys.database_files;

查询结果:

name type_desc SizeMB
APPDB_Data ROWS 31112
APPDB_Log LOG 357032

确认之后,执行收缩:

USE [APPDB];
GO

DBCC SHRINKFILE
(
    N'APPDB_Log',
    10240
);
GO

这里的 10240 单位是 MB,也就是将日志文件的目标大小设置为 10GB。

执行完成后,再次检查日志空间:

DBCC SQLPERF(LOGSPACE);

结果:

Database Name Log Size (MB) Log Space Used (%)
APPDB 10275.94 0.0607

可以看到,日志文件从约 348GB 缩小到了 10GB,日志使用率也从 99.98% 降到了 0.06%。

这次收缩比较顺利,没有遇到日志末尾 VLF 无法释放的问题。

不过需要说明,SHRINKFILE 的目标大小并不保证一定能够达到。如果仍有活动日志或文件末尾存在无法释放的 VLF,就可能无法继续收缩。

另一套数据库,也存在同样的问题

处理完 APPDB 后,又检查了一下其他数据库。

发现还有一套 TESTDB,日志文件也增长到了约 36.8GB。

查询结果显示:

Recovery Model : FULL
Log Reuse Wait : LOG_BACKUP
Log Used       : 99.90%

和 APPDB 的情况基本一致。

确认同样属于不需要时间点恢复的测试数据库后,采用相同方式处理:

USE master;
GO

ALTER DATABASE [TESTDB]
SET RECOVERY SIMPLE;
GO

USE [TESTDB];
GO

CHECKPOINT;
GO

DBCC SHRINKFILE
(
    N'TESTDB_Log',
    10240
);
GO

执行完成后,TESTDB 的日志也缩小到了约 10GB。

至此,两套数据库的日志空间都处理完成。

日志收缩完成,还要调整自动增长

空间释放出来后,还需要检查日志文件的自动增长配置:

SELECT
    name,
    size * 8.0 / 1024 AS SizeMB,
    growth * 8.0 / 1024 AS GrowthMB,
    is_percent_growth
FROM sys.master_files
WHERE database_id = DB_ID('APPDB')
  AND type_desc = 'LOG';

检查发现,原来的日志自动增长大小为 64MB。

对于这套测试环境,决定将其调整为固定 512MB:

ALTER DATABASE [APPDB]
MODIFY FILE
(
    NAME = N'APPDB_Log',
    FILEGROWTH = 512MB
);
GO

另一套 TESTDB 也做了相同调整,这里没有采用百分比增长,而是固定增长大小。

不过,512MB 并不是所有 SQL Server 数据库的标准配置,实际还需要结合日志生成量、业务高峰和磁盘容量确定。

每次手动处理太麻烦,顺手做个自动维护任务

这次虽然把空间释放出来了,但考虑到是测试环境,后面还可能有批量导入、数据清理或者其他测试操作。

如果日志文件再次增长到几十 GB,总不能每次都登录服务器手动处理。

于是利用 SQL Server Agent 做了一个简单的定时维护任务。

我没有选择每天强制收缩,而是增加了一些判断条件:

检查项 条件
数据库范围 指定测试数据库
数据库状态 ONLINE
恢复模式 SIMPLE
日志文件大小 超过 20GB
日志使用率 不超过 10%
日志重用等待 NOTHING
收缩目标 10GB
执行周期 每周日 03:00

只有满足条件,才执行收缩,不满足条件就直接跳过。

这样可以避免每周无意义地收缩日志文件,也减少日志文件反复收缩、增长带来的开销。

将维护逻辑封装成存储过程后,由 SQL Server Agent 调用:

EXEC master.dbo.usp_DBA_LogMaintenance;
GO

然后创建定时任务:

Job Name:
DBA_Weekly_Log_Maintenance

Schedule:
Every Sunday 03:00

Target:
APPDB / TESTDB

创建完成后,手动测试任务。

第一次执行时,SQL Server 返回:

Msg 22022

SQLServerAgent is not currently running
so it cannot be notified of this action.

原来 SQL Server Agent 服务没有启动。

通过 Windows 服务管理器启动 SQL Server Agent,并将启动类型调整为 Automatic。

再次执行:

USE msdb;
GO

EXEC dbo.sp_start_job
    @job_name = N'DBA_Weekly_Log_Maintenance';
GO

这次返回:

Job started successfully.

最后检查任务执行历史:

SELECT TOP 20
    j.name AS JobName,
    h.step_id,
    h.run_status,
    h.message
FROM msdb.dbo.sysjobhistory h
JOIN msdb.dbo.sysjobs j
    ON h.job_id = j.job_id
WHERE j.name = 'DBA_Weekly_Log_Maintenance'
ORDER BY h.instance_id DESC;

Job 和 Step 均显示 SUCCEEDED,说明 SQL Server Agent 已经能够正常调度维护过程。

需要注意,任务成功不代表每次都实际发生了收缩。本次测试时日志文件已经低于 20GB,因此维护过程会按条件跳过。

最后,磁盘空间恢复了多少?

整个处理完成后,再次查看 Windows 磁盘空间:

检查项 处理前 处理后
C 盘剩余空间 199MB 336GB
APPDB 日志大小 348.7GB 约 10GB
TESTDB 日志大小 36.8GB 约 10GB
APPDB 日志使用率 99.98% 0.06%
TESTDB 日志使用率 99.90% 0.06%
数据库状态 ONLINE ONLINE

整个处理过程没有停止 SQL Server 服务,也没有直接删除 LDF 文件。

C 盘从几乎没有剩余空间,恢复到了数百 GB 可用空间。

最后再说两句。

这次问题的关键,其实不是 SHRINKFILE,而是前面查到的 LOG_BACKUP。

LDF 文件很大,只是现象;日志为什么不能重用,才是需要先搞清楚的问题。

另外,定时收缩只是这套测试环境的兜底方案,并不代表 SQL Server 应该定期收缩日志。正常情况下,还是应该根据恢复要求配置备份策略,并合理规划日志容量。

写文章之外,我也在持续完善自己的数据库学习与运维平台 ORA100。

平时整理的数据库实战案例、常用 SQL、运维脚本和实用工具,都可以在这里找到。

网站地址:ora100.com

感兴趣的话,可以来看看。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。