Unable to shrink the SQL server database. Is it the same Blob thing?

This is a very common scenario which I have seen on many customers’ environments. Even shrinking the database doesn’t help to reduce the size of the database. Most of the times on the products which use BLOB very often face this issue e.g Microsoft SharePoint – it deals with BLOB most of the times.

Many times,I have seen many customers complaining of the database size going even double after the upgrade. Most of the times, the issue is found to be due to BLOB stuff.  In this blog we will talk about resolving the issues which are only related to BLOB storage.

Now it’s the time to build some base to understand the cause of the issue.  Let’s start with some Q & A

Q : – When we issue delete command on a table, what happens in the background. Do the records get deleted immediately?

A : –  There are lots of people who have doubt about this. Well, the answer is no, they are not deleted immediately. They are marked as ghost records in the PFS page. There is a process known as ghost record cleanup task which runs periodically to delete those records.

Q : – How is it related to the database size issue.

A: –  When we delete a record, it’s the responsibility of the ghost records cleanup task to delete those records physically to reclaim the space. It helps to keep the database size up to the mark. But the above issue can happen due to the reasons :

1. The ghost record cleanup task is not running.
2. The load on the system is very high and there are huge no. of deletions and updates – and the ghost cleanup task is not able to catch up.
3.  In certain cases, these allocated but empty LOB pages may accumulate and this is by design. When the LOB value to be inserted is larger than one page, we break it up into page-size fragments. And when we insert a page-size fragment, we don’t search the existing LOB pages for free space; we instead just allocate a new page.

This is an optimization: in the typical case, the free space fragments on the existing pages are all smaller than a page. Since we need a whole page of free space, we might as well skip the search and just allocate a new page. Thus, if we repeatedly rollback insertions of LOB data larger than one page, their database will accumulate empty LOB pages.

Now we will talk about the action steps to resolve this issue:

1.  Run DBCC updateusage and see if this sorts out your issue.

It reports and corrects pages and row count inaccuracies in the catalog views. These inaccuracies may cause incorrect space usage reports returned by the sp_spaceused system stored procedure.

2. If step 1 doesn’t help, we need to know whether we have ghost records or not. To find that out , please run this command:

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'<dbname>’), NULL, NULL, NULL , ‘DETAILED’)

and this may take some time to give you output for all the objects .  if the database size is very large then we can first check the largest objects and then run the query for a particular table name. To get the object name with the largest allocation , you could run the query:

select name=object_schema_name(object_id) + ‘.’ + object_name(object_id)
, rows=sum(case when index_id < 2 then row_count else 0 end)
, reserved_kb=8*sum(reserved_page_count)
, data_kb=8*sum( case
when index_id<2 then in_row_data_page_count + lob_used_page_count + row_overflow_used_page_count
else lob_used_page_count + row_overflow_used_page_count
end )
, index_kb=8*(sum(used_page_count)
– sum( case
when index_id<2 then in_row_data_page_count + lob_used_page_count + row_overflow_used_page_count
else lob_used_page_count + row_overflow_used_page_count
end )
, unused_kb=8*sum(reserved_page_count-used_page_count)
from sys.dm_db_partition_stats
where object_id > 1024
group by object_id
–order by name
order by reserved_kb

once we get the top space consumers, we can pick up the table names and run it like:

    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'<dbname>’), object_id(‘<TableName’), NULL, NULL , ‘DETAILED’)

3. If we see the ghost records, then we also need to find out whether ghost cleanup task is running or not. To check that , we will need to enable the trace flags : DBCC TRACEON (662,3605,-1) and it will throw messages in the SQL error logs. Also, you could run the query :

if exists (select 1 from sys.dm_tran_active_transactions where name=’GhostCleanupTask’)
select * from sys.dm_tran_active_transactions where name=’GhostCleanupTask’

Keep this query running for 10-15 seconds continuously and you will get an output as ghost cleanup task runs after every 5-10 seconds. If the ghost cleanup task is not working then please check if you have 661 trace flag enabled. If yes, please disable it.

4.  There is an Cumulative update of SQL server which has fix for this issue: – http://support.microsoft.com/kb/2622823

5. If you can’t upgrade the SQL instance then you could try, dbcc cleantable.

6. If still no help, you could try  ALTER INDEX…REORGANIZE WITH (LOB_COMPACTION = ON)

I hope this will help you to fix the issue of database space usage. Please feel free to post the questions if any.

Analyzing PSSDIAG logs without using SQLNEXUS

In this blog we will discuss about how we can analyze the PSSDIAG logs without using SQLnexus tool.  In this post I will talk in reference to my blog on troubleshooting high CPU issues. Please make sure you open it while reading this blog because I have followed the same steps to analyze the PSSDIAG.

After installing and configuring the PSSDIAG as mentioned in my blog post, here are the next steps we have to follow.

After configuring the PSSDIAG , you will get a cab named pssd.cab in the chosen location:


Please make sure the cab is saved in some folder because we will have to extract files and it will become easier to manage .

Here is how it will look when the files are extracted:


Before running the PSSDIAG data collection , we have to make sure that the issue is happening  or going to happen and we will have to capture the logs for at least 5-10 minutes “whilst” the issue is happening. It will help us to get enough data for analysis.

To start the PSSDIAG we have to double click the PSSDIAG.cmd and it will look like :


Once you see the below text in the command prompt , that means now the PSSDIAG collection process has started.


Below is the scenario of high CPU for which I had collected the PSSDIAG logs.


Now lets look at the collected logs in the folder named “output” which will be created in the same location from where pssdiag.cmd was run.

The output folder will look like:


There will be lots of files in the output and I will discuss about the major files which will help us to find out the cause of high CPU. I will follow my blog on troubleshooting high CPU issues step by step to troubleshoot.

Step 1: – When you see high CPU utilization on the server check in task manager, which process is consuming high CPU. If it’s SQL server then open Perfmon and add the counter for Processor: % Privileged time, % Processor time and %user time

If you see the %privileged time to be high like hovering around 40% and above then it could be some driver issue. To troubleshoot these kinds of issues you could try:

Using Xperf: – http://blogs.msdn.com/b/ntdebugging/archive/2010/03/22/using-xperf-to-root-cause-cpu-consumption.aspx — Video; http://msdn.microsoft.com/en-us/performance/cc709422

If it’s user time which is high then find the process which is consuming high CPU.

In this case which apparently is , now we have to check the process consuming high CPU:


In this case SQL is consuming around 60% of the CPU and rest is consumed by the remaining processes. As SQL is major contributor to the high CPU at the system and is consistently consuming the same amount.

Another way to find out how much CPU SQL server is consuming – Open the file(from the output folder) , name ending with SQL_2005_Perf_Stats_Startup.OUT.

Look for: Recent SQL Processor Utilization (Health Records)  – you will get the output as follows:

runtime                         system_idle_cpu sql_cpu_utilization
——————————  ————— ——————-
2012-03-23T16:26:33.137        90                   1
2012-03-23T16:26:33.137        64                  20
2012-03-23T16:26:33.137        58                  25
2012-03-23T16:26:33.137        56                  25
2012-03-23T16:26:33.137        55                  25
2012-03-23T16:26:33.137        57                  26
2012-03-23T16:26:33.137        57                  26
2012-03-23T16:26:33.137        44                  35
2012-03-23T16:26:33.137        14                  53
2012-03-23T16:26:33.137         4                  60
2012-03-23T16:26:33.137         3                  59
2012-03-23T16:26:33.137         3                  59
2012-03-23T16:26:33.137         2                  58
2012-03-23T16:26:33.137         3                  58
2012-03-23T16:26:33.137         8                  54
2012-03-23T16:26:33.137        10                  53
2012-03-23T16:26:33.137         2                  56

Step 2. Lets move to the next step of finding the architecture which in our case is x64.

Snippet from Msinfo2 file:

Item    Value
OS Name    Microsoft Windows Server 2008 R2 Enterprise
Version    6.1.7600 Build 7600
System Name    ANMOL-PC
System Type    x64-based PC
Processor    Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz, 2933 Mhz, 4 Core(s), 8 Logical Processor(s)

Step 3: – Check if there are any trace flags enabled:

To get this we can check in the SQL error log at the startup of SQL server. the file name is SQL_Base_Errorlog_Shutdown.TXT. In this case we don’t have any trace flags(snippet from error logs):

2012-03-23 11:50:55.060 Server Microsoft SQL Server 2008 (RTM) – 10.0.1600.22 (X64)
Jul  9 2008 14:17:44
Copyright (c) 1988-2008 Microsoft Corporation
Enterprise Edition (64-bit) on Windows NT 6.1 <X64> (Build 7600: )
2012-03-23 11:50:55.060 Server (c) 2005 Microsoft Corporation.

Step 4
: – Check the below mentioned SQL parameters

To check the SP_configure output we can check the file : –  name ending with sp_sqldiag_Shutdown.OUT

In this case:

max degree of parallelism                     0          64            0           0
max server memory (MB)                       16  2147483647         2048        2048
min server memory (MB)                        0  2147483647            0           0
priority boost                                0           1            0           0

In this case we are safe because we have:
1. Set max server memory
2. Not set Priority boost
3. As we have 8 processors, we don’t need to set any value for this unless required.

Step 5
: – Run DBCC memorystatus and check for the USERSTORE_TOKENPERM memory clerk.

To check the DBCC memorystatus, please check the file:  filename ending with DBCC_MEMORYSTATUS_Startup.OUT

In our case, it is normal:

USERSTORE_TOKENPERM (node 0)             KB
—————————————- ———–
VM Reserved                              0
VM Committed                             0
Locked Pages Allocated                   0
SM Reserved                              0
SM Committed                             0
SinglePage Allocator                     584 <=very less so no problem here.
MultiPage Allocator                      288

CACHESTORE_SQLCP (node 0)                KB
—————————————- ———–
VM Reserved                                        0
VM Committed                                       0
Locked Pages Allocated                             0
SM Reserved                                        0
SM Committed                                       0
SinglePage Allocator                           10128 <= Not very high
MultiPage Allocator                             2152

—————————————- ———–
VM Reserved                                        0
VM Committed                                       0
Locked Pages Allocated                             0
SM Reserved                                        0
SM Committed                                       0
SinglePage Allocator                          374256 <= High based on the allocated memory
MultiPage Allocator                                0

Step 6
: – If the memory consumed by CACHESTORE_SQLCP is high then we check compilations and re-compilations which in our case is not required.

But I will still mention about the file in the PSSDIAG output which you will need to check for procedure cache pollution:  File name will end with :- SQL_2005_Mem_Stats_Startup.OUT and you will have to search for the string: –   — proccache_pollution

The output will look like :

runtime     plan_count  total_size_in_bytes  cacheobjtype       objtype    usecounts
———– ———– ——————– —————— ———————-
2011-08-22        48768           2898108416 Compiled Plan      Adhoc                4
2011-08-22         5762            281034752 Compiled Plan      Adhoc                1
2011-08-22         5755           1100980224 Compiled Plan      Adhoc                1
2011-08-22         4125            103612416 Compiled Plan      Adhoc                1
2011-08-22         1380            396263424 Compiled Plan      Adhoc                1
2011-08-22         1379            200916992 Compiled Plan      Adhoc                1
2011-08-22         1379            291463168 Compiled Plan      Adhoc                1
2011-08-22         1378            149831680 Compiled Plan      Adhoc                1
2011-08-22         1166            107454464 Compiled Plan      Adhoc                4
2011-08-22         1056            158662656 Compiled Plan      Adhoc                1
2011-08-22         1056             72105984 Compiled Plan      Adhoc                1
2011-08-22         1054             48947200 Compiled Plan      Adhoc                1
2011-08-22          157             11329536 Compiled Plan      Prepared             1
2011-08-22          109              4751360 Compiled Plan      Adhoc                1

But this is just dummy data to show you how it looks like. There will be more columns e.g. short_qry_text which will show you the query for which the parameterization is required.

In the above output, we have to look at the plan_count , size and the use counts which will simply mean that the query for which a new plan was created per execution. For more explanation, please check step 6 in  troubleshooting high CPU issues.

Step 7:- This relates to our issue i.e. lots of hashing and sorting is happening :


If we see, the output exactly matches with :

—————————————- ———–
VM Reserved 0
VM Committed 0
Locked Pages Allocated 0
SM Reserved 0
SM Committed 0
SinglePage Allocator 374256
MultiPage Allocator 0

Now, the time to check worktables/sec and workfiles/sec

Worktables/sec are high(~15) based on the number of batches/sec (~ 2 ) – which simply lots of sorting spilling to tempdb :


Workfiles/sec are are very low (It shows that there was no hashing spilled to the tempdb) :


In essence:

1. No compilation and re-compilation which means no parameterization issue.
2. No configuration issues at SQL server end.
3. we need to find out a query which might have cause the issue.

Please note : – The values are low because I have run small batched to reproduce high CPU.

Now we will move in the direction of hunting the query which is causing the trouble.We will make use of SQL server 2008’s feature query plan hash to drill down the issue. In the file – name ending with  Perf_Stats_Snapshot_Shutdown.OUT. Look for top 10 CPU by query_hash

Here is the output you will get:

runtime     total_worker_time    sample_statement_text
———-  ——————– ———————————-
2012-03-23            1683404272 insert into test (select * from test123 order by col1)
2012-03-23               4432252 SELECT * FROM sys.sysperfinfo
2012-03-23               2461141 SELECT   CONVERT (varchar(30), @ru
2012-03-23               1595088 select db_id() as dbid,     case
2012-03-23               1495085 SELECT CONVERT (varchar(30), @runt
2012-03-23               1062060 SELECT       CONVERT (varchar(30),
2012-03-23               1026058 SELECT CONVERT (varchar(30), @runt
2012-03-23                905051 SELECT CONVERT (varchar(30), @runt
2012-03-23                686038 SELECT      sess.session_id, req.r
2012-03-23                684039 select TOP 10000 * from sys.dm_exe

In the above output, if we see the query which is causing the high CPU is: insert into test (select * from test123 order by col1)

We can tune this query to avoid excessive sorting and which will further help to avoid high CPU issue.

I will keep adding more content in this blog as I find more interesting scenario of high CPU.

Configure SQL agent alerts to notify the performance issues

In every production environment, we have to monitor the various aspects of the SQL instance proactively to avoid any outage. Many times there are requirements like

Whenever the size of database increases more than a specific size- alert me (email/text me)Whenever the procedure cache size increases more than a specific size –alert me
Whenever Page Splits/sec increases more than a specific no. – alert me
and so on.

Today, we are going to talk about how we can achieve this using SQL agent alerts. It comes very handy when we don’t want to write a T-SQL code or use any third party tool.

In SQL server agent we can create 3 types of alerts:
1. SQ: server event alert
2. SQL server performance condition alert
3. WMI event alert

In this blog I will talk only about the second one i.e. SQL server performance condition alert.

To start with SQL server performance condition alert, I am sure everyone would have heard of the DMV sys.dm_os_performance_counters. In this DMV’s output we get the performance monitor data of SQL server. Yes, that’s what SQL agent use/query in the background.

1) Here is how you select this alert: right click on the alert and select new alert.


Then select SQL server performance condition alert.

2) click on the object combo


and select the counter you are looking for – to monitor.

3) click on the counter


4) select the instance for which you want to monitor and by default it’ll be the one from which you are creating the alert.

5)  select the condition from the “alter if counter” combo and then set the value.

Once this is configured then the other options which are 1. response 2. options can be configured as mentioned in the article: – http://msdn.microsoft.com/en-us/library/ms191508.aspx

One more important thing which you may be thinking is how this type of alert works. As per my testing on this , I came to know that when we configure the alert it makes an SQL query using the sys.dm_os_performance_counter DMV and runs it every 20s. Once the condition which you had set becomes true then the notifications are sent.

Even, you can execute the job in response to the alert along with the notification.


e.g. if space in the database goes below the threshold , I want to add another .ndf file to the database and also send a notification.