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

何时使用NONCLUSTERED主键而不是CLUSTERED主键?

预先感谢

#1 楼

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

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

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


探针,当在索引中搜索单个键值时
范围扫描,当检索到一系列键值时
按需求排序,当索引可以通过不需停停排序的排序来满足订单时

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

另一个因素是,聚集索引键是所有非聚集索引所使用的查找键,因此,较宽的聚集索引键会产生连锁反应,并扩大所有非聚集索引,并且较宽的索引意味着更多的页面,更多的I / O,更多的内存,更少的好处。 >
一个好的聚集索引是稳定的,它在实体的生存期内不会更改,因为聚集索引键值的更改意味着该行必须是删除并插入回去。

为了避免页面拆分和碎片化(不会与FILLFACTOR混在一起),一个好的聚集索引会以非随机的顺序增长(每个新插入的键值大于前一个值)。 )。

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

举个例子,考虑一个销售情况表。每个条目都有一个ID作为主键。但是绝大多数查询都要求一个日期和另一个日期之间的数据,因此最佳的聚集索引键将是销售日期,而不是ID。具有与主键不同的聚簇索引的另一个示例是选择性很低的键,例如“类别”或“状态”,即只有很少的不同值的键。将具有此低选择性键的聚簇索引键作为最左键,例如(state, id)常常是有意义的,因为范围扫描会查找特定“状态”中的所有条目。聚集索引)。这可能是一个有效的场景,典型的原因是批量插入性能至关重要时,因为与聚簇索引相比,堆具有明显更好的批量插入吞吐量。

评论


这里的“按索引排序,当索引可以通过不需停停排序的排序来满足排序时”是什么意思?

–迈克·谢里尔(Mike Sherrill)的“猫召回”
2012年12月1日下午6:13

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

– LCJ
16 Sep 9'在13:52



@ MikeSherrill'CatRecall'意味着索引内的排序与order by子句匹配,从而无需进行排序操作。例如,如果某个表上最常见的order by子句是DESC,则在该表上定义具有确切顺序的索引可能会很有用,以提高性能。

–汤姆·林特(Tom Lint)
19年11月5日在13:21

@ remus-rusanu写得很好,而且很可能是我在任一堆栈站点上阅读的最全面的答案。

– Eli
8月20日17:22

#2 楼

使用聚集索引的基本原因已在Wikipedia上阐明:聚簇会将数据块更改为某个不同的顺序以匹配索引,从而导致行数据按顺序存储。因此,在给定的数据库表上只能创建一个聚簇索引。聚簇索引可以极大地提高整体检索速度,但是通常仅在以聚簇索引相同或相反的顺序顺序访问数据时,或者在选择了一系列项目时才如此。
有一个人表,这些人有一个“国家”列和一个唯一的主键。这是一张人口统计表,所以这是我唯一关心的事情;
因此,我只能在“国家/地区”列中选择“在哪里”或“按国家/地区排序”;主键上的聚集索引对我没有任何好处,我没有通过PK访问此数据,而是通过另一列访问了它。由于我只能在一个表上有一个聚簇索引,因此将我的PK声明为“聚簇”将使我无法在“国家/地区”上使用聚簇索引。 SQL Server 6.5中的插入性能问题(至少希望与这里的大多数人无关)。

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

请注意,在更高版本中并非如此。

评论


没错,您提到了SQL Server 6.5:dba.stackexchange.com/questions/1584/…

– gbn
2011年11月11日下午5:18

#3 楼

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

评论


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

–Charles Burns
18年8月6日在20:22

#4 楼

一个非常常见的示例:



Customer表,带有CustomerID,作为CLUSTERED PRIMARY KEY

订购表,带有OrderID (PK), CustomerID, OrderDate和其他一些列

OrderPositionsOrderPositionID (PK), OrderId, ProductID, Amount, Price ...

您必须为定单表编制索引

当然,“几乎总是”“正确的答案”是正确的答案,但是大多数应用程序(不是BI) -报告)将基于客户工作(例如,您以客户278的身份登录网站并单击“我的订单”,或者业务员列出了客户4569的所有订单,或者您的发票例程将汇总客户137的所有订单)。

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

另一方面,如果您将Order表聚类为CustomerID,每次查询表中的CustomerId = ?时都不必进行多次键查找。

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

因为客户希望(希望)下达多个订单,所以我们必须添加OrderIDOrderDate(如果您通常对此进行排序)(如果是日期时间,则否则为客户) (每天最多只能订购一个)到CLUSTERED INDEX,并得到以下结果:通常,最多的查询会列出特定顺序的所有头寸,因此您应该创建带有CREATE UNIQUE CLUSTERED INDEX IX_Orders_UQ on Orders (CustomerID, OrderID)的PK作为OrderPositions,并在OrderPositionID上创建一个NONCLUSTERED

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

例如CustomerCustomerIDGenders的纯查找表是应通过其PK进行聚类的表的另一个示例(因为您通常将它们加入InvoiceTypesPaymentTypeGenderId)。

#5 楼

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

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

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