Blog Post

Database Ghosts

,

phantasmripRecently you may have seen my first article on this topic over at SQL Solutions Group.  If not, here is the link so you can read that article first before proceeding here.  This article is intended as a first of two part follow-up to that article.

Now that you have read that other article, we can recap it a little bit.

You will recall that in that article, I discussed the presence of Ghosts in your database.  I also discussed that those ghosts are a good thing.  They have some benefits to them, of which is a bit of a performance boost for some operations like the rollback of a delete.

In that article I discussed one method with which you could see these ghost records.  In this article, I would like to share an alternate method to be able to see these ghosts.

In order to explore this alternate means, let’s go ahead and follow the first few steps from the other article to get the setup complete so our second ghost hunting foray can begin.

Use master;
Go
IF DB_ID('Cemetery') IS NULL 
BEGIN
 EXECUTE ('CREATE DATABASE Cemetery');
 ALTER DATABASE [Cemetery] SET RECOVERY SIMPLE WITH NO_WAIT
 ALTER DATABASE [Cemetery] MODIFY FILE ( NAME = N'Cemetery', SIZE = 6144000KB , FILEGROWTH = 262144KB )
 ALTER DATABASE [Cemetery] MODIFY FILE ( NAME = N'Cemetery_log', SIZE = 131072KB , FILEGROWTH = 131072KB )
END
GO
Use Cemetery;
Go
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF SCHEMA_ID('Halloween') IS NULL EXECUTE ('CREATE SCHEMA Halloween');
GO
IF OBJECT_ID('Halloween.Ghosts','U') IS NOT NULL
BEGIN
DROP TABLE Halloween.Ghosts
END
GO
DECLARE @BeginDate DATE = '2014-10-01'
,@EndDate DATE = '2014-10-31'
 SELECT TOP 1000000
        Pinky       = IDENTITY(INT,1,1),
        Blinky= 'GHOST ITEM ' + CONVERT(VARCHAR(20),ISNULL('', 0)),
        CandyMan= ABS(CHECKSUM(NEWID()))%50000+1,
        Sadako= CHAR(ABS(CHECKSUM(NEWID()))%26+65)
                     + CHAR(ABS(CHECKSUM(NEWID()))%26+65),
        Slimer= DATEADD(DAY,RAND(CHECKSUM(NEWID())) * ( 1 + DATEDIFF(DAY, @EndDate,@BeginDate) ), @EndDate),
        Poltergeist= RIGHT(NEWID(),12),
        MalcomCrowe= CAST(ABS(CHECKSUM(NEWID()))%10000 /100.0 AS MONEY),
        TheBlob= REPLICATE('Hey, Dracula!! Why do you say blah, blah blah?',1000)
   INTO Halloween.Ghosts
   FROM Master.dbo.SysColumns t1,
        Master.dbo.SysColumns t2 --Lack of join criteria makes this a CROSS-JOIN
/* let's get our Blinky updated properly */Update hg
Set Blinky = 'GHOST ITEM ' + CONVERT(VARCHAR(20),ISNULL(Pinky, 0))
From Halloween.Ghosts hg;
--===== A table is not properly formed unless a Primary Key has been assigned
     -- Takes about 1 second to execute.
  ALTER TABLE Halloween.Ghosts
        ADD PRIMARY KEY CLUSTERED (Pinky)
/* additional index to show the ghosts */Create NonClustered Index IX_GhostPinky on Halloween.Ghosts (Pinky)

Now with the setup complete, we should once again confirm that we have appropriate data available for the hunt.  Once again a simple query can suffice to show the data.

/* Take a peek at the data */Select top 100 *
From Halloween.Ghosts

Upon execution of the check script, we should see something similar to the following data-set.

datasample

 

Great, the data is confirmed and we should have a wonderful chance to find some ghosts once again.  In the previous attempt, we needed an additional plasma blaster in the form of a trace flag.  In this attempt we will hunt these ghosts without that tool and see what we can find.  Why attempt it in this way?  Well, it is simply because I’d rather not use a trace flag if it is not necessary.  If you recall, that trace flag had a couple of noted effects.  One of the effects was that it turned off the ghost cleanup process.  If I can avoid it, I’d rather leave the containment unit in tact.

Now, due to that clerical error of putting Halloween on the wrong date, we need to follow the prescribed policy to delete records prior to creating the proper records.

/* Somebody made a mistake and thought halloween was Oct 30 instead of Oct 31 
we need to kill those records
*/Begin tran
Delete top(666)
From Halloween.Ghosts
WHERE Slimer = '10/30/2014';

Notice that we issued the delete in a transaction this time around.  We are going to leave that transaction open for a bit while we hunt those ghosts.

The first step is to verify that some ghosts might be present.  To do that we should run a query such as the following.

/* ghost_record_count */Set transaction Isolation level read uncommitted
SELECT DB_NAME(database_id) AS DBName,schema_name(so.schema_id) + '.' + so.name as ObjName, record_count,index_type_desc
,ghost_record_count,version_ghost_record_count
FROM sys.dm_db_index_physical_stats(db_id('Cemetery'), OBJECT_ID('Halloween.Ghosts'), NULL, NULL , 'DETAILED') ps
INNER JOIN sys.objects so
ON ps.object_id = so.object_id
WHERE index_level = 0
;

From that query we should see something like the following.

idxstats_ghosts

Once again we are on the right track.  We can further confirm the existence of these ghosts through a little more investigation and monitoring.  Let’s try the trick with the dblog function again.

/* Use Fn_dblog to get Pages with Ghost Counts..works with TF 661 or an uncommitted tx */Select Operation,Context,[Transaction ID],[Page ID] AS [File:PageIDHex],AllocUnitId,[Slot ID],PartitionId
,CONVERT(INT,CONVERT(VARBINARY,'0x' + RIGHT([Page ID], 8),1)) AS PageID
From fn_dblog(null,null) fn
Where CONTEXT = 'LCX_MARK_AS_GHOST';

With that query, we should see something like the following result set.

dblog_ghosts

 

Very cool.  We once again can see that these phantasms are in the database.  We have enough information that we can proceed on to the next stage.  We can pass the PageID into DBCC PAGE in order to investigate the ghosts on the page.  If we use the PageID that is circled with the green in the preceding result set, we can get a better feel for these specters.

DBCC PAGE('Cemetery',1,2002440,3) WITH TABLERESULTS
GO

And we may see results such as the following.

page_ghosts2

Recall that the log says this page has ghost records on it.  When we check the page with DBCC PAGE we can definitely see that there are ghosts on the page.  This is very cool.  Now, had we tried to check for ghost records on the PFS page we would not be able to see the ghost count like we were able to see by enabling the TF.

Once again we have been able to display the existence of ghosts in the database.  In order to get these ghosts to move on from the database to their afterlife, we merely need to commit the transaction or roll back the transaction.

Stay tuned for the next article in this mini-series about ghosts.  Who knows, we may even have a bonus Halloween article since this is Halloween month.

Part III of the series can now be found here.

Rate

You rated this post out of 5. Change rating

Share

Share

Rate

You rated this post out of 5. Change rating