《Pro SQL Server Internals, 2nd edition》的CHAPTER 1 Data Storage Internals中的Clustered Indexes一节

来源:《Pro SQL Server Internals, 2nd edition》

作者:Dmitri Korotkevitch

美国佛罗里达州坦帕

聚集索引

聚集索引指示表中数据的物理顺序,表中数据按照聚集索引进行排序

索引键。表只能定义一个聚集索引。

让我们假设您希望在堆表上使用数据创建一个聚集索引。作为第一步,如图2-5所示,SQL Server创建另一个数据副本,然后根据群集键的值。数据页链接在一个双链列表中,其中每个页面都包含指向链中下一页和上一页的指针。这个列表被称为索引的叶级

包含实际的表数据。

图2 - 5。聚类索引结构:叶级

图2 - 5。聚类索引结构:叶级

 

■注意页面上的排序顺序由插槽数组控制。页面上的实际数据未排序。

 

当叶子级别包含多个页面时,SQL Server将开始构建索引,如图2-6所示。

图2 - 6。聚类索引结构:中间级和叶级

中间层为每个叶级页面存储一行。它存储两条信息物理地址和索引键在它引用的页面中的最小值。唯一的例外是第一个页面上的第一行,SQL Server在其中存储NULL而不是最小索引键值。通过这种优化,SQL Server在插入行时不需要更新非叶级行表中键值最低的。

中间层的页面也链接到双链列表。SQL Server添加更多以及更多的中间层次,直到有一个层次只包含一个页面。这个级别称为根级别,它成为索引的入口点,如图2-7所示。

图2 - 7。聚集索引结构:根级

如您所见,索引总是有一个叶级、一个根级和零个或多个中间级。唯一的例外是索引数据适合于单个页面。在这种情况下,SQL Server不会创建单独的根级页面,索引仅由单个叶级页面组成。

索引中的级别数量在很大程度上取决于行和索引键大小。例如,4字节整数列上的索引在中间层和根层上每行需要13个字节。那些13个字节由一个2字节的slot-array条目、一个4字节的索引键值、一个6字节的页面指针和一个1字节的行组成开销,这是足够的,因为索引键不包含可变长度和空列。

因此,每行可以容纳8060个字节/ 13个字节=每页620行。这意味着,使用一个中间层,您可以存储最多620 * 620 = 384,400个叶级页面的信息。如果数据行大小为200字节,那么每个叶级页面可以存储40行,最多可以存储15,376,000行指数只有三个层次。在该指数中再增加一个中间水平,将基本上涵盖所有方面可能的整数值。

 

■注意,在现实生活中,索引碎片化会减少这些数字。我们将讨论索引碎片在第六章。

 

SQL Server可以通过三种不同的方式从索引中读取数据。第一个是通过有序扫描。假设我们想要从dbo运行SELECT Name。客户订单的CustomerId查询。索引叶级上的数据已经基于CustomerId列进行了排序价值。因此,SQL Server可以从第一个页面扫描到最后一个页面的索引叶级,并返回按存储顺序排列的行。

SQL Server从索引的根页面开始,并从那里读取第一行。这一行的引用具有表中最小键值的中间页。SQL Server读取该页并重复直到在叶子级找到第一页为止。然后,SQL Server开始逐个读取行,在页的链接列表中移动,直到所有行都被读取。图2-8说明了这个过程。

图2 - 8。命令索引扫描

前一个查询的执行计划显示了带顺序的聚集索引扫描操作符属性设置为true,如图2-9所示。

图2 - 9。有序索引扫描执行计划

值得一提的是,order by子句不是触发有序扫描所必需的。有序扫描只意味着SQL Server根据索引键的顺序读取数据。

SQL Server可以在索引中向前和向后两个方向导航。然而,有您必须记住的一个重要方面是:SQL Server在向后期间不使用并行索引扫描。

 

■提示您可以通过检查索引扫描或索引查找操作符属性来检查扫描方向执行计划。但是请记住,Management Studio不会在执行计划的图形表示。您需要打开Properties窗口,通过选择在执行计划中操作并选择视图/属性窗口菜单项或按F4键。

SQL Server的企业版有一个优化特性,称为旋转木马扫描允许多个任务共享同一个索引扫描。假设你有会话S1,也就是扫描索引。在扫描过程中,另一个会话S2运行一个查询,该查询需要扫描相同的索引。使用旋转木马扫描,S2在当前扫描位置加入S1。SQL Server读取每个页面只有一次,将行传递给两个会话。

当S1扫描到达索引末尾时,S2从索引开始扫描数据直到S2扫描开始。旋转木马扫描是你不能依赖的另一个例子以及当order BY子句很重要时,为什么应该始终指定order BY子句。

顺序扫描之后的下一个访问方法称为分配顺序扫描。SQL Server访问通过IAM页面的表数据,类似于堆表。从dbo。使用(NOLOCK)查询的客户和图2-10说明了这种方法。图2-11显示了该查询执行计划。

图2 - 10。分配顺序扫描

图2 - 11。分配订单扫描执行计划

不幸的是,当SQL Server使用分配顺序扫描时,很难检测到。即使执行计划中的有序属性显示为false,表示SQL Server不关心行按索引键的顺序读取,而不是使用分配顺序扫描。

分配顺序扫描可以更快地扫描大型表,尽管它的启动成本更高。当表很小时,SQL Server不使用这种访问方法。另一个重要的考虑是数据一致性。SQL Server不使用具有聚集索引的表中的转发指针,以及分配顺序扫描可能产生不一致的结果。由于以下原因,可以跳过或多次读取行由页分割引起的数据移动。因此,SQL Server通常避免使用分配顺序扫描除非它以READ UNCOMMITTED或SERIALIZABLE事务隔离级别读取数据。

 

■注意,我们将在第6章“索引碎片”中讨论页面碎片和碎片,并进行讨论第三部分“锁定、阻塞和并发”中的锁定和数据一致性。

最后一种索引访问方法称为索引查找。从dbo中选择名称。客户在哪里CustomerId介于4和7之间,图2-12演示了该操作。

图2 - 12。指数寻求

为了从表中读取行范围,SQL Server需要找到具有最小值的行键值的取值范围,即4。SQL Server从根页面开始,其中是第二行引用键值最小为350的页面。它大于我们要找的键值(4), SQL Server读取根页面第一行引用的中间层数据页(1:170)。

类似地,中间页面将SQL Server引导到第一个叶级页面(1:176)。SQL服务器读取然后读取customerid为4和5的行,最后读取剩下的两行从第二页开始的行。

执行计划如图2-13所示。

图2 - 13。索引查找执行计划

可以猜到,索引查找比索引扫描更有效,因为SQL Server只处理行和数据页的子集,而不是扫描整个表。

从技术上讲,有两种索引查找操作。第一个叫做单例查找,或者有时是点查找,即SQL Server查找并返回一行。你可以想想在哪里CustomerId = 2谓词就是一个例子。另一种索引查找操作称为范围扫描,和它要求SQL Server查找键的最小值或最大值,并扫描(向前或向后)一组行,直到到达扫描范围的末尾。CustomerId位于4和7之间的谓词到扫描范围。这两种情况都显示为执行计划中的索引查找操作。

正如您所猜测的,范围扫描完全有可能强制SQL Server处理大量或甚至索引中的所有数据页。例如,如果将查询更改为使用WHERE CustomerId > 0谓词,SQL Server将读取所有行/页,即使您有一个索引查找操作符显示在执行计划中。您必须记住这种行为,并始终分析的效率查询性能调优期间的范围扫描。

关系数据库中有一个概念叫做SARGable谓词,它代表S earch有能力。如果SQL Server可以使用索引查找操作(如果索引),则谓词是SARGable的存在。简而言之,当SQL Server可以隔离单个值或索引范围时,谓词是可SARGable的要处理的键值,从而限制谓词计算期间的搜索。显然,写作是有益的使用SARGable谓词的查询,并尽可能利用index seek。     

SARGable谓词包括以下操作符:=、>、>=、<、<=、IN、BETWEEN和LIKE(如果是前缀的话)匹配)。非sargable操作符包括NOT、<>、LIKE(在非前缀匹配的情况下)和NOT in。

使谓词不可sargable的另一种情况是使用函数或数学方法对表列进行计算。SQL Server必须调用函数或执行计算它处理的每一行。幸运的是,在某些情况下,您可以重构查询来生成这样的谓词SARGable。表2-1显示了一些这样的例子。

表2 - 1。将非SARGable谓词重构为SARGable谓词的示例

另一个必须记住的重要因素是类型转换。在某些情况下,你可以通过使用不正确的数据类型,谓词是非sargable的。让我们创建一个包含varchar列和的表用一些数据填充它,如清单2-6所示。

清单2 - 6。SARG谓词和数据类型:创建测试表

create table dbo.Data
(
 VarcharKey varchar(10) not null,
 Placeholder char(200)
);
create unique clustered index IDX_Data_VarcharKey
on dbo.Data(VarcharKey);
;with N1(C) as (select 0 union all select 0) -- 2 rows
,N2(C) as (select 0 from N1 as T1 cross join N1 as T2) -- 4 rows
,N3(C) as (select 0 from N2 as T1 cross join N2 as T2) -- 16 rows
,N4(C) as (select 0 from N3 as T1 cross join N3 as T2) -- 256 rows
,N5(C) as (select 0 from N4 as T1 cross join N4 as T2) -- 65,536 rows
,IDs(ID) as (select row_number() over (order by (select null)) from N5)
insert into dbo.Data(VarcharKey)
 select convert(varchar(10),ID) from IDs; 

聚集索引键列被定义为varchar,尽管它存储整数值。现在,让我们运行两个select,如清单2-7所示,并查看执行计划。

清单2 - 7日。SARG谓词和数据类型:使用integer参数选择

declare

 @IntParam int = '200'

select * from dbo.Data where VarcharKey = @IntParam;

select * from dbo.Data where VarcharKey = convert(varchar(10),@IntParam); 

如图2-14所示,对于integer参数,SQL Server扫描集群索引,将varchar转换为每一行的整数。在第二种情况下,SQL Server转换整数参数,并使用更有效的聚集索引查找操作。

图2 - 14。SARG谓词和数据类型:带有整数参数的执行计划

 

■提示注意连接谓词中的列数据类型。隐式或显式数据类型转换可以显著降低查询的性能。

 

您将在unicode字符串参数的情况下观察到非常相似的行为。让我们运行查询如清单2-8所示。图2-15显示了语句的执行计划。

清单2 - 8。SARG谓词和数据类型:使用字符串参数选择

select * from dbo.Data where VarcharKey = '200';

select * from dbo.Data where VarcharKey = N'200'; -- unicode parameter

图2-15。SARG谓词和数据类型:带有字符串参数的执行计划

可以看到,unicode字符串参数对于varchar列是不可sargable的。这是一个这个问题比看起来的要大。虽然很少以这种方式编写查询,如清单2-8所示,现在大多数应用程序开发环境都将字符串视为unicode。因此,SQL Server客户端库为字符串对象生成unicode (nvarchar)参数,除非参数数据类型被显式指定为varchar。这使得谓词不可sargable,并且可以导致major由于不必要的扫描而导致的性能下降,即使索引了varchar列也是如此。

■在客户端应用程序中始终指定重要的参数数据类型。例如,在ADO中。净,用SqlDbType Parameters.Add (“@ParamName”。Varchar、<大小>)。Value = stringVariable而不是Parameters.Add (“@ParamName”)。Value = stringVariable重载。在ORM框架中使用映射到在类中显式指定非unicode属性。

值得一提的是,对于nvarchar unicode数据列,varchar参数是可SARGable的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值