此文为翻译,由于笔者语言及技术水平有限,疏漏在所难免,欢迎建议指正。

 

原文链接:传送门

 

在之前的章节,我们看到了索引的内部结构以及这种结构的改变所带来的影响。在本章节我们继续这一主题,检查INSERT,DELETE,UPDATE和MERGE语句的影响。

首先我们分别看下这四个命令,然后我们包含一个对于所有三个命令都可用的主题:每行数据更新以及每个索引数据的更新。

插入

通过在第十一章节介绍索引碎片的主题,我们将枪口瞄准了插入语句。我们所说的东西这儿有一个概述,如果需要一个更完整的论述,以及示例代码,请参见第十一章节-索引碎片。

当一行数据被插入表的时候,不管这个表是一个堆或者是一个聚集索引,一个条目便会被插入到这个表的各个索引中(对于过滤索引可能会有所例外)。当插入索引条目的时候,SQL SERVER使用数据行的索引键值从索引的跟节点遍历到叶子节点。当它到达了叶子层级节点,它检查叶子页的可用空间,如果那个也具有足够的可用空间,新的索引条目便会插入到这个页的合适的位置。

 事实上SQL SERVER会尝试将一个索引条目插入到一个已经满了的页中。当发生这种事情的时候,SQL SERVER会查询它的存储空间分配结构来找到一个空的页,一旦找到了一个空的页,它会做如下三件事的一件,其依赖于新的索引键被插入的顺序。

      随机序列:通常,SQL SERVER从满的页里移动一半的条目到空的页。然后将新条目插入到合适的页。因此产生了两个半满的页,如果你的应用程序继续插入却并不删除数据行,这两个也实际上也会从半满增长到满,然后它们各自又会分裂为半满的页。随着时间的流逝,每个页都从今半满到满一次一次的循环,最终导致了平均页覆盖率大概为75%。

     升序序列:然而,如果SQL SERVER注意到新条目是满页的最后一个条目,它会假设数据行是以索引键相同的顺序来插入数据表,因此,它将新的数据行放置在空的页中,如果SQL SERVER的假设是正确的,数据将会以索引键的顺序到达,后续的数据行都会属这个相同的页。这个页便会被填充,导致另一个新页被分配,当一个页充满后,它便停留在充满额的状态,这样便会产生很少的内部碎片甚至没有内部碎片。

    降序顺序:相反的,如果SQL SERVER注意到新条目是所充满页的第一个条目,它便会假设数据行是以降序的顺序插入数据库表的,再一次,它将新条目放置在空的页中。此时的内部碎片和升序序列模式的内部碎片是相同的:几乎是100%。

当新条目被放置到页中,一些清理工作需要被完成,跨越三个页的四个 前一页/后一页指针肯定会被更新,并且指向新页的一个条目会被插入更高层级的索引页中,而这,反过来又会导致在那个页层级上的页分裂。

删除

当一个数据行被从表中删除,它的交互索引条目也会从索引中移除。再一次的,对于每一个索引,SQL SERVER从根节点导航到叶子叶子层级的页并且找到对应的条目。一旦SQL SERVER 找到相应的条目,它便会做如下两件事之一:要么立即移除这个条目,要么在表头中设置一个标准位(bit),使得这个条目成为索引中的幽灵记录,这种技术称为条目幽灵化(ghosting the entry),在恰当且合适的时机,幽灵记录将会被移除。这个在本章节后续会介绍。

这个表后续的所有查询将会忽略索引中幽灵列的存在,在物理上来看,幽灵列的确存在,但是逻辑上它们并不存在。索引中的幽灵列的数量可以通过sys.dm_db_index_physical_stats这个系统函数来获取到。

正是因为性能和并发管理的原因,SQL SERVER将一个条目标记为后续删除而不是立即删除它。不仅仅是DELETE语句本身的性能,还有后续的事务回滚的性能都将从幽灵化的列中受益,毕竟,通过反标记一个条目来回滚一个DELETE操作,要比通过事务日志来重建这个条目要容易的多。

是否幽灵化一个索引条目的决定,以及在它被确实删除之前流逝的时间的长度受到很多因素的影响,它们中的许多概念都不在本系列的主题之内。这些多样的因素使得我们很难预言是否一个DELETE 操作会确实移除一个条目,或者是幽灵化一个条目,亦或两个兼有。而这,反过来又使得我们很难预言一个DELETE语句对于一个索引碎片的即刻影响。

影响DELETE处理过程的因素部分列表如下所示:

      如果行级锁生效,那么将会幽灵化一个条目。

      如果在DELETE语句的执行过程中,需要5000个锁,那么锁通常便会升级为表级锁。

      行版本代替锁的使用作为并发机制,可能会导致删除行的幽灵化。

      幽灵化的记录在事务结束之前绝不会被真正移除。

      SQL SERVER的后台幽条目清理线程会移除幽灵行,然而,准确说何时它将进行清理是不可预知的。DELETE语句本身并不会通知后台幽灵清理线程一个幽灵行的存在。而是后续的页扫描将这些页加到包含幽灵列的列表中,这个列表将周期性的被清理线程所处理。

     幽灵清理线程大概每5秒钟唤醒一次,每次执行会清理10个页。这个数字可能会在后续的发布中发生改变。

    你可以通过执行sp_clean_db_free_space  或 sp_clean_db_file _free_space 这两个存储过程来强制进行幽灵列额清理。它们将会从整个数据库或者指定的数据库文件中移除幽灵列。

换句话说,当你删除数据行时,逻辑上来讲,它们不存在了。而如果它们没有被立即移除,当条件允许SQL SERVER来进行处理的时候,它们将会被彻底删除。

 一个关于幽灵记录的示例

为了演示幽灵记录,我们用上面提到的顺序插入模式来加载一个具有非聚集索引的包含20000条数据的表,这将产生一个页完全充满的表。然后,在一个未被提交的事务中,我们删除一半的表数据然后检查结果。使用 sys.dm_db_index_physical_stats这个系统视图我们将看到一些删除的数据已经被移除而另一些记录则被幽灵化,最后,我们将事务提交 并再次检查这个表,也许要检查好几次,直至幽灵记录已经被移除。

这个示例中我们做了两个变化,其中一个我们隔一行删除一行,因此会删除所有页的一半数据行,另一个我们删除条目的前一半数据行,因此会删除一半页的数据行。

在各个DELETE之后,为了查看幽灵记录以及碎片的存在,我们使用了一个与之前使用过的查询相似的查询。然而仅仅是这次,最右边的列会告诉我们索引中幽灵记录的数量,因为我们要使用这个查询来访正在被打开的事务修改的索引,我们必须在事务正在使用的相同数据库连接上执行查询。为了简短起见,我们创建一个名为 dbo.viewTestIndexInfo的视图,如列表1所示,我们在测试脚本中从这个视图中进行查询。

 

USE AdventureWorks;
GO
IF EXISTS (SELECT *
       FROM sys.objects
      WHERE name = 'viewTestIndexInfo' and type = 'V')
BEGIN
  DROP VIEW dbo.viewTestIndexInfo
END
GO
CREATE VIEW dbo.viewTestIndexInfo
AS
SELECT IX.name as 'Name'
   , PS.index_level as 'Level'
   , PS.page_count as 'Pages'
   , PS.avg_page_space_used_in_percent as 'Page Fullness (%)'
   , PS.ghost_record_count as 'Ghost Records'
 FROM sys.dm_db_index_physical_stats( db_id(), object_id('dbo.FragTest')
                   , default, default
                   , 'DETAILED') PS
 JOIN sys.indexes IX
  ON IX.object_id = PS.object_id AND IX.index_id = PS.index_id
 WHERE IX.name = 'PK_FragTest_PKCol';
GO
View Code

相关文章:

  • 2021-07-24
  • 2022-02-01
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-07-04
猜你喜欢
  • 2022-12-23
  • 2021-10-18
  • 2021-10-12
  • 2021-07-25
  • 2021-08-04
  • 2021-08-13
  • 2022-01-26
相关资源
相似解决方案