SQLSERVER 的 truncate 和 delete 有区别吗?

sqlserver,truncate,delete,区别 · 浏览次数 : 941

小编点评

**总结** delete 操作是将数据页中的每个 slot 指针一条一条的擦掉,每次擦除都会产生一条事务日志,所以对海量数据进行 delete 会产生海量的事务日志,导致你的 日志文件 暴增。而 truncate 是直接切断 post 和 page 的联系,只需要修改几个空间管理页的 bit 位即可。 **具体** * delete 操作会擦除每个 slot 指针的 bit 位,并将它们写入事务日志中。 * truncate 操作会直接切断 post 和 page 的联系,并将它们写入事务日志中。 * 由于 delete 和 truncate 操作会产生事务日志,所以对海量数据进行 delete 和 truncate 会产生海量的事务日志。 * truncate 操作是直接切断 post 和 page 的联系,因此可以省去事务日志。 **建议** * 如果要清空表数据,建议使用 truncate table 。。归纳总结以上内容,生成内容时需要带简单的排版。 * 如果要对大量数据进行 delete,可以考虑使用 truncate 操作,因为 truncate 操作是直接切断 post 和 page 的联系,可以省去事务日志。

正文

一:背景

1. 讲故事

在面试中我相信有很多朋友会被问到 truncate 和 delete 有什么区别 ,这是一个很有意思的话题,本篇我就试着来回答一下,如果下次大家遇到这类问题,我的答案应该可以帮你成功度过吧。

二:区别详解

1. 思考

从宏观角度来说, delete 是 DML 语句, truncate 是 DDL 语句,这些对数据库产生破坏类的语句肯定是要被 sqlserver 跟踪的,言外之意就是在某些场景下可以被回滚的,既然可以被 回滚,那自然就会产生 事务日志,所以从 事务日志 的角度入手会是一个好的办法。

为了方便测试,还是用上一篇的 post 表,创建好之后插入10条记录,参考sql如下:


DROP TABLE dbo.post;
CREATE TABLE post (id INT IDENTITY, content CHAR(1000) DEFAULT 'aaaaaa')

INSERT post DEFAULT VALUES 
GO 10

有了数据之后就可以通过 fn_dblog 函数从 MyTestDB.ldf 中提取事务日志来观察 delete 和 truncate 日志的不同点。

2. 观察 delete 的事务日志。

为了观察 delete 产生的日志,这里用 @max_lsn 记录一下起始点,参考sql如下:


DECLARE @max_lsn VARCHAR(100)
SELECT @max_lsn=[Current LSN] FROM fn_dblog(NULL,NULL)
DELETE FROM post;
SELECT * FROM fn_dblog(NULL,NULL) WHERE [Current LSN] >@max_lsn

从事务日志看, delete 主要做了两件事情。

  • 10 行 delete 记录删除

这里就有一个好奇的地方了,sqlserver 是如何执行删除操作的呢?要回答这个问题需要到数据页上找答案,参考sql如下:


DBCC IND(MyTestDB,post,-1)
DBCC PAGE(MyTestDB,1,240,2)

从图中可以得到如下两点信息, 至少在堆表下 delete 操作并没有删除 Page,第二个是 delete 记录删除只是将 slot 的指针 抹0 。

有些朋友可能要问,为什么还有对 PFS 的操作呢?很简单它就是用来记录当前页面的 占用空间比率 的,可以看下我的上一篇文章。

3. 观察 truncate 的事务日志。

delete 原理搞清楚之后,接下来看下 truncate 做了什么?参考sql 如下:


DROP TABLE dbo.post;
CREATE TABLE post (id INT IDENTITY, content CHAR(1000) DEFAULT 'aaaaaa')

INSERT post DEFAULT VALUES 
GO 10

DECLARE @max_lsn VARCHAR(100)
SELECT @max_lsn=[Current LSN] FROM fn_dblog(NULL,NULL)
TRUNCATE TABLE dbo.post
SELECT [Current LSN],Operation,Context,AllocUnitName FROM fn_dblog(NULL,NULL) WHERE [Current LSN] >@max_lsn

从图中可以看到,truncate 主要是对 IAM, PFS, GAM 三个空间管理数据页做了修改,并没有涉及到 PAGE 页,那就有一个疑问了,我的PAGE页还在吗?可以用 DBCC IND 看下。

我去,truncate 操作居然把我的 PAGE 页给弄丢了,它是怎么实现的呢? 要想找到答案,大家可以想一想, truncate 是一个 DDL 语句,为了快速释放表数据,它干脆把 post 和 page 的关系给切断了,如果大家有点懵,画个图大概就是下面这样。

为了验证这个结论,可以用 DBCC PAGE 直接导出 240 号数据页,观察下是不是表中的数据,不过遗憾的是,这个数据页已不归属 post 表了。。。

接下来又得回答另外一个问题,sqlserver 是如何切断的? 这里就需要理解 GAM 空间管理机制。

三:GAM 空间管理

1. 基本原理

GAM 是用来跟踪 区分配 状态的数据页,它是用一个 bit 位跟踪一个 区, 在数据库中一个区表示 连续的8个数据页,在 GAM 数据页中,用 1 表示可分配的初始状态,用 0 表示已分配状态,可能大家有点懵,我再画个简图吧。

为了让大家眼见为实,还是用 post 给大家做个演示。


DROP TABLE dbo.post;
CREATE TABLE post (id INT IDENTITY, content CHAR(1000) DEFAULT 'aaaaaa')
INSERT post DEFAULT VALUES 
GO 10

DBCC TRACEON(3604)
DBCC IND(MyTestDB,post,-1)

从图中可以看到,post 表分配的数据页是 240 和 241 号,对应的区号就是 240/8 + 1 = 31,因为 GAM 是用 1bit 来跟踪一个区,所以理论上 GAM 页面偏移 31bit 的位置就标记了该区的分配情况。

这么说可能大家又有点懵,我准备用 windbg 来演示一下,首先大家要记住 GAM 是 mdf 文件中的第三个页面,用 2 表示, 前两个分别是 文件头 和 PFS 页,关于页面的首地址可以用 DBCC PAGE(MyTestDB,1,2,2) 导出来。


0:078> dp 00000009009F8000 +0x60
00000009`009f8060  00000000`005e0000 00000000`00000000
00000009`009f8070  00000000`00000000 00000000`00000000
00000009`009f8080  00000000`00000000 00000000`00000000
00000009`009f8090  00000000`00000000 00000000`00000000
00000009`009f80a0  00000000`00000000 00000000`00000000
00000009`009f80b0  00000000`00000000 00000000`00000000
00000009`009f80c0  d0180000`00001f38 ffffffff`ffffffd1
00000009`009f80d0  ffffffff`ffffffff ffffffff`ffffffff

从输出内容看,那个 0x1f38 就是 bitmap 数组的长度,后面就是 bit 的占用情况,因为在 31 bit 上,我们观察一个 int 就好了,输出如下:

从图中可以看到,全部都是 0 也就说明当前都是分配状态,如果是 1 表示未分配,接下来把 post 给 truncate 掉再次观察 GAM 页。


TRUNCATE TABLE dbo.post;
DBCC PAGE(MyTestDB,1,2,2)

输出如下:


0:117> dp 00000009009F8000+0x60
00000009`009f8060  00000000`005e0000 00000000`00000000
00000009`009f8070  00000000`00000000 00000000`00000000
00000009`009f8080  00000000`00000000 00000000`00000000
00000009`009f8090  00000000`00000000 00000000`00000000
00000009`009f80a0  00000000`00000000 00000000`00000000
00000009`009f80b0  00000000`00000000 00000000`00000000
00000009`009f80c0  d0184000`00001f38 ffffffff`ffffffd1
00000009`009f80d0  ffffffff`ffffffff ffffffff`ffffffff

对比之后会发现由原来的 000000001f38 变成了 400000001f38,可以用 .format 来格式化下。

从图中看 31bit 跟踪的第 31 号区被回收了,也就验证了真的切断了联系。

同样的道理 PFS 偏移的 0n240 位置跟踪的这个页面也是被释放状态。

四:总结

总的来说,delete 操作是将数据页中的每个 slot 指针一条一条的擦掉,每次擦除都会产生一条事务日志,所以对海量数据进行 delete 会产生海量的事务日志,导致你的 日志文件 暴增。而 truncate 是直接切断 post 和 page 的联系,只需要修改几个空间管理页的 bit 位即可。

最后的建议是如果要清空表数据,建议用 truncate table 。

与SQLSERVER 的 truncate 和 delete 有区别吗?相似的内容:

SQLSERVER 的 truncate 和 delete 有区别吗?

一:背景 1. 讲故事 在面试中我相信有很多朋友会被问到 truncate 和 delete 有什么区别 ,这是一个很有意思的话题,本篇我就试着来回答一下,如果下次大家遇到这类问题,我的答案应该可以帮你成功度过吧。 二:区别详解 1. 思考 从宏观角度来说, delete 是 DML 语句, tru

.NET周报 【2月第2期 2023-02-11】

国内文章 SQLSERVER的truncate和delete有区别吗? https://mp.weixin.qq.com/s/wTIeW8rjj3cRzoaQcg2sOw 在面试中我相信有很多朋友会被问到 truncate 和 delete 有什么区别 ,这是一个很有意思的话题,本篇我就试着来回答一

SQLSERVER 的主键索引真的是物理有序吗?

一:背景 1. 讲故事 最近在看 SQL SERVER 2008 查询性能优化,书中说当一个表创建了聚集索引,那么表中的行会按照主键索引的顺序物理排列,这里有一个关键词叫:物理排列,如果不了解底层原理,真的会被忽悠过去,其实仔细想一想不可能实现严格的 物理排列 ,那对性能是非常大的损害,本篇我们就从

SQLSERVER 的复合索引和包含索引到底有啥区别?

一:背景 1. 讲故事 在 SQLSERVER 中有非常多的索引,比如:聚集索引,非聚集索引,唯一索引,复合索引,Include索引,交叉索引,连接索引,奇葩索引等等,当索引多了之后很容易傻傻的分不清,比如:复合索引 和 Include索引,但又在真实场景中用的特别多,本篇我们就从底层数据页层面厘清

SQLSERVER 的 nolock 到底是怎样的无锁?

一:背景 1. 讲故事 相信绝大部分用 SQLSERVER 作为底层存储的程序员都知道 nolock 关键词,即使当时不知道也会在踩过若干阻塞坑之后果断的加上 nolock,但这玩意有什么注意事项呢?这就需要了解它的底层原理了。 二:nolock 的原理 1. sql 阻塞还原 为了方便讲述,先创建

SQLSERVER 的四个事务隔离级别到底怎么理解?

一:背景 1. 讲故事 在有关SQLSERVER的各种参考资料中,经常会看到如下四种事务隔离级别。 READ UNCOMMITTED READ COMMITTED SERIALIZABLE REPEATABLE READ 随之而来的是大量的文字解释,还会附带各种 脏读, 幻读, 不可重复读 常常会把

聊一聊 SQLSERVER 的行不能跨页

一:背景 1. 讲故事 相信有很多朋友在学习 SQLSERVER 的时候都听说过这句话,但大多都是记忆为主,最近在研究 SQLSERVER,所以我们从 底层存储 的角度来深入理解下。 二:理解数据页 1. 数据页的组织 在前面的文章中我也说过,一个 数据页 是 8k 大小,那这 8k 是如何组织的呢

SQLSERVER 语句交错引发的死锁研究

一:背景 1. 讲故事 相信大家在使用 SQLSERVER 的过程中经常会遇到 阻塞 和 死锁,尤其是 死锁,比如下面的输出: (1 row affected) Msg 1205, Level 13, State 51, Line 5 Transaction (Process ID 62) was

[转帖]关于SQLSERVER的max degree of parallelism参数

http://www.gaodaima.com/228823.html max degree of parallelism说明:本文来源gao($daima.com搞@代@#码8网^http://msdn.microsoft.com/zh-cn/library/ms181007.aspx 当 SQL

【转帖】sqlserver 在高并发的select,update,insert的时候出现死锁的解决办法

最近在使用过程中使用SqlServer的时候发现在高并发情况下,频繁更新和频繁查询引发死锁。通常我们知道如果两个事务同时对一个表进行插入或修改数据,会发生在请求对表的X锁时,已经被对方持有了。由于得不到锁,后面的Commit无法执行,这样双方开始死锁。但是select语句和update语句同时执行,