标识列上的索引是否应该非聚集?


19

对于具有标识列的表,是否应为标识列创建聚集或非聚集的PK /唯一索引?

原因是将为查询创建其他索引。使用非聚集索引(在堆上)并返回该索引未覆盖的列的查询将使用较少的逻辑I / O(LIO),因为没有额外的聚集索引b树查找步骤?

create table T (
  Id int identity(1,1) primary key, -- clustered or non-clustered? (surrogate key, may be used to join another table)
  A .... -- A, B, C have mixed data type of int, date, varchar, float, money, ....
  B ....
  C ....
  ....)

create index ix_A on T (A)
create index ix_..... -- Many indexes can be created for queries

-- Common query is query on A, B, C, ....
select A, B 
from T 
where A between @a and @a+5 -- This query will have less LIO if the PK is non-clustered (seek)

select A, B, C
from T 
where B between @a and @a+5 

....

标识列上的聚集PK是好的,因为:

  1. 它单调增加,因此插入时不会拆分页面。据说批量插入的速度和在堆(非簇)表上的速度一样快

  2. 很窄

但是,如果不将其设置为集群,问题中的查询会更快吗?

**更新:**如果Id其他表的FK是什么,它将在某些查询中加入?


3
这是好是坏,取决于情况。
亚伦·伯特兰

1
@ypercube链接kejser.org/clustered-indexes-vs-heaps表示,非CI的LIO更少。
u23432534

2
我过去已经读过这篇文章,它的确指出,存在聚集索引的情况和堆的情况。不是全黑还是全白。
ypercubeᵀᴹ

4
我不确定您对@ypercube的回复是否满足Kejser先生所引用的任何标准-至少在您所分享的细节上是否如此。以目前的形式,我实际上不确定这是否会产生有用的答案,因为它几乎必须涵盖所有单个场景-您引用的博客文章中已经做到了。如果您可以提供有关您的特定方案的更多详细信息,那么可以应用帖子中的一些知识。
swasheck

2
它将取决于以下内容:a)工作量(OLTP,OLAP等),b)表大小,c)正常形式,仅举几例。您尚未提供有关任何这些因素的详细信息,因此任何建议都将基于您的环境的猜测。另外,您是否尝试过分析所提出的查询(使用清除的缓冲区)并获取每个配置的特定IO配置文件并亲自查看?
swasheck

Answers:


16

默认情况下,PK是群集的,在大多数情况下,这很好。但是,应该问以下问题:

  • 我的PK应该聚类吗?
  • 哪几列将是我的聚集索引的最佳键?

PK和聚集索引是2个不同的东西:

  • PK是一个约束。PK用于唯一地标识行,但是没有存储的概念。但是,默认情况下(在SSMS中),如果尚不存在聚集索引,则由唯一的聚集索引强制执行。
  • 聚集索引是一种特殊的索引类型,它在叶级存储行数据,这意味着它总是被覆盖。所有列(无论它们是否是键的一部分)都存储在叶级别。它不必是唯一的,在这种情况下,将一个唯一符(4个字节)添加到集群键中。

现在我们有两个问题:

  • 我想如何唯一标识表(PK)中的行
  • 我想如何将其存储在索引的叶级(聚集索引)

这取决于如何:

  • 您设计数据模型
  • 您查询数据并编写查询
  • 您插入或更新数据
  • ...

首先,您是否需要聚集索引?如果批量插入,则将无序数据存储到HEAP(相对于集群中的有序数据)更为有效。它使用RID(行标识符,8字节)唯一地标识行并将其存储在页面上。

聚集索引不应为随机值。叶子级的数据将通过索引键进行存储和排序。因此,它应该连续增长以避免碎片或页面拆分。如果PK无法做到这一点,则应考虑将另一个密钥作为聚集候选者。从顺序的角度来看,同一列上的聚集索引,顺序GUID或什至类似于插入日期的索引都很好,因为所有行都将添加到最后一个叶子页面。另一方面,尽管唯一标识符作为PK可能对您的业务需求很有用,但不应将它们组合在一起(它们是随机排序/生成的)。

如果经过一些数据和查询分析后发现,在集群PK中进行键查找之前,您大多使用相同的索引来获取数据,则可以将其视为集群索引,尽管它可能无法唯一地标识您的数据。

聚集索引键由要索引的所有列组成。如果没有唯一约束,则添加一个uniquefier列(4个字节)(重复项的增量值,否则为null)。然后,该索引键将在所有非聚集索引的叶级为每行存储一次。其中一些还将在索引树(B树)的根和叶级别之间的中间级别(分支)存储几次。如果密钥太大,则所有非聚集索引都将变大,将需要更多的存储空间和更多的IO,CPU,内存,...如果您在名称+出生日期+国家/地区拥有PK,则很可能该密钥不是一个好的候选人。对于聚集索引而言,它太大了。尽管使用NEWSEQUENTIALID()的Uniqueidentifier是顺序的,但通常不视为窄键(16字节)。

然后,一旦确定了如何唯一标识表中的行,就可以添加PK。如果您认为您不会在查询中使用它,请不要将其创建为群集。如果您有时需要查询另一个非聚集索引,则仍可以创建它。请注意,PK将自动创建唯一索引。

非聚集索引将始终包含聚集键。但是,如果覆盖了索引列(+键列),则聚集索引中将不会有任何键查找。别忘了,您还可以将Include和Where添加到非聚集索引中。(明智地使用它)

聚簇索引应唯一且尽可能窄聚簇索引不应随时间变化,而应增量插入。

现在是时候编写一些SQL来创建表,聚集索引和非聚集索引以及约束的时候了。

这完全是理论上的,因为我们不知道您使用的数据模型和数据类型(A和B)。


11

对于在标识列上具有主键(PK)的表,默认情况下将对其进行群集。像非集群那样会更好吗?

如果您要询问是否应该不对identity列上的主键的默认值(尤其是默认值)进行群集化,我会说不。大多数表都受益于具有聚集索引,因此将聚集设置为主键约束的默认值可能总体上很有帮助,尤其是对于SQL Server的新用户而言。

与几乎所有选项一样,总是有不同的情况,其中一种情况比另一种情况更可取,但是有经验的DBA应该了解默认值,并能够在适当时覆盖它。另请参阅相关的问答,何时应将主键声明为非集群键?

如果不将其设置为集群,问题中的查询会更快吗?

是的,但有一些警告。

RID查找确实比密钥查找更有效。即使所有必需的页面都在内存中(很可能在索引的较高级别),在导航聚簇索引b树时也会有CPU开销。因此,与每单位CPU时间的键查找相比,SQL Server通常可以执行更多的RID查找。

注意事项

在决定是否将表构造为堆时,上述因素通常不是决定因素。避免查找(使用覆盖索引)将是不切实际的,并且鉴于硬件环境和工作负载,查找的数量必须足够大以对性能产生可测量(且重要)的影响。

在此答案中涵盖堆与聚簇索引辩论的所有方面并不是很实际,但是我会说,相对而言,没有什么充分的理由更倾向于将表构造为堆。对我而言,选择问题中建议的设计类型将需要在实施之前进行非常仔细的分析,并且必须满足较高的标准。关于“可扩展性”的一般论点还不够。

关于有关联接问题的更新,评估丢失聚集索引对执行计划的影响将构成上述分析的一部分。如果使用嵌套循环联接,则在联接键上具有聚簇索引非常方便,因为该行中的所有列都可以立即使用而无需查找。

我自己的经验是,考虑到所有因素,在标识列上具有唯一的聚集索引通常是有益的。我发现堆在空间管理方面是有问题的,我还应该提到一些SQL Server功能需要唯一的聚集索引才能起作用。


8

实际上,您不需要创建聚簇索引或主键,因为唯一索引和非唯一索引可以处理工作。SQL Server至少从1.1版开始就支持群集索引,但是主键只是程序员通过定义唯一索引来实施的“概念”。

但是,在大多数数据库中,主键和聚簇索引似乎都是有价值的概念。

让我们看一下SQL Server文档,以查看一些索引选项的部分说明,如下所示。

群集索引: https : //msdn.microsoft.com/en-us/library/ms190457.aspx

  • 聚集索引根据它们的键值对数据行进行排序并将其存储在表或视图中。这些是索引定义中包含的列。
  • 每个表只能有一个聚集索引

主键: https : //msdn.microsoft.com/en-us/library/ms190457.aspx

  • 一个表只能包含一个PRIMARY KEY约束。

  • 在PRIMARY KEY约束中定义的所有列都必须定义为NOT NULL。

  • 可以将主键创建为聚簇索引(如果没有聚簇索引,则为默认值)或非聚簇索引。

唯一索引: https : //msdn.microsoft.com/en-us/library/ms187019.aspx

  • 创建UNIQUE约束时,默认情况下会创建一个唯一的非聚集索引来强制执行UNIQUE约束。

  • 如果表的聚簇索引尚不存在,则可以指定UNIQUE聚簇索引。

这意味着您有关聚簇索引和主键的问题实际上是关于以下一些问题。请注意,并非每个表都受益于相同的索引编制计划。

我何时可以从与聚簇索引分开的主键中受益?

也许当聚集索引很宽时(例如5列文本信息,而主键很小(INT或BIGINT)),就像您正在描述的那样。

  • 宽泛的聚集索引使您可以从索引中快速选择查询子集的行,这些查询子集可以从聚集索引(也称为)中提供串行答案。例如,五列聚簇索引将支持扫描列C1,C2,C3,C4,C5或C1,C2,C3,C4等,直到C1。
  • 注意:如果行很大,则在选择串行行集时可能会给您带来一些速度上的好处,特别是如果表中的其他列定期包含在结果集中时。
  • 在那种情况下,您可以使用主键来实现参照完整性,以便提供所需的值作为外键来约束其他表中的行。PK很小,因此FK对引用表的大小影响很小。
  • 但是,请注意,在具有“聚集索引”的表上创建的任何索引都将在此表上创建的其他索引中包括所有集群列。较宽的聚集索引将扩大该表上所有非聚集索引的大小。

您是否应该仅使主键成为聚簇索引?

  • 如果您有一个小的主键(INT或BIGINT)并且它是聚簇索引,则聚簇列的开销相对较小。尽管在这种情况下,群集主键也将存在于此表的每个索引中,但要付出的代价比上面讨论的宽群集要小。

  • 通常,该主键聚簇索引不会直接为串行选择许多行提供简便的方法。

  • 既然您已经创建了集群主键,那么您曾经计划将其包括在集群索引中的那些其他列呢?

  • 根据需要创建唯一(或非唯一)索引,以索引列C1,C2,C3,C4,C5的广泛搜索条件。该“模仿群集”索引中的值可以用作这5列的更快搜索路径。如果也有定期选择的一两个非索引列,则可以使用将它们包括在索引中 INCLUDE (Doctor_Name, Diagnosis_Synopsis)

尽管我发现简单的聚集索引和主键很有用,但仍有一些充分的理由考虑是否在表或数据库中使用它们。

您是否完全需要聚集索引?

  • 如果创建索引(唯一索引和非唯一索引)并定义主键而没有成为聚集索引的开销,则可能会发现较窄的索引可以为您提供查询所需的内容。

  • 聚集索引和主键中有一些有用的行为,但是请记住,实际上最重要的是索引。设计索引策略时要考虑到应用程序的实际情况。也许OneBigTable需要与大多数表使用不同的索引策略。

  • 如果没有聚簇索引,您的数据将与行标识符(RID)一起存储为,这根本不是一个好的搜索机制。但是,如前所述,您可以创建唯一索引和非唯一索引来处理查询。

现在,您需要考虑堆:

堆和索引: https : //msdn.microsoft.com/en-us/library/hh213609.aspx

  • 将表存储为堆时,通过参考行标识符(RID)来标识各个行,该行标识符由文件号,数据页号和页面上的插槽组成。行ID是一个小型高效的结构。(但这不是索引。)
  • 有时,当总是通过非聚簇索引访问数据且RID小于聚簇索引键时,数据架构师会使用堆。

但是,如果您在大数据集中也有一些“热点”,那么您也可以查看另一种类型的索引:

筛选的索引: https : //msdn.microsoft.com/zh-cn/library/cc280372.aspx

  • 精心设计的筛选索引可提高查询性能和执行计划的质量,因为它比全表非簇索引小并且具有筛选统计信息。过滤后的统计信息比全表统计信息更准确,因为它们覆盖过滤后的索引中的行

  • 过滤索引具有许多限制,这些限制在过滤索引的链接中概述。

但是,如果您有兴趣考虑完全跳过主键和聚簇索引的可能性,则可以阅读下面链接的Markus Winand的文章。他通过一些代码示例演示了其原因,以表明有时放弃使用这些功能可能是个好主意。

http://use-the-index-luke.com/blog/2014-01/unreasonable-defaults-primary-key-clustering-key

但这一切最终都回到了理解您的应用程序以及设计代码,表,索引等以适合您正在执行的工作的角度。


对于它的价值,在我的日常工作中,如果我发现一个表是一个堆,我认为它很可能是一个错误,并与开发人员联系以查看它是否被故意制成了堆。
RLF

-2

需要考虑几点。

单调递增值上的索引(成簇或不成簇)可以在批量插入过程中节省页面拆分,但它会在索引的末尾创建新的热点。尽管使用单线程批量插入可能不是问题,但是对于多线程应用程序,以高速率插入新元组肯定会增加争用,因为线程将不断争夺对索引最后一页的访问。

根据代理(身份)PK对表进行聚类很少有好处。这样的主键通常用于一次访问单个元组,或扫描整个索引以查找连接。在这两种情况下,索引是否聚集都无关紧要(可能是合并联接,但是它们有多频繁?)

我认为您将从聚集索引中受益最多,该聚集索引涵盖了要求进行键范围扫描的查询以及引用其他列的其他谓词。


实际要成为问题,必须达到多少速率?
ypercubeᵀᴹ

@ypercube我可以说“取决于”吗?因为有。在表上没有触发器的情况下,我希望通过十几个线程(每秒总计1K次插入)开始遇到一些争用。
mustaccio


我不同意,但是我在问一个热点能走多远。我记得曾见过一篇有关以IDENTITY作为CI在表中每秒插入3万行的文章(如果我的记忆力很好的话),但找不到博客文章。
ypercubeᵀᴹ

如果没有针对特定硬件上的具体架构运行的具体工作负载,则该讨论毫无意义。我希望我们所有人都同意,单调递增的序列索引将创建一个“热点”。它是否会造成不可接受的瓶颈,是否应该关心它取决于情况。
mustaccio
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.