sql truncate

This article gives you an insight into the SQL Truncate and SQL Delete commands behavior.

本文使您深入了解SQL截断和SQL删除命令的行为。

介绍 ( Introduction )

In the article Difference between SQL Truncate and SQL Delete statements in SQL Server , we learned the difference between truncate and delete statements. You should go through this article before going further with this article.

SQL Server中SQL Truncate和SQL Delete语句 之间的区别一文中,我们了解了truncate和delete语句之间的区别。 在继续本文之前,您应该先阅读本文。

In this article, we will look at the following topics.

在本文中,我们将研究以下主题。

  • Can you Rollback a delete transaction and a truncate command?

    您可以回滚删除事务和截断命令吗?

  • Which one is faster, Truncate or Delete and why?

    截断或删除哪个更快,为什么?

回滚删除事务 ( Rollback a delete transaction )

Let’s say you started two separate transactions – delete and truncate. Later, we want to roll back this transaction. We issued a rollback command for this.

假设您启动了两个单独的事务–删除和截断。 稍后,我们要回滚此事务。 为此,我们发出了回滚命令。

Now, a question for you – Does rollback command recovers data for both delete and truncate? You might think we cannot rollback a truncate statement. Let’s explore this practically.

现在,您遇到了一个问题– rollback命令是否同时恢复删除和截断的数据? 您可能会认为我们无法回滚截断语句。 让我们实践一下。

准备数据库环境 ( Prepare a database environment )

Let’s create a new database [SQLShackDemo] for this article, along with a sample data table:

让我们为本文创建一个新的数据库[SQLShackDemo],以及一个示例数据表:

CREATE DATABASE SQLShackDemo;
USE SQLShackDemo;
CREATE TABLE test
(id     INT IDENTITY(1, 1), 
 [Name] VARCHAR(10)
INSERT INTO test([Name])
VALUES('SampleData');
GO 10
 

We have 10 records in the test table for demo purposes:

测试表中有10条记录用于演示:

We can use dynamic management view sys.dm_db_database_page_allocations(starting from SQL Server 2012) to list database pages belonging to the SQL table. This DMV takes the following arguments:

我们可以使用动态管理视图sys.dm_db_database_page_allocations(从SQL Server 2012开始)列出属于SQL表的数据库页面。 该DMV采用以下参数:

  • Database id: We can specify database id or use DB_ID() function to pass the current database id 数据库ID:我们可以指定数据库ID或使用DB_ID()函数传递当前数据库ID
  • Object id: This parameter takes object id we want to analyze. It is good to use OBJECT_ID(‘Object’) for automatically supplying this value 对象ID:此参数采用我们要分析的对象ID。 最好使用OBJECT_ID('Object')自动提供此值
  • The next two arguments are Index id and partition id. If we specify NULL values, it returns information about all indexes and partitions

    接下来的两个参数是索引ID和分区ID。 如果我们指定NULL值,它将返回有关所有索引和分区的信息

  • In the last argument, we can specify LIMITED or DETAILED values. Let’s use DETAILED as it gives additional information compared to the LIMITED option output

    在最后一个参数中,我们可以指定LIMITED或Detailed值。 让我们使用Detailed,因为它提供了与LIMITED选项输出相比的更多信息

SELECT allocated_page_file_id AS PageFID, 
       allocated_page_page_id AS PagePID, 
       allocated_page_iam_file_id AS IAMFID, 
       allocated_page_iam_page_id AS IAMPID, 
       object_id AS ObjectID, 
       index_id AS IndexID, 
       partition_id AS PartitionNumber, 
       rowset_id AS PartitionID, 
       allocation_unit_type_desc AS iam_chain_type, 
       page_type AS PageType, 
       page_level AS IndexLevel, 
       next_page_file_id AS NextPageFID, 
       next_page_page_id AS NextPagePID, 
       previous_page_file_id AS PrevPageFID, 
       previous_page_page_id AS PrevPagePID
FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID('test'), NULL, NULL, 'DETAILED')
WHERE is_allocated = 1;
 

Alternatively, we can use DBCC IND command as well and it returns almost the similar output:

另外,我们也可以使用DBCC IND命令,它返回几乎类似的输出:

DBCC IND('SQLShackDemo','test',1)
 

In the output, we can see two pages with PagePID 94 and 256:

在输出中,我们可以看到两个页面的页面PID为94和256:

  • PagePID 94: It is an IAM (Index Allocation Map) page. The IAM page has NULL values for IAMFID and IAMPID columns. PageType 10 denotes IAM page PagePID 94:这是一个IAM(索引分配图)页面。 IAM页的IAMFID和IAMPID列的值为NULL。 PageType 10表示IAM页面
  • PagePID 256: It is a data page. PageType 1 denotes a data page PagePID 256:这是一个数据页。 PageType 1表示数据页

Now, let’s begin a delete transaction using the begin transaction statement:

现在,让我们使用begin transaction语句开始删除事务:

USE SQLShackDemo;
BEGIN TRANSACTION;
DELETE FROM [dbo].[test];
 

Since we did not specify the WHERE clause in the delete statement, it removes all ten rows from the test table:

由于我们未在delete语句中指定WHERE子句,因此它将删除测试表中的所有十行:

Do not commit the transaction as of now. We use the undocumented function fn_dblog to read transaction logs. You can refer to this article, How to continuously read Transaction log file data directly in a SQL Server database with fn_dblog and fn_dump_dblog to go through this command in detail.

到目前为止,请勿提交事务。 我们使用未记录的函数fn_dblog读取事务日志。 您可以参考本文, 如何使用fn_dblog和fn_dump_dblog直接直接在SQL Server数据库中连续读取事务日志文件数据,以详细了解此命令。

  • Note: You should be careful using this function in the production environment. Here, we are using it for demonstration purposes.

    注意:您应该在生产环境中小心使用此功能。 在这里,我们将其用于演示目的。

In the same query window with existing Begin SQL transaction, execute the following command of fn_dblog. This query filters the transaction logs for delete transaction ( defined as test1 in begin transaction statement):

在与现有Begin SQL事务相同的查询窗口中,执行以下fn_dblog命令。 此查询过滤用于删除事务的事务日志(在begin transaction语句中定义为test1):

USE SQLShackDemo;
CHECKPOINT;
BEGIN TRANSACTION Test1;
DELETE FROM [dbo].[test];
SELECT [Current LSN], 
       [transaction ID] tranID, 
       [end time] endTime, 
       AllocUnitId, 
       Description, 
       operation, 
       Context
FROM ::fn_dbLog(NULL, NULL)
WHERE [Transaction ID] IN
    SELECT [Transaction ID]
    FROM fn_dbLog(NULL, NULL)
    WHERE [Transaction Name] = 'Test1'
 

SQL delete statement removes individual row and logs entry for each row in the transaction log. We removed 10 rows from the test table, and output also reflects LOB_DELETE_ROWS entry for an individual row. This LOP_DELETE_ROWS operation occurs for the delete statement. We can also see a total of 11 rows in the output of fn_dblog for this delete operation:

SQL delete语句删除单个行,并在事务日志中为每一行记录日志条目。 我们从测试表中删除了10行,并且输出还反映了单个行的LOB_DELETE_ROWS条目。 LOP_DELETE_ROWS操作发生在delete语句中。 我们还可以在fn_dblog的输出中看到此删除操作共有11行:

Execute the select statement in the same transaction, and it returns zero in the output:

在同一事务中执行select语句,并在输出中返回零:

Select count(*) from [dbo].[test];
 

Now, issue a rollback statement and check for the number of rows in the test table. It returns 10 rows in the output. It shows you can rollback a delete transaction:

现在,发出回滚语句并检查测试表中的行数。 它在输出中返回10行。 它显示您可以回滚删除事务:

Rollback transaction
Select count(*) from [dbo].[test];
 

回滚截断事务 (Rollback a truncate transaction)

Now, we want to check whether we can rollback a truncate statement or not. For this demo, drop the test table and recreate it with the same 10 rows from the script shared above.

现在,我们要检查是否可以回滚截断语句。 对于此演示,删除测试表并使用上面共享的脚本中的相同10行重新创建它。

Once we have an environment ready, start a new transaction and truncate table from the following script:

一旦我们准备好环境,就可以开始新事务并从以下脚本截断表:

USE SQLShackDemo;
BEGIN TRANSACTION;
TRUNCATE TABLE [dbo].[test];
 

In the following query, we started a transaction T1 and filtered output of fn_dblog for that particular transaction:

在以下查询中,我们启动了事务T1并为该特定事务过滤了fn_dblog的输出:

USE SQLShackDemo;
CHECKPOINT
BEGIN TRANSACTION T1;
TRUNCATE TABLE [dbo].[test];
SELECT [Current LSN], 
       [transaction ID] tranID, 
       [end time] endTime, 
       AllocUnitId, 
       Description, 
       operation, 
       Context
FROM ::fn_dbLog(NULL, NULL)
WHERE [Transaction ID] IN
    SELECT [Transaction ID]
    FROM fn_dbLog(NULL, NULL)
    WHERE [Transaction Name] = 'T1'
 

For SQL truncate, we do not get an entry for individual rows in the transaction log. It deallocates the extent, as shown below:

对于SQL截断,我们不会在事务日志中获得单个行的条目。 它取消分配范围,如下所示:

Let’s issue rollback transaction command and check records in a test table. We get our records back. Yes, it proves that we can rollback a SQL Truncate statement as well:

让我们发出rollback transaction命令并检查测试表中的记录。 我们得到我们的记录。 是的,它证明我们也可以回滚一条SQL Truncate语句:

SQL Server minimizes logging for truncate. To prove this point, insert more records in the test table using the following query:

SQL Server最小化了截断的日志记录。 为了证明这一点,请使用以下查询在测试表中插入更多记录:

INSERT INTO test([Name])
VALUES('SampleData');
GO 90
 

We have 100 records in the test table. Now, check the transaction log records count for both truncate and delete operations using scripts specified above.

测试表中有100条记录。 现在,使用上面指定的脚本检查事务日志记录中的截断和删除操作计数。