这篇文章解释了SQL Server数据库的Ghost清理任务的内部情况。
简介
每个关系型数据库都由后台进程和用户进程组成。同样,SQL Server也有许多内部进程,如Checkpoint、Lazy Writer、Lock Monitor、System_Health_Monitor、Trace Queue Task和Ghost CleanUp。大体上,我们可以把这些系统进程分为以下几类。
- 数据编写员流程
- 懒人写手
- 检查点(CHECKPOINT
- 渴望写作的人
- 记录交易过程
- LOG写入器
- 备份日志
- 系统流程
- 资源监控
- 锁定监控
- 幽灵清理
- 跟踪队列任务
- 工作调度流程
- SQL服务器代理
我看到DBA们对Checkpoint、Lazy Writer进程有一定的了解,但他们忽略了Ghost Cleanup后台进程的重要性。要了解这些后台进程的一些情况,请参考文章 -SQL Server中的CHECKPOINT、Lazy Writer、Eager Writer和Dirty Pages
在使用sp_who2或动态管理视图sys.dm_exec_requests检查进程时,你是否看到GHOST CLEANUP任务?你可以使用状态栏输出的Background来识别一个后台进程。
本文对SQL Server数据库中的幽灵清理任务进行了深入的探讨。
前提条件
如果你对SQL Server索引--集群和非集群索引有基本的了解,对理解本文的幽灵清理任务会有帮助。
- 注意:你可以随时参考索引来了解索引的要求、架构和使用方法。
什么是SQL Server数据库的Ghost清理任务?
幽灵清理是一个定期运行并清理幽灵记录的后台进程。接下来的问题是--什么是幽灵记录?
当用户从聚类索引数据页或非聚类索引叶子页中删除记录时,SQL Server不会物理地删除它们。相反,它将这些记录标记为幽灵记录。因此,被删除的记录会留在页面上;但是,行头会显示该记录是一个幽灵记录。
这是一种性能优化技术,使删除操作执行逻辑删除并迅速完成。同样地,在回滚过程中,SQL Server必须将该记录取消标记为幽灵记录。因此,它消除了在回滚时重新插入这些被删除的记录的要求。
一旦删除事务提交,鬼魂清理任务发生,它就会检查页面上的鬼魂记录,并从物理上删除它们。SQL Server也会更新PFS(页面可用空间)的页头,将其标记为幽灵记录页。它还将数据库标记为有鬼魂记录。
幽灵清理任务执行以下工作。
-
幽灵清理任务每5秒发生一次(SQL Server 2012+)。
-
它开始检查被标记为有幽灵记录的数据库,并扫描幽灵数据库的PFS页面
-
它从数据库中物理地删除幽灵记录行
-
一旦它从数据库中删除了所有的幽灵记录,它就把它标记为没有幽灵条目。因此,在下一次运行鬼魂清理任务时,SQL Server会跳过该数据库。
重要说明。
- GHOST CLEANUP任务的单次运行在每次执行中最多可以清理10个页面。这是为了确保不影响系统的性能。如果有多个页面(超过10个),它会在随后执行的幽灵清理任务中处理它们
- 如果清理过程不能在有幽灵记录的数据库上取得共享锁,它就会跳过它,并在下一次运行时再次检查。*
通常情况下,如果你查询sp_who2或DMV,我们不会看到GHOST CLEANUP任务,因为它是一个快速过程。然而,对于一个有多次频繁删除的大量数据库,你可以在会话的输出中观察到这一点。
在本文中,为了捕获幽灵清理任务,我们将创建一个表[GhostSession]并使用WHILE循环向表中插入记录,直到捕获GHOST CLEANUP后台进程。
CREATE TABLE [dbo].[GhostSession] (
[session_id] [smallint] NOT NULL,
[start_time] [datetime] NOT NULL,
[status] [nvarchar](30) NOT NULL,
[command] [nvarchar](32) NOT NULL,
[database_id] [smallint] NOT NULL
) ON [PRIMARY]
GO
SET NOCOUNT ON;
GO
DECLARE @i INT
SELECT @i = 0;
WHILE (@i < 1)
BEGIN
INSERT INTO [GhostSession]( [session_id],[start_time],[status],
[command],[database_id])
SELECT [session_id],[start_time],[status], [command],[database_id]
FROM sys.dm_exec_requests WHERE command LIKE '%ghost%'
SELECT @i = COUNT (*) FROM [GhostSession]
END;
GO
Select * from [GhostSession]
如下图所示,SQL Server触发了数据库id 5的幽灵清理任务。
为了找出数据库中的幽灵记录,我们可以按照下面的脚本,使用DMVsys.dm_db_index_physical_stats 。这个脚本根据数据库ID对记录进行分组,并按照幽灵记录总数的降序排列。
- 注意:DMV返回指定表或数据库的大小、碎片信息和幽灵记录信息。你可以参考微软的文档来了解DMV的使用情况sys.dm_db_index_physical_stats
SELECT sum(ghost_record_count) total_ghost_records, db_name(database_id) as DB
FROM sys.dm_db_index_physical_stats (NULL, NULL, NULL, NULL, 'SAMPLED')
group by database_id
order by total_ghost_records desc
如下图所示,[AdventureWorks2017]数据库在运行DMV时有两条幽灵记录。我们在其余的数据库中没有任何幽灵记录。

SQL Server数据库的幽灵记录的实际演示
直到你对SQL Server的幽灵清理任务有了了解。本节将查看SQL Server内部页面,以确定幽灵记录和清理过程。为此目的,让我们创建一个SQL Server数据库,并在其中插入样本记录。
Create Database TestDatabase
Go
Use TestDatabase
Go
CREATE TABLE [dbo].[ViewGhostRecords]
(
[ID] Int PRIMARY KEY CLUSTERED,
[Name] varchar(100) NULL,
[Amount] int NULL
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_Amount] ON [dbo].[ViewGhostRecords]
(
[Amount]
)
declare @i int
Set @i=0
While(@i<1000)
BEGIN
insert into [dbo].[ViewGhostRecords] values (@i, 'Ghost record demo',100)
set @i= @i+1
END
在我们进一步进行之前,我们启用以下两个跟踪标志。
-
追踪标志3604用于在SSMS中查看DBCC结果
-
追踪标志661用于禁用GHOST CLEANUP任务
-
注意:除非你有特殊要求,否则不要在生产环境中启用跟踪标志。
DBCC traceon (3604,-1)
go
DBCC traceon (661,-1)
go
- 注意:不要在生产实例中禁用GHOST CLEANUP。如果需要的话,在生产之前要有一个受控的环境测试。
此外,我们从演示表中删除记录,并使用DBCC PAGE查看页面数据。你也可以在SQL Server 2019中使用DMV sys.dm_db_page_info。
BEGIN TRAN ABCD
Delete from [dbo].[ViewGhostRecords]
go
DBCC PAGE(7,1,1,3) WITH TABLERESULTS
go
样本输出显示了PFS页面上有信息的条目。
你可以查询索引DMV来检查幽灵记录的数量。如下图所示,到现在为止,它有2000条幽灵记录。

我们还可以使用fn_dblog()函数来检查交易日志的内容。
DECLARE @TranID CHAR (20)
SELECT @TranID = [Transaction ID] FROM fn_dblog (null, null) WHERE
[Transaction Name]='ABCD'
SELECT * FROM fn_dblog (null, null) WHERE [Transaction ID] = @TranID;
GO
在输出结果中,你看到操作为LOP_DELETE_ROWS,上下文为LCX_MARK_AS_GHOST,用于样本表中删除的行。

禁用SQL Server数据库的幽灵清理功能
禁用ghost清理过程会增加缓冲池的大小,提高IO请求。它可能会增加数据库的大小,因为SQL Server不会重复使用通过删除行而清除的空间。SQL Server还必须添加新的页面,以适应新的插入,增加页面分割,最终导致性能问题
正如我们前面所看到的,你可以使用跟踪标志661来禁用幽灵清理。然而,不建议这样做,直到你有一个特定的要求,并且在生产版本中测试得足够好。
然而,如果你禁用了幽灵清理任务,你可以使用下面的存储过程手动删除幽灵记录。
在SQL Server数据库中使用存储过程sp_clean_db_free_space
这个存储过程清理了数据库页面上的幽灵记录。它需要两个参数-数据库名称(必选)和清理延迟时间(秒)(可选)。清理延迟是一个可以指定的间隔时间,用来延迟清理页面的时间。默认值是0。
存储过程sp_clean_db_free_space的语法。
sp_clean_db_free_space
[ @dbname = ] 'database_name'
[ , [ @cleaning_delay = ] 'delay_in_seconds' ] [;]
SQL Server数据库中的存储过程sp_clean_db_file_free_space
这个存储过程可以清理一个数据库的特定数据文件中的幽灵记录。如语法所示,它需要一个额外的数据文件id(强制)参数。
存储过程sp_clean_db_file_free_space的语法。
sp_clean_db_file_free_space
[ @dbname = ] 'database_name'
, [ @fileid = ] 'file_number'
[ , [ @cleaning_delay = ] 'delay_in_seconds' ] [;]
- 注意:这些存储过程的执行时间取决于数据库的大小、数据文件、可用自由空间、磁盘容量。你应该在非工作时间运行它们,以避免任何性能影响*
总结
这篇文章探讨了在SQL Server数据库中删除幽灵记录的幽灵清理任务。你应该了解这个有用的后台程序。如果你因为特定的原因禁用它,可以使用存储过程来手动清理它们。这篇文章的目的是让大家了解鬼魂清理任务。 因此,我再次重申,除非你有特殊原因,否则永远不要禁用SQL Server中的鬼魂清理任务。

