数据库存储空间使用率是我们运维中需要必不可少的一个资源监控点,甚至说它的监控要比CPU、内存、IO使用率还要重要。当SQL Server实例磁盘使用率达到高水位时,我们首先需要判断究竟是哪部分空间的空间占用最大,然后再进行对应的措施考虑是否可以释放空闲空间,本章考虑的是当SQL Server事务日志空间使用率较高时,我们应该如何排查定位以及处理。
操作系统层面,可以直接查各个磁盘目录文件大小定位磁盘空间消耗最大数据库文件。
通过数据库查询系统表定位数据库实例磁盘消耗最大文件
SELECT DB_NAME(database_id) AS [Database Name],
[Name] AS [Logical Name],
[Physical_Name] AS [Physical Name],
((size * 8) / 1024) AS [Size(MB)]
FROM sys.master_files
ORDER BY [Size(MB)] DESC
我们空间瓶颈或者异常的定位,我们可以具体的定位到是哪一数据库的事务日志空间存在问题,所以接下来我们需要查看该数据库的事务日志状态,查看事务日志是否可以直接进行收缩
SELECT [name],[recovery_model_desc],
[log_reuse_wait_desc]
FROM master.sys.databases
数据库恢复模式区别:
需要重点关注log_reuse_wait_desc的状态值:
更多状态以及状态解释可参考:https://docs.microsoft.com/en-us/sql/relational-databases/logs/the-transaction-log-sql-server?view=sql-server-ver15
不同的log_reuse_wait_desc状态下,我们对应的处理方式不一致:
1、若log_reuse_wait_desc为ACTIVE_TRANSACTION,表示当前数据库有活跃事务一直未提交。我们需要优先处理活跃事务,才能进行后续的事务日志备份进行日志截断、最终进行事务日志空间收缩操作
1)定位活跃事务,可重点关注sys.dm_tran_database_transactions表信息
-- 查看执行时间超过10分钟的活跃事务
SELECT ST.session_id,ST.transaction_id AS TransactionID ,
DB_NAME(DT.database_id) AS DatabaseName ,
AT.transaction_begin_time AS TransactionStartTime ,
DATEDIFF(minute, AT.transaction_begin_time, GETDATE()) AS Tran_run_time ,
CASE AT.transaction_type
WHEN 1 THEN 'Read/Write Transaction'
WHEN 2 THEN 'Read-Only Transaction'
WHEN 3 THEN 'System Transaction'
WHEN 4 THEN 'Distributed Transaction'
END AS TransactionType ,
CASE AT.transaction_state
WHEN 0 THEN 'Transaction Not Initialized'
WHEN 1 THEN 'Transaction Initialized & Not Started'
WHEN 2 THEN 'Active Transaction'
WHEN 3 THEN 'Transaction Ended'
WHEN 4 THEN 'Distributed Transaction Initiated Commit Process'
WHEN 5 THEN 'Transaction in Prepared State & Waiting Resolution'
WHEN 6 THEN 'Transaction Committed'
WHEN 7 THEN 'Transaction Rolling Back'
WHEN 8 THEN 'Transaction Rolled Back'
END AS TransactionState
FROM sys.dm_tran_session_transactions AS ST
INNER JOIN sys.dm_tran_active_transactions AS AT ON ST.transaction_id = AT.transaction_id
INNER JOIN sys.dm_tran_database_transactions AS DT ON ST.transaction_id = DT.transaction_id
WHERE DATEDIFF(minute, AT.transaction_begin_time, GETDATE())>10 -- 找出运行时间大于10分钟的事务
ORDER BY TransactionStartTime
-- 获取长时间未完成的活跃事务SPID
DBCC OPENTRAN
-- 查看对应SPID信息
select * from sys.sysprocesses where spid=${SPID}
2)将活动事务进行kill
DBCC INPUTBUFFER(${SPID})
3)将数据库活跃事务进行kill完毕后,事务日志状态可正常转变为LOG_BACKUP,LOG_BACKUP状态下的处理方式具体参考如下
2、若log_reuse_wait_desc为LOG_BACKUP,表示事务日志未进行日志备份,需要进行事务日志备份后才可进行截断、事务日志空间收缩的操作
1)若数据库事务日志从未进行过备份,那么必须进行两次事务日志备份后,事务日志才可被截断,且状态转变成为NOTHING,此时事务日志被可正常进行数据日志收缩。相关参考文档连接为:https://docs.microsoft.com/en-us/sql/relational-databases/logs/troubleshoot-a-full-transaction-log-sql-server-error-9002?view=sql-server-2017
2)若数据库事务日志之前有过备份,那么可直接对当前事务日志进行备份,备份后事务日志截断且状态转变为NOTHING,表示事物已经被截断完成
--事物日志备份命令
BACKUP LOG [dbname] TO DISK = 'some_volume:\some_folder\dbname_LOG.trn'
3)检查数据库事物日志状态与使用率
-- 检查数据库文件空间使用率
SELECT a.name [文件名称] ,cast(a.[size]*1.0/128 as decimal(12,1)) AS [文件设置大小(MB)] ,
CAST( fileproperty(s.name,'SpaceUsed')/(8*16.0) AS DECIMAL(12,1)) AS [文件所占空间(MB)] ,
CAST( (fileproperty(s.name,'SpaceUsed')/(8*16.0))/(s.size/(8*16.0))*100.0 AS DECIMAL(12,1)) AS [所占空间率%] ,
CASE WHEN A.growth =0 THEN '文件大小固定,不会增长' ELSE '文件将自动增长' end [增长模式] ,CASE WHEN A.growth > 0 AND is_percent_growth = 0
THEN '增量为固定大小' WHEN A.growth > 0 AND is_percent_growth = 1 THEN '增量将用整数百分比表示' ELSE '文件大小固定,不会增长' END AS [增量模式] ,
CASE WHEN A.growth > 0 AND is_percent_growth = 0 THEN cast(cast(a.growth*1.0/128as decimal(12,0)) AS VARCHAR)+'MB'
WHEN A.growth > 0 AND is_percent_growth = 1 THEN cast(cast(a.growth AS decimal(12,0)) AS VARCHAR)+'%' ELSE '文件大小固定,不会增长' end AS [增长值(%或MB)] ,
a.physical_name AS [文件所在目录] ,a.type_desc AS [文件类型]
FROM sys.database_files a
INNER JOIN sys.sysfiles AS s ON a.[file_id]=s.fileid
LEFT JOIN sys.dm_db_file_space_usage b ON a.[file_id]=b.[file_id] ORDER BY a.[type]
-- 检查事物日志状态,将要收缩的事物日志状态为NOTHING
SELECT [name],[recovery_model_desc],
[log_reuse_wait_desc]
FROM master.sys.databases
4)对事物日志进行空间收缩
USE [${DB_Name}]
DBCC SHRINKFILE (N'db1' , 0, TRUNCATEONLY)
当数据库服务器磁盘空间已经基本打满,没有多余的空间用来进行事物日志备份,那么需要通过变更数据库恢复模式来将数据库日志进行截断,然后进行空间收缩处理。变更数据库恢复模式与上面备份事物日志后处理相对,
1、变更数据库恢复模式进行事物日志收缩流程
1)变更数据库恢复模式,将数据库恢复模式设置为simple模式,该模式下当数据库进行checkpoint时会自动对事物日志进行截断
alter database [$DB_Name] set recovery simple
2)检查事物日志状态以及事物日志空间利用率,确定截断操作完成
-- 检查数据库文件空间使用率
SELECT a.name [文件名称] ,cast(a.[size]*1.0/128 as decimal(12,1)) AS [文件设置大小(MB)] ,
CAST( fileproperty(s.name,'SpaceUsed')/(8*16.0) AS DECIMAL(12,1)) AS [文件所占空间(MB)] ,
CAST( (fileproperty(s.name,'SpaceUsed')/(8*16.0))/(s.size/(8*16.0))*100.0 AS DECIMAL(12,1)) AS [所占空间率%] ,
CASE WHEN A.growth =0 THEN '文件大小固定,不会增长' ELSE '文件将自动增长' end [增长模式] ,CASE WHEN A.growth > 0 AND is_percent_growth = 0
THEN '增量为固定大小' WHEN A.growth > 0 AND is_percent_growth = 1 THEN '增量将用整数百分比表示' ELSE '文件大小固定,不会增长' END AS [增量模式] ,
CASE WHEN A.growth > 0 AND is_percent_growth = 0 THEN cast(cast(a.growth*1.0/128as decimal(12,0)) AS VARCHAR)+'MB'
WHEN A.growth > 0 AND is_percent_growth = 1 THEN cast(cast(a.growth AS decimal(12,0)) AS VARCHAR)+'%' ELSE '文件大小固定,不会增长' end AS [增长值(%或MB)] ,
a.physical_name AS [文件所在目录] ,a.type_desc AS [文件类型]
FROM sys.database_files a
INNER JOIN sys.sysfiles AS s ON a.[file_id]=s.fileid
LEFT JOIN sys.dm_db_file_space_usage b ON a.[file_id]=b.[file_id] ORDER BY a.[type]
-- 检查事物日志状态,将要收缩的事物日志状态为NOTHING
SELECT [name],[recovery_model_desc],
[log_reuse_wait_desc]
FROM master.sys.databases
3)变更数据库恢复模式为full
alter database [$DB_Name] set recovery full
2、变更数据库恢复模式进行空间回收的风险
将数据库模式变更为Simple模式,主要是为了快速对数据库事物日志进行截断,当数据库Checkpoint时会自动对数据库事物日志截断。其风险就是该数据库的事物日志丢失,后续将无法对该事物日志包含时间点数据进行按时间点恢复。
该方式下,建议变更完毕后,立即对数据库进行一次全量物理备份,保证变更后时间节点可正常进行数据恢复。
常见的事物日志暴涨的场景如下:
为防止数据库事物日志暴涨,我们需要检查如下几点:
相关官方文档可参考:https://docs.microsoft.com/en-us/sql/relational-databases/logs/troubleshoot-a-full-transaction-log-sql-server-error-9002?view=sql-server-ver15