Tempdb Space Usage Monitoring

In today’s blog, we will be talking about Tempdb Space Usage.

Tempdb is a system database inside SQL Server, used primarily for internal and temporary operations. In the case of troubleshooting performance, DBAs focus mostly on the size of Tempdb, what is being stored here and why?

Tempdb is comprised of two files, a data file (could be more than one) and a log file. Upon executing workloads, there is a significant increase in the size of these two files, while the Available Space will continue to decrease, causing SQL Server to not function at its optimum.

The DMV, sys.dm_db_file_space_usage, and a system catalog – sys.database_files, provide the required output for purposes of troubleshooting Tempdb.

Note: The following test must not be performed in a production environment.

USE [tempdb];
GO

SELECT GETDATE() AS runtime
    ,SUM(user_object_reserved_page_count) * 8 AS usr_obj_kb
    ,SUM(internal_object_reserved_page_count) * 8 AS internal_obj_kb
    ,SUM(version_store_reserved_page_count) * 8 AS version_store_kb
    ,SUM(unallocated_extent_page_count) * 8 AS freespace_kb
    ,SUM(mixed_extent_page_count) * 8 AS mixedextent_kb
FROM sys.dm_db_file_space_usage

USE [tempdb]
GO

SELECT [name] AS [Logical Name]
    ,size / 128.0 AS [Total Size in MB]
    ,size / 128.0 - CAST(FILEPROPERTY(name, 'SpaceUsed') AS INT) / 128.0 AS [Available Space In MB]
FROM sys.database_files

tempdb1

Tempdb objects are classified as user objects, internal objects, and version store.

  • User objects are created by stored procedures, queries, and workloads including both local and global temporary tables.
  • Internal objects are created by SQL Server for running queries – that may involve a sort or hash operation, which requires temporary space to complete the execution successfully.
  • Version store object comprises SNAPSHOT isolation, which is responsible for maintaining multiple versions of records.

Let’s create a stored procedure to simulate workloads.

--Create a Stored Procedure to populate data on the temp table
USE [tempdb];
GO

IF OBJECT_ID('[dbo].[sp_populate_data]','P') IS NOT NULL
DROP PROCEDURE [dbo].[sp_populate_data]
GO
CREATE PROCEDURE [dbo].[sp_populate_data]
@TAB AS NVARCHAR(50), @ROW AS INT
AS
SET NOCOUNT ON
DECLARE @RowCount INT 
DECLARE @RowMax INT
DECLARE @RowString INT 
DECLARE @SQL1 NVARCHAR(200)
SET @SQL1 = N'SELECT @RowCount1=COUNT(1) FROM '+@TAB
EXEC sp_executesql @SQL1, N'@RowCount1 INT OUTPUT', @RowCount1 = @RowCount OUTPUT
SET @RowMax = @RowCount + @ROW
WHILE @RowCount < @RowMax 
BEGIN
SET @RowString = CAST(@RowCount AS INT) 
DECLARE @FNAME VARCHAR(30)
SET @FNAME = (SELECT CAST(CAST(newid() as binary(16)) as varchar(8)))
DECLARE @num1 VARCHAR(20)
SET @num1 = (SELECT CONVERT(INT, (2000+1)*RAND()))
DECLARE @name1 VARCHAR(50)
SET @name1 = @FNAME +'_'+ @num1;
--SELECT @name1;
DECLARE @LNAME VARCHAR(30)
SET @LNAME = (SELECT CAST(CAST(newid() as binary(16)) as varchar(8)))
DECLARE @num2 VARCHAR(20)
SET @num2 = (SELECT CONVERT(INT, (2000+1)*RAND()))
DECLARE @name2 VARCHAR(50)
SET @name2 = @LNAME +'_'+ @num2;
--SELECT @name2;
DECLARE @ADDRESS VARCHAR(200)
SET @ADDRESS = (SELECT CAST(CAST(newid() as binary(16)) as varchar(50)))
--SELECT @ADDRESS
DECLARE @SID INT
SET @SID = (REPLICATE('0', 10 - DATALENGTH(@RowString)) + @RowString)
--SELECT @SID;
DECLARE @PID INT
SET @PID = (SELECT CONVERT(INT, (200000+1)*RAND()))
--SELECT @PID;
DECLARE @SALARY INT
SET @SALARY = (SELECT CONVERT(INT, (200000+1)*RAND()))
DECLARE @SQL2 NVARCHAR(MAX)
SET @SQL2 = N'INSERT INTO '+@TAB+ N' VALUES(
@SID11,@PID11,@name11,@name22,@ADDRESS11,@SALARY11);'
DECLARE @PARAM NVARCHAR(MAX)
SET @PARAM = N'@SID11 INT,@PID11 INT,@name11 VARCHAR(50),@name22 VARCHAR(50),@ADDRESS11 VARCHAR(50),@SALARY11 INT'
EXEC sp_executesql @SQL2, @PARAM, @SID11 = @SID, @PID11 = @PID, 
@name11 = @name1, @name22 = @name2, @ADDRESS11 = @ADDRESS, @SALARY11 = @SALARY 
--EXEC sp_executesql @SQL2
SET @RowCount = @RowCount + 1 
END
GO

Let’s create a global temporary table.

--Create a Global temp table 
USE [AdventureWorks2016]
GO
IF OBJECT_ID('Tempdb..##NewGlobalTempTable','U') IS NOT NULL
DROP TABLE  ##NewGlobalTempTable
GO
CREATE TABLE ##NewGlobalTempTable(
    [SID] [bigint] NOT NULL,
    [PID] [int] NOT NULL,
    [FNAME] [varchar](50) NOT NULL,
    [LNAME] [varchar](50) NOT NULL,
    [ADDRESS] [varchar](max) NOT NULL,
    [SALARY] [int] NOT NULL)
GO

Let’s populate the global temporary table.

   
--Execute the below-stored procedure to populate data in ##NewGlobalTempTable table
USE tempdb
GO
EXEC sp_populate_data ##NewGlobalTempTable,100000

Now let’s check the tempdb usage. 

USE [tempdb];
GO

SELECT GETDATE() AS runtime
    ,SUM(user_object_reserved_page_count) * 8 AS usr_obj_kb
    ,SUM(internal_object_reserved_page_count) * 8 AS internal_obj_kb
    ,SUM(version_store_reserved_page_count) * 8 AS version_store_kb
    ,SUM(unallocated_extent_page_count) * 8 AS freespace_kb
    ,SUM(mixed_extent_page_count) * 8 AS mixedextent_kb
FROM sys.dm_db_file_space_usage

USE [tempdb]
GO

SELECT [name] AS [Logical Name]
    ,size / 128.0 AS [Total Size in MB]
    ,size / 128.0 - CAST(FILEPROPERTY(name, 'SpaceUsed') AS INT) / 128.0 AS [Available Space In MB]
FROM sys.database_files

It is observed that the size of the user object increases to about 28MB, Available Space drops below 50MB, while the size of the data file remains unchanged. This is because there was already enough space available in the data file to be consumed.

tempdb2

Performing this operation multiple times will lead to SQL Server running out of Available Space completely, which will require the data file to be grown.

Next, the space usage of internal objects is analyzed using a few CROSS JOIN operations running in a loop. For this sort operation to execute successfully, a work table needs to be created internally, to store the intermediate results of the sort operation. This will cause the size of the data file to grow.

USE AdventureWorks2014;
GO

WHILE (1 = 1)
BEGIN
    DBCC DROPCLEANBUFFERS

    SELECT TOP (100000) a.*
    FROM master..spt_values a
        ,master..spt_values b
        ,master..spt_values C
    ORDER BY a.number DESC
        ,b.number DESC
        ,C.number DESC
END;
GO

Let’s check the tempdb usage again (execute the script again).

This leads to the internal object occupying more space and causing the data file to grow further. Keep executing the script to see the increase in space.

tempdb3

Once the workload has stopped executing, there is no longer a requirement for the work table that was created. As a result, the size of the internal object will be greatly reduced, while the amount of space available will increase.

tempdb4

It is important to note that the size of the data file will not reduce automatically. It needs to be shrunk manually to recover space on the disk.

The final part of this blog deals with the version store. To watch this in action, first READ COMMITED SNAPSHOTS need to be enabled in AdventureWorks2014. This will allow SQL Server to store the last committed values/records for a given transaction in the version store object.

--Enable READ COMMITED SNAPSHOT on database PWI_TEMPDBFULL
USE [master]
GO
ALTER DATABASE [AdventureWorks2016] SET READ_COMMITTED_SNAPSHOT ON WITH NO_WAIT
GO

Currently, our version store is empty, holding no data. This can be verified using the DMV- sys.dm_tran_version_store. 
SELECT * FROM sys.dm_tran_version_store
Let’s create a table for this demo.
USE [AdventureWorks2014]
GO
CREATE TABLE OpenTran (NAME VARCHAR(50));
GO
INSERT INTO OpenTran VALUES('SQLMaestros');
GO
SELECT * FROM OpenTran
GO

Now, let’s update the table in an in-flight transaction with no commits or rollbacks.

USE [AdventureWorks2014]
GO

BEGIN TRAN
UPDATE [OpenTran]
SET [NAME] = 'TEST'

Having done so, running sys.dm_tran_version_store, there is one record in the version store which is the last committed image of the record consuming 64KB of space on the disk. In the case of a mass update, this value will be much higher.

SELECT * FROM sys.dm_tran_version_store

tempdb5

Now let’s issue the ROLLBACK.

ROLLBACK

Keep executing the query a few times and observe that the record gets cleared in some time.

SELECT * FROM sys.dm_tran_version_store

 

   

About Amit Bansal

Amit Bansal is always brainstorming around SQL Server. Despite working with SQL since 1997, he is amazed that he keeps learning new things every single day. SQL Server is AB's first love, and his wife does not mind that. He tries to share as much and spreads the SQL goodness. Internals and Performance Tuning excites him, and also gives him sleepless nights at times, simply because he is not a genius, but quite a hard worker and does not give up. It has been a long and exciting journey since 1997, you can read here: http://sqlmaestros.com/amit-bansal/ He is on Twitter: https://www.twitter.com/A_Bansal

View all posts by Amit Bansal →

Leave a Reply

Your email address will not be published.