什么时候应该声明主键为非集群键?


169

在为我之前问过的另一个问题创建测试数据库时,我记得有一个主键可以被声明 NONCLUSTERED

什么时候使用NONCLUSTERED主键而不是CLUSTERED主键?

提前致谢

Answers:


187

问题不是“ PK何时应为NC”,而是您应该问“聚集索引的正确密钥是什么”?

答案实际上取决于您如何查询数据。聚集索引比所有其他索引都有一个优势:因为它总是包含所有列,所以总是被覆盖。因此,可以利用聚集索引的查询当然不需要使用查找来满足某些预计的列和/或谓词。

另一个难题是如何使用索引?有三种典型的模式:

  • 在索引中搜索单个键值时进行探测
  • 范围扫描,当检索到一系列键值时
  • 按索引排序,当索引可以满足无顺序排序要求时

因此,如果您分析了预期的负载(查询),并发现大量查询将使用特定索引,因为它们使用了受益于索引的某种访问模式,则建议将该索引建议为聚簇索引。

另一个因素是,聚集索引键是所有非聚集索引使用的查找键,因此,较宽的聚集索引键会产生连锁反应,并扩大所有非聚集索引,并且较宽的索引意味着更多的页面,更多的I / O ,更多的记忆,更少的善良。

一个好的聚簇索引是稳定的,它在实体的生存期内不会改变,因为聚簇索引键值的更改意味着必须删除该行并将其插入回去。

一个好的聚簇索引的增长顺序不是随机的(每个新插入的键值都大于前一个值),以避免页面拆分和碎片化(不会与FILLFACTORs 混为一谈)。

因此,既然我们知道什么是好的聚集索引键,那么主键(这是数据建模逻辑属性)是否符合要求?如果是,则应将PK聚类。如果否,则PK应该是非群集的。

举一个例子,考虑一个销售事实表。每个条目都有一个ID作为主键。但是绝大多数查询都要求一个日期和另一个日期之间的数据,因此最佳的聚集索引键将是销售日期,而不是ID。具有与主键不同的聚簇索引的另一个示例是选择性很低的键,例如“类别”或“状态”,即只有很少的不同值的键。例如(state, id),将具有低选择性键的聚簇索引键作为最左键通常是有意义的,因为范围扫描会查找特定“状态”中的所有条目。

最后一点关于在堆上使用非聚集主键的可能性(即根本没有聚集索引)。这可能是一个有效的场景,典型的原因是批量插入性能至关重要时,因为与聚簇索引相比,堆具有明显更好的批量插入吞吐量。


1
这里的“按需求排序,当索引可以通过不需停停排序的排序来满足订单时”是什么意思?
Mike Sherrill'Cat Recall'12

2
@RemusRusanu。+1非常有用的答案。有关示例的一个问题(state, id)。在此示例中,将无法满足“良好的聚簇索引不按顺序增长”的要求,不是吗?那么我们可以将其视为良好的聚集索引吗?
Lijo

26

使用聚集索引的基本原因已在Wikipedia上阐明:

聚类将数据块更改为某个不同的顺序以匹配索引,从而导致按顺序存储行数据。因此,在给定的数据库表上只能创建一个聚簇索引。聚簇索引可以极大地提高整体检索速度,但通常仅在按聚簇索引的相同或相反顺序顺序访问数据时,或者在选择一系列项目时才如此。

假设我有一个人的表,这些人有一个国家列和一个唯一的主键。这是一个人口统计表,所以这是我唯一关心的事情;哪个国家/地区以及与该国家/地区有多少不重复的人。

因此,我只能选择“国家/地区”列中的“选择位置”或“按顺序”;主键上的聚集索引对我没有任何帮助,我没有通过PK访问此数据,而是通过另一列访问了它。由于我只能在一个表上有一个聚集索引,因此将我的PK声明为“聚集”将使我无法在“国家/地区”上使用聚集索引。

另外,这是一篇有关聚集索引和非聚集索引的好文章,结果证明聚集索引导致SQL Server 6.5中的插入性能问题(至少希望与我们大多数人无关)。

如果将聚集索引放在IDENTITY列上,则所有插入都将在表的最后一页上发生-并且该页面在每个IDENTITY期间都被锁定。没什么大不了的……除非您有5000个人都想要最后一页。然后您对该页面有很多争论

请注意,在更高版本中不是这种情况。


3
FIY,你提到的SQL Server 6.5:dba.stackexchange.com/questions/1584/...
GBN

15

如果您的主键是UNIQUEIDENTIFIER,请确保指定它为NONCLUSTERED。如果将其群集,则每个插入都必须进行一堆记录改组以将新行插入正确的位置。这将提高坦克的表现。


1
尽管我尝试避免使用集群密钥的UUID,但我认为上述推理可能不完整。SQL Server不必重新排列行以将a插入正确的位置(如果您的意思是“介于较低值和较高值之间”)。考虑插入到万亿行表的中间。需要额外的间接访问,这可能就是您的意思。序列UNIQUEIDENTIFIER类型也存在,并且具有生成唯一密钥的相同可能性,尽管它仍然遭受128大小的影响。
查尔斯·伯恩斯

7

一个非常常见的示例:

  • Customer带有CustomerIDas的表CLUSTERED PRIMARY KEY
  • 订单表,OrderID (PK), CustomerID, OrderDate以及其他一些列
  • OrderPositionsOrderPositionID (PK), OrderId, ProductID, Amount, Price ...
  • 您必须索引订单表

当然,“视情况而定”(几乎总是)是正确的答案,但是大多数应用程序(不是BI-Reports)将基于客户使用(例如,您以客户278的身份登录网站并单击“我的订单”或店员列出客户4569的所有订单,否则您的发票例程将汇总客户137的所有订单)。

在这种情况下,用来对表进行聚类将没有多大意义OrderID。是的,您将有SELECT ... WHERE OrderId = ?关于列出订单详细信息的查询,但这通常很短且便宜(3次读取)索引查找。

另一方面,如果您要Order根据来对表进行群集CustomerID,则不必在每次查询表时都进行多次键查找CustomerId = ?

该参数CLUSTERED INDEX应始终为UNIQUE,否则SQL Server将添加一个不可见(=不可用)的INT列UNIQUIFIER以确保唯一性-添加真实(可用)数据比添加一些随机(取决于插入顺序)的东西更有意义。

因为客户将(希望)下达多个订单,所以我们必须添加OrderID或(如果您通常对此进行排序)OrderDate(如果是日期时间-否则客户每天将限于一个订单)在CLUSTERED INDEX和结束:

CREATE UNIQUE CLUSTERED INDEX IX_Orders_UQ on Orders (CustomerID, OrderID)

相同的规则适用于OrderPositions表。通常,大多数查询会列出特定顺序的所有头寸,因此您应使用OrderPositionIDas NONCLUSTEREDUNIQUE CLUSTERED INDEXon 来创建PK OrderId, OrderPositionID

顺便说一句:正确的是,Customer表是由其PK聚集的(CustomerID,因为它是“顶级表”,并且在典型的应用程序中,大多数情况下将由其CustomerID查询。

纯查找表作为例如GendersInvoiceTypes或者PaymentType是应该由它来PK集群(因为你通常会加入他们的表上的另一个例子GenderIdInvoiceTypeIdPaymentTypeId)。


2

通过使用某种性能度量,认为聚簇索引比聚簇PK对整个系统更有利。一个表上只能有一个聚集索引。

性能的示例度量是单个查询时间(速度),针对表的总查询时间的集成(效率),并且必须向非常大的非聚集索引中的a中添加许多include列,以实现类似于聚集(大小)的性能。 )。

当通常使用非唯一索引,包含空值(PK中不允许)或由于其他原因(例如复制或审核跟踪记录标识)添加PK来检索数据时,可能会发生这种情况。

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.