SQL Server索引进阶:第十三级,插入,更新,删除
在第十级到十二级中,我们看了索引的内部结构,以及改变结构造成的影响。在本文中,继续查看Insert,update,delete和merge造成的影响。首先,我们单独看一下这四个命令。
插入INSERT
当向表中插入一行数据的时候,不管表是堆表还是聚集索引表,肯定会在表的索引中插入一个入口,过滤索引除外。这么做的时候,SQL Server使用索引键的值从根页到叶子层页,到达叶子层页之后,检查页的可用空间,如果有足够的空闲空间,新的入口就会被插入适当的位置。
最终,SQL Server可能会试图向一个已经没有空间的页插入入口信息。这时候,SQL Server就会查询位置结构,找一个有空闲空间的页。一旦找到,就会做三件事,每一件都和要插入的索引键的顺序有关:
随机序列:正常情况,SQL Server会将满页的一半的入口移动的一个空页,然后将新入口插入合适的页,这就产生了两个用了一半的页。如果你的应用继续插入数据,但是不删除数据,这两个页将会从用了一半的状态变成满页,然后再被分成两个半页,然后再次变成满页,这样周而复始,循环往复。每页的充满率大概是75%。
增序序列:SQL Server发现新的入口需要插入满页的最后面,就会新建一页,然后插入这个新入口,新页再次满了的话,就再新建一页。一旦一页满了之后,他就一直是满的,所以内部碎片很小,甚至没有。
降序序列:相反,如果SQL Server发现新的入口需要插入满页的开始,也会新建一页,插入新的入口,但是由于是降序,内部碎片接近100%。
删除DELETE
当从表中删除一行的时候,对应的索引入口会从索引中删除。对每一个索引,SQL Server为了查找入口,都要从根页导航到叶子层页。一旦SQL Server发现入口,就会做两件事:立即删除入口,或者是在行的头部设置标记,使得入口变成ghost record,在适当的时候,就会删除ghost record。
Ghost record在查询的时候,会被忽略。它们只是在物理上还存在,逻辑上已经不存在了。一个索引的ghost record的数量可以通过系统函数sys.dm_db_index_physical_stats来获取。
SQL Server没有立即删除是出于性能和并发管理的需要。不仅仅是删除本身的性能,也包括随后的事务回滚性能。上面的做法使得回滚一个删除操作是很容易的,相比较从事务日志中重新创建记录而言。
下面的因素会影响删除的处理过程:
- 如果行被锁定,删除的索引会变成ghost record。
 - 如果执行的过程需要锁定5000行数据,行级别的锁会升级为表级别的锁。
 - 作为并发技术,行版本的使用,也会导致出现ghost record。
 - 直到事务完成,才会删除ghost record。
 - SQL Server的后台线程ghost-cleanup负责删除ghost record,但是,什么时候删除也是不可预期的。删除操作本身并不通知ghost-cleanup线程去这么做,随后的页扫描会将包含ghost record的页加入一个列表,ghost-cleanup线程会定期的处理这个列表。
 - ghost-cleanup线程大约每5秒钟唤醒一次。每次会清理10页。这些数字都是可以设置的。
 - 你可以通过sp_clean_db_free_space或者sp_clean_db_file_free_space来强制清理,将会删除整个数据库或者数据文件中的ghost record。
 
换句话说,当你删除数据行的时候,逻辑上讲已经删除了。如果没有被理解删除,只要SQL Server认为是安全的,他们就会被删除。
更新UPDATE
当更新表中数据行的时候,需要修改索引的入口。对于每一个索引入口,SQL Server会执行就地的更新,或者是删除再插入。只要有可能,SQL Server还是会使用就地更新。但是,也有一些情况不能就地更新,SQL Server就会执行删除紧跟着插入。下面是一些这方面的原因:
- 更新要修改键列,导致索引的入口需要重新分配。
 - 更新要修改很多列,导致入口不合适在当前页。
 - 在表上有DML的触发器。
 
如果修改的列是索引键的一部分,入口的位置肯定要变化。入口会从旧的位置删除,以新键的顺序在新的位置插入入口。大部分情况,都会再删除之后执行插入。如果新的位置和旧的位置在同一页,有可能会就地更新。SQL Server会从根页到叶子层跑上两次,一次用来查找当前的入口位置,一次用来决定入口的新位置。
如果修改的列是聚集索引的一部分,所有的非聚集索引都需要更新,因为他们的标签是由聚集索引的键组成的。
如果修改的不是索引键的一部分,入口的位置不会改变。但是,入口的大小可能会改变。如果页中不够空间存放新的入口,更新就会变成删除再插入。
合并MERGER
在SQL Server 2008中引入了合并操作,很强大,很灵活,很好。合并操作会产生插入,更新,删除语句。合并和你写insert,udpate,delete产生的效果一样,所以在本系列中没有介绍。
MERGE 目标表 USING 源表 ON 匹配条件 WHEN MATCHED THEN 语句 WHEN NOT MATCHED THEN 语句;
以上是MERGE的最最基本的语法,语句执行时根据匹配条件的结果,如果在目标表中 找到匹配记录则执行WHEN MATCHED THEN后面的语句,如果没有找到匹配记录则执行WHEN NOT MATCHED THEN后面的语句。注意源表可以是表,也可以是一个子查询语句。
格外强调一点,MERGE语句最后的分号是不能省略的!
MERGE ProductNew AS d
USING
Product
AS s
ON s.ProductID = d.ProductId
WHEN NOT MATCHED THEN
INSERT( ProductID,ProductName,Price)
VALUES(s.ProductID,s.ProductName,s.Price);
MERGE ProductNew AS d
USING
Product
AS s
ON s.ProductID = d.ProductId
WHEN NOT MATCHED THEN
INSERT( ProductID,ProductName,Price)
VALUES(s.ProductID,s.ProductName,s.Price)
WHEN MATCHED THEN
UPDATE SET d.ProductName = s.ProductName, d.Price = s.Price;
一次性更新索引Index-at-a-Time Update
当执行插入,更新,删除语句操作表的一行的时候,SQL Server肯定会修改数据,然后修改索引。在执行完插入,更新,删除数据之后,SQL Server有两个选择:
- 对每一行,执行完操作之后,都去修改索引。
 - 对每一行,执行完操作之后,对每个索引,将修改信息挂起在一个集合中。等所有的行都执行完操作之后,在执行挂起的索引修改集合。
 
第二种叫做“一次性更新索引”,是插入,更新,删除操作的一个选项。
SQL Server查询优化器将会决定采用哪一种来优化性能。如果修改的是表中的大部分行,很有可能会使用第二种。
为了证明,我们新建一张表,包含两个索引。
USE AdventureWorks;
GO
IF EXISTS (SELECT *
FROM sys.objects
WHERE name = 'FragTestII' and type = 'U')
BEGIN
DROP TABLE dbo.FragTestII;
END
GO
CREATE TABLE dbo.FragTestII
(
PKCol int not null
, InfoCol nchar(64) not null
, CONSTRAINT PK_FragTestII_PKCol primary key nonclustered (PKCol)
);
GO
CREATE INDEX IX_FragTestII_InfoCol
ON dbo.FragTestII (InfoCol);
GO
先执行一个插入一条记录的语句。
INSERT dbo.FragTestII
VALUES (100000, 'XXXX');

上图的执行计划,只是显示了插入数据的过程,没有显示索引更新的信息。这是因为,上面的情况下,索引的更新是行更新的一部分。
当时,当我们插入大量数据的时候,执行计划就会不一样了。
我们先构造一个20000条记录的FragTest表,然后将FragTest的数据批量插入FragTestII表。
CREATE TABLE dbo.FragTest
(
PKCol int IDENTITY(1,1) not null
, InfoCol nchar(64) not null
, CONSTRAINT PK_FragTest_PKCol primary key nonclustered (PKCol)
);
GO DECLARE @index INT
SET @index=0 WHILE (@index<20000)
BEGIN
INSERT INTO dbo.FragTest(InfoCol)VALUES('') SET @index=@index +1 END
INSERT dbo.FragTestII
SELECT PKCol, InfoCol
FROM dbo.FragTest;

执行计划就是上面的样子,包含很多的操作。一类操作是表中插入数据。有两个排序,每个都包含一个插入索引的操作。
尽管是一个复杂的执行计划,排序和更新挂起的索引的单独执行的,但也是一个高效的执行计划。相比随即添加索引,有顺序的添加索引,产生的碎片会更少。
结论
在索引中插入入口会导致三种碎片,这依赖于插入入口的顺序。
从索引中删除入口,包括从聚集索引中删除,可能会立即删除入口。也可能会创建ghost record使得索引入口成为逻辑删除。ghost只是存在于叶子层。SQL Server在事务完成之后,才会删除ghost record。
更新索引可能会立即就地更新,也可能是删除后在插入。如果表中没有DML的触发器,如果更新没有重新分配入口,或者增加入口的大小,通常还是会就地更新的。
如果数据修改语句影响的是大量的行,SQL Server可能会选择一次性更新索引,先修改表,然后在更新每个索引。
SQL Server索引进阶:第十三级,插入,更新,删除的更多相关文章
- SQL Server索引进阶:第三级,聚集索引
		
原文地址: Stairway to SQL Server Indexes: Level 3, Clustered Indexes 本文是SQL Server索引进阶系列(Stairway to SQL ...
 - SQL Server索引进阶:第十级,索引内部结构
		
原文地址: Stairway to SQL Server Indexes: Level 10,Index Internal Structure 本文是SQL Server索引进阶系列(Stairway ...
 - 【译】SQL Server索引进阶第八篇:唯一索引
		
原文:[译]SQL Server索引进阶第八篇:唯一索引 索引设计是数据库设计中比较重要的一个环节,对数据库的性能其中至关重要的作用,但是索引的设计却又不是那么容易的事情,性能也不是那么轻易就 ...
 - SQL Server索引进阶:第八级,唯一索引
		
原文地址: Stairway to SQL Server Indexes: Level 8,Unique Indexes 本文是SQL Server索引进阶系列(Stairway to SQL Ser ...
 - SQL Server索引进阶:第六级,标签
		
原文地址: Stairway to SQL Server Indexes: Level 6,Bookmarks 本文是SQL Server索引进阶系列(Stairway to SQL Server I ...
 - SQL Server索引进阶:第四级,页和区
		
原文地址: Stairway to SQL Server Indexes: Level 4, Pages and Extents 本文是SQL Server索引进阶系列(Stairway to SQL ...
 - SQL Server索引进阶:第九级,读懂执行计划
		
原文地址: Stairway to SQL Server Indexes: Level 9,Reading Query Plans 本文是SQL Server索引进阶系列(Stairway to SQ ...
 - SQL Server索引进阶:第七级,过滤的索引
		
原文地址: Stairway to SQL Server Indexes: Level 7,Filtered Indexes 本文是SQL Server索引进阶系列(Stairway to SQL S ...
 - SQL Server索引进阶:第五级,包含列
		
原文地址: Stairway to SQL Server Indexes: Level 5, Included Columns 本文是SQL Server索引进阶系列(Stairway to SQL ...
 - SQL Server索引进阶:第二级,深入非聚集索引
		
原文地址: Stairway to SQL Server Indexes: Level 2, Deeper into Nonclustered Indexes 本文是SQL Server索引进阶系列( ...
 
随机推荐
- mysql zk切换整个过程
			
<pre name="code" class="html">mysql master: test:/root/zk# cat zk.pl use Z ...
 - POJ——多项式的加法
			
1:多项式加法 查看 提交 统计 提问 总时间限制: 1000ms 内存限制: 5000kB 描述 我们经常遇到两多项式相加的情况,在这里,我们就需要用程序来模拟实现把两个多项式相加到一起.首先 ...
 - @Transactional  注解说明
			
先让我们看代码吧! 以下代码为在"Spring3事务管理--基于tx/aop命名空间的配置"基础上修改.首先修改applicationContext.xml如下: <pre ...
 - 使用yii中CSecurityManager的一点小技巧
			
当我们使用CSecurityManager::encrypt对字符串进行加密, 加密后的字符串是一串乱码(看起来确实像乱码, 具体是什么有待考证), 这不利于我们的下一步操作. 我们可以使用base6 ...
 - 利用PowerDesigner15在win7系统下对MySQL 进行反向project(二)
			
利用PowerDesigner15在win7系统下对MySQL 进行反向project 1.打开PowerDesigner,建立新模型.选择Physical Data Model中的Physical ...
 - mysql distinct&group by 应用
			
在使用mysql时,有时需要查询出某个字段不重复的记录,虽然mysql提供 有distinct这个关键字来过滤掉多余的重复记录只保留一条,但往往只用它来返回不重复记录的条数,而不是用它来返回不重记录的 ...
 - iOS状态栏字体设置为白色
			
info.plist 添加字段: view controller -base status bar appearence 设为NO [[UIApplication sharedApplication] ...
 - mysql安装常见问题(系统找不到指定的文件、发生系统错误 1067 进程意外终止)
			
在安装mysql时总是会遇到这样那样的问题,每次重新安装都会花很多时间来排查.在网上其实有很多相关的文章,但很多都只讲了方法,但没讲具体细节问题,导致无法解决问题.其实有时候知道问题的原因,但总是因为 ...
 - Web 应用性能提升 10 倍的 10 个建议
			
转载自http://blog.jobbole.com/94962/ 提升 Web 应用的性能变得越来越重要.线上经济活动的份额持续增长,当前发达世界中 5 % 的经济发生在互联网上(查看下面资源的统计 ...
 - uva12589
			
题目大意:给n(n<=50)个向量(xi,yi) (0<=xi<=yi<=50),选出其中k(1<=k<=n)个,从(0,0)点开始,依次首尾相连,求此k个向量与x ...