【译】索引进阶(十二):SQL SERVER中的索引碎片【中篇】
原文链接:传送门。
为了讨论碎片产生的原因,以及避免和移除索引碎片的技术,我们必须从本进阶系列后续将介绍的两个章节借用一些知识点:创建/更新索引的知识,以及向一个索引表插入数据行的相关知识。
当我们讲解这些信息的时候,记住本节背景部分提到的内容,仅当你的应用是进行索引扫描时,碎片是不被期望的(不管是索引的完全扫描还是部分扫描)。当你的应用程序仅仅返回一个索引键值的少量数目的条目时,碎片其实并不是个问题。如同列表3所示的5行数据的查询。
SELECT *
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = 56080;
GO
列表3:一个高选择性的查询
当你在一个已经填充了数据行的表上创建或者重建索引的时候,会发生磁盘空间的重分配,导致了一几乎不包含外部碎片的索引。内部碎片的数值取决于你为 FILLFACTOR选项指定的值,它决定了页的覆盖率。在后续的数据更新中,SQL SERVER不会做任何努力来包含这种页覆盖率,随着数据行被插入表,结果条目会被插入索引页中,这些页会变得更加饱和。
最后SQL SERVER会尝试向已经充满的也插入一个条目,当这发生时,SQL SERVER将搜索它的分配架构来找到一个空的页,一旦SQL SERVER找到一个空页,它会做三件事中的一个,这决定于新的索引键被插入的顺序。
随机序列:通常SQL SERVER从一个充满的也移动一半的条目到空的页,然后将新条目插入到合适的页中,因此产生了两个半满的页,这种技术称之为页的分割,同时也是这种类型的索引被称之为平衡树索引的原因。因为有很小的可能性新的页会在物理上紧邻着旧的页,这会导致外部碎片的增加,如果你的程序不断的插入行而不删除行,这两个半满的页又会充满,然后又被分割为两个半满的页,一段时间之后,每个页都在半满与充满之间循环,一次又一次的,导致了内部碎片(平均也覆盖率)大约为75%。
升序:然而,如果SQL SERVER注意到新的条目是满页的最后一个条目,它便会假定数据行是以和索引一样的顺序插入表的,在这种模式下,如果索引是建立在LastName 列上,一个“Kimmel”条目被放置在页的末尾,SQL SERVER推测“Kimmel” 条目会被另一个“Kimmel”条目跟着,或许是一个“Kinder”行,因此它将新行,并且仅仅是新行放置在空的页中。当插入开始时,如果表是空的,不管逻辑上还是物理上,新页是索引的最后一个页。因此外部碎片保持为0,如果SQL SERVER的假定是正确的,数据行以索引键的顺到达,一个充满页不会被分割,一旦一个页充满了,它就会一直充满,导致了很少的或者没有内部碎片。
降序:相反的,如果SQL SERVER注意到新条目是充满页的第一个条目,它便会假设数据行是以降序的方式被插入数据表的,在这种情况下, SQL SERVER假定“Kimmel”行被另一个“Kimmel”行或者“Kimato”行跟着,又一次的,它将新条目,并进将新条目放置在空页中,外部碎片的增长与随机序列模式是一样的,而内部碎片是与升序模式一样,几乎是100%。
当一个新条目被放置在新页中,一些清理工作会被进行。四个上一页,下一页指针分布在三个页中,它们必须被更新,并且一个指向新页的条目会被插入更改级别的索引层级中,而这反过来会导致那个层级的页分割。
因此,虽然页分割是有益的,它们能够保持索引的平衡,而不必要的页分割还是需要被避免的,这个主题本节后续会进行讲解。
我们可以用三个简单的示例来验证以上提到的三种行为,每一个都会用到一个两列的,并且在其中一个列上具有索引的表,你可以运行列表4的代码来创建它。
USE AdventureWorks;
GO
CREATE TABLE dbo.FragTest
( PKCol INT NOT NULL
, InfoCol NCHAR(64) NOT NULL
, CONSTRAINT PK_FragTest_PKCol PRIMARY KEY NONCLUSTERED (PKCol)
);
GO
列表4:带有一个索引的简单表
在各个例子中,我们以三种可能的顺序中的一种向表中插入50000行数据:随机,升序,降序。在每个加载的结尾,我们使用列表5的查询来查看碎片信息:
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.avg_fragmentation_in_percent AS 'External Fragmentation (%)'
, PS.fragment_count AS 'Fragments'
, PS.avg_fragment_size_in_pages AS 'Avg Fragment Size'
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
列表5:碎片查询
是否具有非聚集索引无关紧要,如果是一个聚集索引,那么结果是类似的。
随机插入行
对于我们的第一个例子来说,我们使用列表6显示的技术,插入具有随机索引键值的50000行数据,然后查看结果碎片,如图6所示。
TRUNCATE TABLE dbo.FragTest;
GO
DECLARE @limit INT; SET @limit = 50000;
DECLARE @counter INT; SET @counter = 1;
DECLARE @key INT;
SET NOCOUNT ON;WHILE @counter <= @limit
BEGIN
SET @key = CONVERT(INT, (RAND() * 1000000));
BEGIN TRY
INSERT dbo.FragTest VALUES (@key, 'AAAA');
SET @counter = @counter + 1;
END TRY
BEGIN CATCH
END CATCH;
END;
GO
列表6:随机序列插入
随机插入的碎片结果,如图6所示,是我们所期望的结果。输出结果告诉我们平均的叶子节点页稍微少于75%的覆盖率,它同时也告诉我们这个索引是完全碎片化的,也就是说,每个页都有自己的碎片,意味着没有一个下一页指针指向物理上紧邻的页。

图6:随机插入导致的碎片
以升序插入行
下一步,我们更改我们的批处理使得其以递增的索引键值来插入行,如同列表7所示:
TRUNCATE TABLE dbo.FragTest;
GO
DECLARE @limit INT; SET @limit = 50000;
DECLARE @counter INT; SET @counter = 1;
SET NOCOUNT ON;
WHILE @counter <= @limit
BEGIN
BEGIN TRY
INSERT dbo.FragTest VALUES (@counter, 'AAAA');
SET @counter = @counter + 1;
END TRY
BEGIN CATCH
END CATCH;
END;
GO
列表7:升序插入
插入数据行之后,我们再一次运行列表5的查询,得到了图7的结果。表明页被很好的组织起来。并且其外部碎片几乎为0,SQL SERVER 通过读取一个区便可以扫描整个索引,设置一次IO可以读取多个区,而那些IO可以通过预读完成。

图7:升序插入的碎片
以降序插入行
接下来,我们更新我们的批处理命令,使其以递减的索引键值插入行,如列表8所示:
TRUNCATE TABLE dbo.FragTest;
GO
DECLARE @limit INT; SET @limit = 50000;
DECLARE @counter INT; SET @counter = 1;
SET NOCOUNT ON;
WHILE @counter <= @limit
BEGIN
BEGIN TRY
INSERT dbo.FragTest VALUES (@limit - @counter, 'AAAA');
SET @counter = @counter + 1;
END TRY
BEGIN CATCH
END CATCH;
END;
GO
列表8:递减的序列插入
再一次的,在插入数据之后,我们允许列表5的查询,并且检查结果,显示在图8:

图8:递减的序列插入时候的碎片
我们可以看到也是充满的,但是文件是完全碎片化的。这后者事实稍微有点误解,因为索引的页是连续的,但是索引序列上的第一页是物理文件上的最后一个页,每一个下一页指针指向物理上的前一个页,因此导致了索引的高外部碎片率。
降序插入是很少见的,通常是从外部数据源通过日期或者金额以降序的方式请求数据的结果。
每当你参加一个降序插入的模式时,确保在创建索引的时候指定DESC关键字,如同列表9所示:
CREATE INDEX IX_SalesOrderHeader_OrderDate ON Sales.SalesOrderHeader
(
OrderDate DESC
, SalesOrderID
);
GO
列表9:创建一个降序索引
这将索引变成了一个升序插入模式的索引,因此减少了外部碎片问题。
删除/更新数据行
因此数据的插入顺序,不管其是随机的,升序或者降序,都会影响索引的碎片,但是大部分的数据行不会永远的停留在数据表中,最后它们都会被删除,这也会影响索引的碎片。
更新也能够影响索引碎片,但仅仅是如下情况之一:
一个列被更新参与到索引键中。
或者:
更新导致了可变宽度列的大小增加,以致于索引键不再适合一个页大小。
在以上的情况,更新会被当做删除之后的再插入来对待。
因此,此刻我们需要查看通用的 删除/插入模式的组合以及它们导致的碎片。通过了解了这些模式,你便可以应用你的数据库索引和应用程序数据更新的知识来预期即将产生的碎片。对于任何索引来说,如果这个碎片是不可接受的,你便可以应用本章后续出现以及后续章节出现的知识来防止或者调整那个碎片。
本节我们介绍了碎片产生的原因,下一节我们将介绍如何防止产生碎片以及一些通用的碎片模式。
【译】索引进阶(十二):SQL SERVER中的索引碎片【中篇】的更多相关文章
- 转载: SQL Server中的索引
http://www.blogjava.net/wangdetian168/archive/2011/03/07/347192.html 1 SQL Server中的索引 索引是与表或视图关联的磁盘上 ...
- SQL Server中的索引
1 SQL Server中的索引 索引是与表或视图关联的磁盘上结构,可以加快从表或视图中检索行的速度.索引包含由表或视图中的一列或多列生成的键.这些键存储在一个结构(B 树)中,使 SQL Serve ...
- sql server中的索引详情
什么是索引 拿汉语字典的目录页(索引)打比方:正如汉语字典中的汉字按页存放一样,SQL Server中的数据记录也是按页存放的,每页容量一般为4K .为了加快查找的速度,汉语字(词)典一般都有按拼音. ...
- 【译】索引进阶(十一):SQL SERVER中的索引碎片【上篇】
原文链接:传送门. 第十章节我们分析了索引的内部结构.有了这些关于索引结构的知识,我们便可以分析索引碎片了:其产生的原因,如何防止,以及何时可以不去关注它们. 一些背景知识 / 复习 以下知识对于理解 ...
- 【译】索引进阶(十三):SQL SERVER中的索引碎片【下篇】
原文链接:传送门. 通用碎片模式 如果一个表具有多个索引,那么这些索引便具有多个索引键,因而也具有不同的顺序.通常情况下,这些索引中的一两个展示了之前描述过的升序插入模式,而其他的通常展示了随机插入模 ...
- SQL Server中的索引结构与疑惑
说实话我从没有在实际项目中使用过索引,仅知道索引是一个相当重要的技术点,因此我也看了不少文章知道了索引的区别.分类.优缺点以及如何使用索引.但关于索引它最本质的是什么笔者一直没明白,本文是笔者带着这些 ...
- 【译】第十篇 SQL Server安全行级安全
本篇文章是SQL Server安全系列的第十篇,详细内容请参考原文. 不像一些其他industrial-strength数据库服务,SQL Server缺乏一个内置保护个别数据记录的机制,称为行级安全 ...
- 【译】第十篇 SQL Server代理使用代理帐户
本篇文章是SQL Server代理系列的第十篇,详细内容请参考原文 在这一系列的上一篇,你查看了msdb库下用于授权访问SQL Server代理的安全角色.这些角色包括SQLAgentUserRole ...
- (转)SQL Server中的索引结构与疑惑
说实话我从没有在实际项目中使用过索引,仅知道索引是一个相当重要的技术点,因此我也看了不少文章知道了索引的区别.分类.优缺点以及如何使用索引.但关于索引它最本质的是什么笔者一直没明白,本文是笔者带着这些 ...
随机推荐
- JDK8中HashMap
引用别人的一句话: JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多 ...
- 关于php中 json_encode 编码成json数据参数说明
JSON_UNESCAPED_SLASHES 编码不要转义符 \ 反斜杠 JSON_UNESCAPED_UNICODE 编码不转义成 unicode 字符串 如果如果多个参数可以用 | 隔开添 ...
- AtCoder Grand Contest 032 A - Limited Insertion( 思维)
Time Limit: 2 sec / Memory Limit: 1024 MB Score : 400400 points Problem Statement Snuke has an empty ...
- Java Core - ‘==’和‘equals’的区别
不管是‘==’还是‘equals’,他们的比较都需要区分类型来讨论的: ‘==’ 当比较的数据类型是基本类型时,比较值是否相同 当比较的数据类型是引用类型时,不仅比较值相同还比较其所在内存地址是否相同 ...
- codeforces510D
Fox And Jumping CodeForces - 510D Fox Ciel is playing a game. In this game there is an infinite long ...
- 图片的HTML
Code <center> <img style="border-radius: 0.3125em; box-shadow: 0 2px 4px 0 rgba(34,36, ...
- 微信小程序工具类
wechat-common-sdk ? 场景:目前工作中的项目需要包含并使用另一个项目. 也许是第三方库,或者你独立开发的,用于多个父项目的库. 现在问题来了:你想要把它们当做两个独立的项目,同时又想 ...
- Forethought Future Cup - Elimination Round
A:签到. #include<bits/stdc++.h> using namespace std; #define ll long long char getc(){char c=get ...
- Windows Server 2012 蓝屏 Wpprecorder.sys 故障
坑爹的园区昨天停电了,导致运行中的服务器中断,来电之后,其中有一台Windows 系统的服务无法运行了,接了个显示器,发现无法进入系统了,挂掉了,这下可完蛋了,虽然做了Radio 磁盘阵列,数据不会丢 ...
- Atcoder刷题小记
1. 2019.4.27 agc016d 一道很坑的题. 首先判无解,求出异或值后排个序就可以. 然后直接让\(a_i\rightarrow b_i\)并查集维护,注意离散化和判重,答案加上联通块个数 ...