Share via


Miss-understanding of Truncate Command


Introduction

Recently I been interviewing for senior level DBA for our company. One of the technical questions that I asked all the candidates was "Can TRUNCATE command be roll backed". To my surprise, none of the candidate said Yes.  I believe there is a miss understanding that DELETE can be rollbacked but not TRUNCATE command. In this article, I want to show that even TRUNCATE command is logged in transaction log and can be rollbacked.

Let's create a new database for the demonstration purpose. 

USE MASTER
GO
 
CREATE DATABASE  [test]
 
GO
USE [test]
GO

Create a test table and populate it.

CREATE TABLE  VTTEST(id INT  IDENTITY, data char(10))
GO
INSERT INTO  VTTEST(DATA) VALUES('TEST')
GO 10 
CHECKPOINT

Issue the above command couple of times to clear  the log.

  

 Caution
Before I begin I would like to point out  fn_dblog() is an undocumented function introduced in SQL Server 2005. This function is used to return active (or un-truncated) part of transaction log file. To read the transaction log we need to use a function fn_dblog(). Undocumented command is not supported by Microsoft and should not be used on production database in any case.

Implicit Transaction

Open a new query window and issue following command

TRUNCATE TABLE vttest

Open a new query window and execute the following

SELECT
 [Current LSN], [Operation], [Context],
 [Transaction ID], [Transaction Name], [Description]
FROM fn_dblog (NULL, NULL)

Current LSN

Operation

Context

Transaction Name

Description

00000025:0000007e:0001

LOP_BEGIN_XACT

LCX_NULL

TRUNCATE TABLE

TRUNCATE TABLE;0x01050000000000051500000079762b0f9908d06414787b49a42d0000

00000025:0000007e:0002

LOP_LOCK_XACT

LCX_NULL

NULL

00000025:0000007e:0003

LOP_MODIFY_ROW

LCX_IAM

NULL

00000025:0000007e:0004

LOP_MODIFY_ROW

LCX_PFS

NULL

Deallocated 0001:000000b1

00000025:0000007e:0005

LOP_MODIFY_ROW

LCX_PFS

NULL

Deallocated 0001:000000b2

00000025:0000007e:0006

LOP_INVALIDATE_CACHE

LCX_NULL

NULL

cache invalidation (cache: IAMPageRangeCache, keys: allocUnitId: 72057594045792256). 

00000025:0000007e:0007

LOP_COUNT_DELTA

LCX_CLUSTERED

NULL

Action 0 (HOBTCOUNT) on rowset 72057594040549376. Leaf page count: 1, Reserved page count: 2, Used page count: 2

00000025:0000007e:0008

LOP_COUNT_DELTA

LCX_CLUSTERED

NULL

Action 1 (ROWSETCOUNT) on rowset 72057594040549376. Row count: 20.

00000025:0000007e:0009

LOP_COUNT_DELTA

LCX_CLUSTERED

NULL

Action 2 (ROWSETCOLUMNCOUNT) on rowset 72057594040549376. Column Id: 1, mod count: 360

00000025:0000007e:000a

LOP_COUNT_DELTA

LCX_CLUSTERED

NULL

Action 2 (ROWSETCOLUMNCOUNT) on rowset 72057594040549376. Column Id: 2, mod count: 360

00000025:0000007e:000b

LOP_HOBT_DDL

LCX_NULL

NULL

Action 2 (ALTER_HOBT) on HoBt 0x28:100, partition 0x0, rowset 72057594040549376.

00000025:0000007e:000c

LOP_MODIFY_ROW

LCX_CLUSTERED

NULL

00000025:0000007e:000d

LOP_HOBT_DDL

LCX_NULL

NULL

Action 2 (ALTER_HOBT) on HoBt 0x28:100, partition 0x0, rowset 72057594040549376.

00000025:0000007e:000e

LOP_MODIFY_ROW

LCX_CLUSTERED

NULL

00000025:0000007e:000f

LOP_MODIFY_ROW

LCX_CLUSTERED

NULL

00000025:0000007e:0010

LOP_COMMIT_XACT

LCX_NULL

NULL

As you can see in the above table that TRUNCATE command is executed with in implicit transaction (LOP_BEGIN_XACT,LOP_COMMIT_XACT) and is logged in transactional log as well.

Explicit Transaction

Let's see what happens when TRUNCATE table is issued with an explicit transaction on.  Execute CHECKPOINT command couple of times to clear the log the execute following command

INSERT INTO  VTTEST(DATA) VALUES('TEST')
GO 10
 
CHECKPOINT
 
BEGIN TRAN VTTEST
TRUNCATE TABLE  VTTEST
 
ROLLBACK

Current LSN

Operation

Context

Transaction ID

Transaction Name

Description

00000025:000000da:0001

LOP_BEGIN_CKPT

LCX_NULL

0000:00000000

NULL

00000025:000000db:0001

LOP_XACT_CKPT

LCX_BOOT_PAGE_CKPT

0000:00000000

NULL

00000025:000000dc:0001

LOP_END_CKPT

LCX_NULL

0000:00000000

NULL

log_minRecoveryLsn 00000025:000000da:0001;

log_replbeginlsn 00000000:00000000:0000;

log_replnextlsn 00000000:00000000:0000;

log_distbackuplsn 00000000:00000000:0000;

log_distlastlsn 00000000:00000000:0000

00000025:000000dd:0001

LOP_BEGIN_XACT

LCX_NULL

0000:0000043e

VTTEST

VTTEST;

0x01050000000000051500000079762

b0f9908d06414787b49a42d0000

00000025:000000dd:0002

LOP_LOCK_XACT

LCX_NULL

0000:0000043e

NULL

00000025:000000dd:0003

LOP_MODIFY_ROW

LCX_IAM

0000:0000043e

NULL

00000025:000000dd:0004

LOP_MODIFY_ROW

LCX_PFS

0000:0000043e

NULL

Deallocated 0001:000000b1

00000025:000000dd:0005

LOP_MODIFY_ROW

LCX_PFS

0000:0000043e

NULL

Deallocated 0001:000000b2

00000025:000000dd:0006

LOP_INVALIDATE_CACHE

LCX_NULL

0000:0000043e

NULL

cache invalidation (cache: IAMPageRangeCache, keys: allocUnitId: 72057594045792256). 

00000025:000000dd:0007

LOP_COUNT_DELTA

LCX_CLUSTERED

0000:00000000

NULL

Action 0 (HOBTCOUNT) on rowset 72057594040549376.

Leaf page count: 1, Reserved page count: 2, Used page count: 2

00000025:000000dd:0008

LOP_COUNT_DELTA

LCX_CLUSTERED

0000:00000000

NULL

Action 1 (ROWSETCOUNT) on rowset 72057594040549376.

Row count: 20.

00000025:000000dd:0009

LOP_COUNT_DELTA

LCX_CLUSTERED

0000:00000000

NULL

Action 2 (ROWSETCOLUMNCOUNT) on rowset 72057594040549376.

Column Id: 1, mod count: 410

00000025:000000dd:000a

LOP_COUNT_DELTA

LCX_CLUSTERED

0000:00000000

NULL

Action 2 (ROWSETCOLUMNCOUNT) on rowset 72057594040549376.

Column Id: 2, mod count: 410

00000025:000000dd:000b

LOP_HOBT_DDL

LCX_NULL

0000:0000043e

NULL

Action 2 (ALTER_HOBT) on HoBt 0x28:100, partition 0x0, rowset 72057594040549376.

00000025:000000dd:000c

LOP_MODIFY_ROW

LCX_CLUSTERED

0000:0000043e

NULL

00000025:000000dd:000d

LOP_HOBT_DDL

LCX_NULL

0000:0000043e

NULL

Action 2 (ALTER_HOBT) on HoBt 0x28:100, partition 0x0, rowset 72057594040549376.

00000025:000000dd:000e

LOP_MODIFY_ROW

LCX_CLUSTERED

0000:0000043e

NULL

00000025:000000dd:000f

LOP_MODIFY_ROW

LCX_CLUSTERED

0000:0000043e

NULL

00000025:000000dd:0010

LOP_MODIFY_ROW

LCX_CLUSTERED

0000:0000043e

NULL

COMPENSATION

00000025:000000dd:0011

LOP_MODIFY_ROW

LCX_CLUSTERED

0000:0000043e

NULL

COMPENSATION

00000025:000000dd:0012

LOP_HOBT_DDL

LCX_NULL

0000:0000043e

NULL

COMPENSATION;Action 2 (ALTER_HOBT) on HoBt 0x28:100,

partition 0x0, rowset 72057594040549376.

00000025:000000dd:0013

LOP_MODIFY_ROW

LCX_CLUSTERED

0000:0000043e

NULL

COMPENSATION

00000025:000000dd:0014

LOP_HOBT_DDL

LCX_NULL

0000:0000043e

NULL

COMPENSATION;Action 2 (ALTER_HOBT) on HoBt 0x28:100,

partition 0x0, rowset 72057594040549376.

00000025:000000dd:0015

LOP_MODIFY_ROW

LCX_PFS

0000:0000043e

NULL

COMPENSATION;Allocated 0001:000000b2

00000025:000000dd:0016

LOP_MODIFY_ROW

LCX_PFS

0000:0000043e

NULL

COMPENSATION;Allocated 0001:000000b1

00000025:000000dd:0017

LOP_MODIFY_ROW

LCX_IAM

0000:0000043e

NULL

COMPENSATION

00000025:000000dd:0018

LOP_ABORT_XACT

LCX_NULL

0000:0000043e

NULL

The red color part are the log entries for   

BEGIN TRAN VTTEST

TRUNCATE TABLE VTTEST

and the green color are the log entries for 

ROLLBACK

As you can see in the red section the data page got deallocated and in the green section the data pages are getting allocated back up on rollback.

Conclusion

From this it is clear that TRUNCATE Command is a logged operation and can be rollbacked.

Reference

http://www.sqlservercentral.com/blogs/livingforsqlserver/2012/11/19/time-pass-with-transaction-log-part-3-fn-dblog/