What is Ghost record?
When a record is deleted from a clustered index data page or non-clustered index leaf page or a versioned heap page or a forwarded record is recalled, the record is logically removed by marking them as deleted but not physically removed from the page immediately.
Pages which are marked as deleted but actually not deleted physically are called Ghost Records.
Why?
Allowing delete or update operation run faster and to ensure faster rollback if the transaction is cancelled or roll backed.
Who will remove the records which are marked for deletion?
Ghostcleanuptask: SQL Server Ghostcleanuptask thread physically removes the records which are marked as deleted.
How Ghost cleanup task works?
1. Ghostcleanuptask thread wakes up every 10 seconds.
2. Sweep databases one by one starting from master.
3. Skip the database if it is not able to take a shared lock for database (LCK_M_S) or database is not in Open read/write state.
4. Scans the PFS pages of the current database to get the pages which has ghost records.
PFS Page: A PFS page occurs once in 8088 pages. SSQL Server will attempt to place a PFS page on the first page of every PFS interval(8088Pages). The only time a PFS page is not the first page in its interval is in the first interval for a file.
In this case, the file header page is first, and the PFS page is second. (Page ID starts from 0 so the first PFS page is at Page ID 1)

5. Remove the records which are marked as deleted (ghosted) physically.
The GHOST_VERSION_RECORD is used in cases where versioned heap records, or BLOB fragments are deleted and when versioned forwarded heap rows are recalled back to their originating page. The records must be ghosted rather than deleted so the versioning chain can be maintained. The GHOST_VERSION_RECORDs cannot be expunged until all transactions that have references to the original data record have finished.
Let us see how the ghost records are marked for deletion and deleted later using below example.
Note: We will be using few trace flags for testing purpose. Use this trace flags and below steps only in your testing servers.
Trace flag 3604 Directs SQL Server to send output some of the command’s to client executing the command. Ex. When we execute DBCC PAGE or DBCC TRACESTATUS(-1) we will not see results in SSMS or any other client unless Trace flag 3604 is on
Trace flag 661 Disables ghost record cleanup task. When this trace flag is enabled Ghost cleanup task will not run.
Trace flag 662 along with 3605 prints the output of Ghost cleanup task to SQL Server error log ( DBCC TRACEON (662, 3605,-1))
Step1:
//Create a database named Ghost test
create database Ghosttest
go
use Ghosttest
go
Step 2:
//Create a table named ‘a’ with clustered and non-clustered index.
CREATE TABLE [dbo].[a](
[a] [char](3000) NULL,
[b] [char](3000) NULL,
[c] [int] NULL
) ON [PRIMARY]
CREATE Clustered INDEX [CIX_a] ON [dbo].[a]
(
[c] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
CREATE INDEX [IX_a] ON [dbo].[a]
(
[c] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
Go
Step 3:
//Insert 1000 records in to the table.
insert into a values (‘Ghost’,‘Record’,1);
go 1000
Step 4:
//Enable trace flag 3604 to see the output of DBCC command from client like SSMS.
dbcc traceon (3604,-1)
go
Step 5:
// Disable Ghost record cleanup task to see how the ghost records are marked in data/index pages. (Don’t enable in production)
dbcc traceon (661,-1)
go
Step 6:
//Delete the 1000 records which we inserted in table ‘a’ to generate ghost records (Records marked for deletion but not physically deleted).
delete from a
go
Step 7:
//Ghost cleanup task first scans the PFS pages in current database to get the pages which has ghost records. Let us dump the PFS pages to see how the pages with ghost records are marked
// Watch the text which are in red or search for Has Ghost in the output of DBCC page.
DBCC PAGE(‘Ghosttest’,1,1,3)
Go
PAGE: (1:1)
BUFFER:
BUF @0x00000000FEC0ED80
bpage = 0x00000000CA024000 bhash = 0x0000000000000000 bpageno = (1:1)
bdbid = 9 breferences = 0 bcputicks = 0
bsampleCount = 0 bUse1 = 62975 bstat = 0xc0010b
blog = 0x79797979 bnext = 0x0000000000000000
PAGE HEADER:
Page @0x00000000CA024000
m_pageId = (1:1) m_headerVersion = 1 m_type = 11
m_typeFlagBits = 0x1 m_level = 0 m_flagBits = 0x0
m_objId (AllocUnitId.idObj) = 99 m_indexId (AllocUnitId.idInd) = 0 Metadata: AllocUnitId = 6488064
Metadata: PartitionId = 0 Metadata: IndexId = 0 Metadata: ObjectId = 99
m_prevPage = (0:0) m_nextPage = (0:0) pminlen = 0
m_slotCnt = 1 m_freeCnt = 2 m_freeData = 8188
m_reservedCnt = 0 m_lsn = (50:335:27) m_xactReserved = 0
m_xdesId = (0:0) m_ghostRecCnt = 0 m_tornBits = 412090936
Allocation Status
GAM (1:2) = ALLOCATED SGAM (1:3) = NOT ALLOCATED PFS (1:1) = 0x44 ALLOCATED 100_PCT_FULL
DIFF (1:6) = CHANGED ML (1:7) = NOT MIN_LOGGED
PFS: Page Alloc Status @0x000000002205A000
(1:0) – (1:3) = ALLOCATED 100_PCT_FULL
(1:4) – (1:5) = NOT ALLOCATED 0_PCT_FULL
(1:6) – (1:7) = ALLOCATED 100_PCT_FULL
(1:8) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:9) - = ALLOCATED 100_PCT_FULL Mixed Ext
(1:10) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:11) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:12) - = ALLOCATED 100_PCT_FULL IAM Page Mixed Ext
(1:13) – (1:14) = ALLOCATED 0_PCT_FULL Mixed Ext
(1:15) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:16) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:17) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:18) - = ALLOCATED 0_PCT_FULL Has Ghost Mixed Ext
(1:19) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:20) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:21) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:22) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:23) – (1:25) = ALLOCATED 0_PCT_FULL Mixed Ext
(1:26) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:27) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:28) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:29) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:30) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:31) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:32) - = ALLOCATED 50_PCT_FULL Mixed Ext
(1:33) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:34) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:35) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:36) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:37) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:38) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:39) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:40) – (1:41) = ALLOCATED 0_PCT_FULL
(1:42) - = NOT ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:43) - = NOT ALLOCATED 0_PCT_FULL Mixed Ext
(1:44) - = NOT ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:45) – (1:46) = NOT ALLOCATED 0_PCT_FULL Mixed Ext
(1:47) - = NOT ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:48) – (1:50) = ALLOCATED 0_PCT_FULL Mixed Ext
(1:51) - = ALLOCATED 0_PCT_FULL Has Ghost Mixed Ext
(1:52) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:53) - = ALLOCATED 0_PCT_FULL Has Ghost Mixed Ext
(1:54) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:55) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:56) – (1:61) = ALLOCATED 0_PCT_FULL
(1:62) - = NOT ALLOCATED 0_PCT_FULL Has Ghost Mixed Ext
(1:63) - = NOT ALLOCATED 0_PCT_FULL Mixed Ext
(1:64) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:65) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:66) – (1:71) = ALLOCATED 0_PCT_FULL Mixed Ext
(1:72) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:73) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:74) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:75) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:76) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:77) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:78) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:79) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:80) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:81) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:82) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:83) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:84) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:85) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:86) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:87) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:88) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:89) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:90) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:91) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:92) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:93) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:94) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:95) - = ALLOCATED 0_PCT_FULL Has Ghost Mixed Ext
(1:96) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:97) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:98) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:99) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:100) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:101) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:102) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:103) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:104) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:105) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:106) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:107) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:108) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:109) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:110) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:111) – (1:116) = ALLOCATED 0_PCT_FULL Mixed Ext
(1:117) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:118) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:119) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:120) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:121) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:122) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:123) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:124) - = ALLOCATED 0_PCT_FULL Has Ghost Mixed Ext
(1:125) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:126) - = ALLOCATED 0_PCT_FULL Has Ghost Mixed Ext
(1:127) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:128) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:129) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:130) - = ALLOCATED 0_PCT_FULL Has Ghost Mixed Ext
(1:131) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:132) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:133) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:134) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:135) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:136) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:137) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:138) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:139) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:140) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:141) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:142) – (1:143) = ALLOCATED 0_PCT_FULL Mixed Ext
(1:144) - = ALLOCATED 0_PCT_FULL Has Ghost Mixed Ext
(1:145) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:146) - = ALLOCATED 0_PCT_FULL Has Ghost Mixed Ext
(1:147) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:148) - = ALLOCATED 50_PCT_FULL Mixed Ext
(1:149) - = ALLOCATED 0_PCT_FULL IAM Page Mixed Ext
(1:150) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:151) - = ALLOCATED 100_PCT_FULL Mixed Ext
(1:152) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:153) – (1:158) = ALLOCATED 0_PCT_FULL Has Ghost Mixed Ext
(1:159) - = ALLOCATED 0_PCT_FULL Mixed Ext
(1:160) – (1:383) = ALLOCATED 0_PCT_FULL Has Ghost
(1:384) – (1:385) = ALLOCATED 0_PCT_FULL Has Ghost Mixed Ext
(1:386) – (1:391) = NOT ALLOCATED 0_PCT_FULL
(1:392) – (1:497) = ALLOCATED 0_PCT_FULL Has Ghost
(1:498) – (1:499) = ALLOCATED 0_PCT_FULL
(1:500) – (1:655) = ALLOCATED 0_PCT_FULL Has Ghost
(1:656) - = ALLOCATED 0_PCT_FULL
(1:657) – (1:663) = ALLOCATED 0_PCT_FULL Has Ghost
(1:664) – (1:671) = NOT ALLOCATED 0_PCT_FULL
Step 8:
//Dump one of the page which is marked as having ghost record in PFS page. I have picked Page 158 for example.
//Look at m_ghostRecCnt
dbcc page(‘Ghosttest’,1,158,3)
go
PAGE: (1:158)
BUFFER:
BUF @0x00000000FEC02CC0
bpage = 0x00000000C9E22000 bhash = 0x0000000000000000 bpageno = (1:158)
bdbid = 9 breferences = 0 bcputicks = 933
bsampleCount = 1 bUse1 = 64101 bstat = 0xc0010b
blog = 0x2159bb79 bnext = 0x0000000000000000
PAGE HEADER:
Page @0x00000000C9E22000
m_pageId = (1:158) m_headerVersion = 1 m_type = 1
m_typeFlagBits = 0x4 m_level = 0 m_flagBits = 0x0
m_objId (AllocUnitId.idObj) = 28 m_indexId (AllocUnitId.idInd) = 256
Metadata: AllocUnitId = 72057594039762944
Metadata: PartitionId = 72057594038845440 Metadata: IndexId = 1
Metadata: ObjectId = 2105058535 m_prevPage = (1:157) m_nextPage = (1:160)
pminlen = 3508 m_slotCnt = 2 m_freeCnt = 1054
m_freeData = 7134 m_reservedCnt = 0 m_lsn = (37:160:37)
m_xactReserved = 0 m_xdesId = (0:1983) m_ghostRecCnt = 2
m_tornBits = 602498023
Allocation Status
GAM (1:2) = ALLOCATED SGAM (1:3) = NOT ALLOCATED
PFS (1:1) = 0x68 MIXED_EXT ALLOCATED 0_PCT_FULL DIFF (1:6) = CHANGED
ML (1:7) = NOT MIN_LOGGED
Slot 0 Offset 0x60 Length 3519
Record Type = GHOST_DATA_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 3519
Memory Dump @0x000000001CEBA060
//There are 2 ghost records in page 158 (m_ghostRecCnt=2)
Step 8:
//Additionally we can query sys.dm_db_index_physical_stats to find the number of ghost records.
SELECT object_name(object_id) as Name, record_count, GHOST_RECORD_COUNT,Version_ghost_record_count,INDEX_TYPE_DESC, ALLOC_UNIT_TYPE_DESC
FROM sys.dm_db_index_physical_stats (DB_ID(N‘Ghosttest’), NULL, NULL, NULL , ‘DETAILED’) where index_level=0
go

Step 8:
//Let us enable trace flag 662 and 3605 to print the output of ghost cleanup task to SQL error log
dbcc traceon (662,3605,-1)
go
Step 9:
//Let us disable trace flag 661 to start the Ghost cleanup task again and let us disable 3604 which we enabled to print output of DBCC command in client (SSMS).
dbcc traceoff (661,3604,-1)
go
Purging page Dbid 9, File 1, Page 144
Purging page Dbid 9, File 1, Page 146
Purging page Dbid 9, File 1, Page 153
Purging page Dbid 9, File 1, Page 154
Purging page Dbid 9, File 1, Page 155
Purging page Dbid 9, File 1, Page 156
Purging page Dbid 9, File 1, Page 157
Purging page Dbid 9, File 1, Page 158
Purging page Dbid 9, File 1, Page 160
Purging page Dbid 9, File 1, Page 161
We will see the pages purged by the ghost cleanup task in the SQL Server error log.
Step 10:
// We can also capture Ghost cleanup task in profiler by selecting SQL Transaction and filter by object name like “GhostCleanupTask”

If you liked this post, do like us on Facebook at https://www.facebook.com/mssqlwiki and join our Facebook group https://www.facebook.com/mssqlwiki#!/groups/454762937884205/
Thank you,
Karthick P.K |My Facebook Page |My Site| Blog space| Twitter
Like this:
Like Loading...