什么时候应该将主键声明为非聚集的?

Stu*_*ler 188 sql-server primary-key

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

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

提前致谢

Rem*_*anu 205

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

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

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

  • 探测,当在索引中查找单个键值时
  • 范围扫描,当检索到一系列键值时
  • order by 要求,当索引可以满足 order by 时不需要走走停停排序

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

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

一个好的聚集索引是稳定的,它在实体的生命周期内不会改变,因为聚集索引键值的改变意味着该行必须被删除和插入。

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

既然我们知道了什么是好的聚集索引键,那么主键(这是一个数据建模逻辑属性)是否符合要求?如果是,那么应该对 PK 进行聚类。如果不是,那么 PK 应该是非聚集的。

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

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

  • “按要求排序,当索引可以满足不要求走走停停排序的订单时”是什么意思? (2认同)

Ben*_*cka 27

维基百科上说明了使用聚集索引的基本原因:

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

假设我有一张 People 表,这些人有一个 Country 列和一个唯一的主键。这是一个人口统计表,所以这些是我唯一关心的事情;哪个国家以及有多少独特的人与该国家有关。

因此,我只可能在 Country 列中选择 WHERE 或 ORDER;主键上的聚集索引对我没有任何好处,我不是通过 PK 访问这些数据,而是通过另一个列访问它。因为我只能在一张表上有一个聚集索引,所以将我的 PK 声明为 Clustered 会阻止我在 Country 上使用聚集索引。

此外,这里有一篇关于Clustered vs Nonclustered Indexes的好文章,结果表明聚集索引导致 SQL Server 6.5 中的插入性能问题(至少希望与我们这里的大多数人无关)。

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

请注意,在以后的版本中情况并非如此。

  • FIY,你提到了 SQL Server 6.5:http://dba.stackexchange.com/questions/1584/is-avoid-creating-a-clustered-index-based-on-an-incrementing-key-a-myth-from- s/1586#1586 (3认同)

小智 20

如果您的主键是UNIQUEIDENTIFIER,请确保指定它是NONCLUSTERED。如果你让它聚集,每次插入都必须做一堆记录的混洗才能在正确的位置插入新行。这将坦克性能。


Tho*_*anz 9

一个很常见的例子:

  • CustomerCustomerID作为表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,以确保uniquiness -它将使更多的意义,以增加真正的(可用)数据,那么一些随机(取决于插入顺序)的东西。

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

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

相同的规则适用于OrderPositions表。通常大多数查询会列出特定订单的所有头寸,因此您应该使用OrderPositionIDasNONCLUSTERED和 a UNIQUE CLUSTERED INDEXon创建 PK OrderId, OrderPositionID

顺便说一句:Customer表按其 PK 聚类是正确的(CustomerID,因为它是“顶级表”并且 - 在典型应用程序中 - 主要由其 CustomerID 查询。

纯查找表作为 eg Gendersor InvoiceTypesorPaymentType是应该按其 PK 聚类的表的另一个示例(因为您通常会在GenderId, InvoiceTypeIdor上加入它们PaymentTypeId)。