Stuart Blackler Asked: 2011-11-11 13:31:59 +0800 CST2011-11-11 13:31:59 +0800 CST 2011-11-11 13:31:59 +0800 CST 什么时候应该将主键声明为非聚集的? 772 在为我之前提出的另一个问题创建测试数据库时,我记得可以声明主键NONCLUSTERED 什么时候使用NONCLUSTERED主键而不是CLUSTERED主键? 提前致谢 sql-server primary-key 5 个回答 Voted Best Answer Remus Rusanu 2011-11-11T14:39:02+08:002011-11-11T14:39:02+08:00 问题不是“PK 何时应该是 NC”,而是您应该问“聚集索引的正确键是什么”? 答案实际上取决于您如何查询数据。聚集索引比所有其他索引都有一个优势:因为它总是包含所有列,所以总是覆盖。因此,可以利用聚集索引的查询当然不需要使用查找来满足某些预计列和/或谓词。 另一个难题是如何使用索引?典型的模式有以下三种: 探测,当在索引中搜索单个键值时 范围扫描,当检索到一系列键值时 按要求排序,当索引可以满足不需停止排序的排序时 因此,如果您分析您的预期负载(查询)并发现大量查询将使用特定索引,因为它们使用从索引中受益的特定访问模式,那么建议将该索引作为聚集索引是有意义的。 另一个因素是聚集索引键是所有非聚集索引使用的查找键,因此宽聚集索引键会产生连锁反应并扩大所有非聚集索引,而宽索引意味着更多页面、更多 I/O ,更多的记忆,更少的善良。 一个好的聚集索引是稳定的,它在实体的生命周期内不会改变,因为聚集索引键值的变化意味着该行必须被删除并重新插入。 并且一个好的聚簇索引不是随机增长的(每个新插入的键值都大于前一个值)以避免页面拆分和碎片(不弄乱FILLFACTORs)。 那么现在我们知道什么是好的聚集索引键,主键(它是一个数据建模逻辑属性)是否符合要求?如果是,那么 PK 应该是集群的。如果不是,那么 PK 应该是非集群的。 举个例子,考虑一个销售事实表。每个条目都有一个作为主键的 ID。但是绝大多数查询都需要一个日期和另一个日期之间的数据,因此最好的聚集索引键是销售日期,而不是ID。另一个具有与主键不同的聚集索引的示例是选择性非常低的键,例如“类别”或“状态”,只有很少的不同值的键。将具有这种低选择性键的聚集索引键作为最左边的键,例如(state, id),通常是有意义的,因为范围扫描会查找特定“状态”中的所有条目。 关于堆上可能存在非聚集主键的最后一点说明(即根本没有聚集索引)。这可能是一个有效的场景,典型的原因是大容量插入性能至关重要,因为与聚集索引相比,堆的大容量插入吞吐量要好得多。 Ben Brocka 2011-11-11T14:26:13+08:002011-11-11T14:26:13+08:00 使用聚集索引的基本原因在Wikipedia上有说明: 聚类将数据块更改为特定的不同顺序以匹配索引,从而使行数据按顺序存储。因此,在给定的数据库表上只能创建一个聚集索引。群集指数可以大大提高检索的总体速度,但通常仅在数据以相同或相反的顺序访问集群索引或选择一定范围的项目时才依次访问。 假设我有一张 People 表,这些人有一个 Country 列和一个唯一的主键。这是一个人口统计表,所以这些是我唯一关心的事情;哪个国家以及有多少独特的人与该国家相关联。 因此,我只可能在 Country 列中选择 WHERE 或 ORDER;主键上的聚集索引对我没有任何好处,我不是通过 PK 访问这些数据,而是通过另一列访问它。由于我只能在一张表上拥有一个聚集索引,因此将我的 PK 声明为聚集会阻止我在国家/地区使用聚集索引。 此外,这里有一篇关于Clustered vs Nonclustered Indexes的好文章,结果表明聚集索引在 SQL Server 6.5 中导致了插入性能问题(至少希望与我们这里的大多数人无关)。 如果您在 IDENTITY 列上放置聚集索引,那么您的所有插入都将发生在表的最后一页 - 并且该页在每个 IDENTITY 的持续时间内被锁定。没什么大不了的……除非你有 5000 人都想要最后一页。然后您对该页面有很多争用 请注意,在以后的版本中并非如此。 Bryan Johns 2012-11-29T20:07:29+08:002012-11-29T20:07:29+08:00 如果您的主键是 ,请UNIQUEIDENTIFIER确保指定它是NONCLUSTERED。如果将其设置为集群,则每次插入都必须对记录进行一系列洗牌才能将新行插入正确的位置。这将降低性能。 Thomas Franz 2016-04-20T23:10:51+08:002016-04-20T23:10:51+08:00 一个很常见的例子: Customer带有CustomerIDas 的表CLUSTERED PRIMARY KEY OrderID (PK), CustomerID, OrderDate带有和其他一些列的订单表 OrderPositions和OrderPositionID (PK), OrderId, ProductID, Amount, Price ... 你必须索引订单表 当然,“这取决于”是 - 几乎总是 - 正确答案,但大多数应用程序(不是 BI-Reports)将基于客户(例如,您以客户 278 身份登录网站并单击“我的订单”或职员列出客户 4569 的所有订单,否则您的发票例程将汇总客户 137 的所有订单)。 在这种情况下,通过OrderID. 是的,您将有SELECT ... WHERE OrderId = ?关于列出订单详细信息的查询,但这通常是简短且便宜的(3 次读取)索引搜索。 另一方面,如果您Order通过 对表进行聚类CustomerID,则每次查询表时都不必进行多次键查找CustomerId = ?。 CLUSTERED INDEX应该是 always ,UNIQUE否则 SQL Server 会添加一个不可见(= 不可用)UNIQUIFIER的 INT 列以确保唯一性 - 添加真实(可用)数据然后添加一些随机(取决于插入顺序)的东西会更有意义。 因为客户将(希望)下多于一份订单,我们将不得不添加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 查询。 纯查找表如GendersorInvoiceTypes或PaymentType是应按其 PK 聚集的表的另一个示例(因为您通常将它们加入GenderId,InvoiceTypeId或PaymentTypeId)。 crokusek 2013-04-17T14:44:04+08:002013-04-17T14:44:04+08:00 当通过使用某种性能度量认为聚集索引比聚集 PK 对整个系统更有益时。一张表只能有一个聚集索引。 性能度量的示例是单个查询时间(速度)、总查询时间与表的集成(效率)以及必须将许多包含列添加到非常大的非聚集索引中以实现类似于聚集的性能(大小)。 当通常使用不唯一的索引检索数据、包含空值(不允许在 PK 中)或出于次要原因(例如复制或审计跟踪记录标识)添加 PK 时,可能会发生这种情况。
问题不是“PK 何时应该是 NC”,而是您应该问“聚集索引的正确键是什么”?
答案实际上取决于您如何查询数据。聚集索引比所有其他索引都有一个优势:因为它总是包含所有列,所以总是覆盖。因此,可以利用聚集索引的查询当然不需要使用查找来满足某些预计列和/或谓词。
另一个难题是如何使用索引?典型的模式有以下三种:
因此,如果您分析您的预期负载(查询)并发现大量查询将使用特定索引,因为它们使用从索引中受益的特定访问模式,那么建议将该索引作为聚集索引是有意义的。
另一个因素是聚集索引键是所有非聚集索引使用的查找键,因此宽聚集索引键会产生连锁反应并扩大所有非聚集索引,而宽索引意味着更多页面、更多 I/O ,更多的记忆,更少的善良。
一个好的聚集索引是稳定的,它在实体的生命周期内不会改变,因为聚集索引键值的变化意味着该行必须被删除并重新插入。
并且一个好的聚簇索引不是随机增长的(每个新插入的键值都大于前一个值)以避免页面拆分和碎片(不弄乱
FILLFACTOR
s)。那么现在我们知道什么是好的聚集索引键,主键(它是一个数据建模逻辑属性)是否符合要求?如果是,那么 PK 应该是集群的。如果不是,那么 PK 应该是非集群的。
举个例子,考虑一个销售事实表。每个条目都有一个作为主键的 ID。但是绝大多数查询都需要一个日期和另一个日期之间的数据,因此最好的聚集索引键是销售日期,而不是ID。另一个具有与主键不同的聚集索引的示例是选择性非常低的键,例如“类别”或“状态”,只有很少的不同值的键。将具有这种低选择性键的聚集索引键作为最左边的键,例如
(state, id)
,通常是有意义的,因为范围扫描会查找特定“状态”中的所有条目。关于堆上可能存在非聚集主键的最后一点说明(即根本没有聚集索引)。这可能是一个有效的场景,典型的原因是大容量插入性能至关重要,因为与聚集索引相比,堆的大容量插入吞吐量要好得多。
使用聚集索引的基本原因在Wikipedia上有说明:
假设我有一张 People 表,这些人有一个 Country 列和一个唯一的主键。这是一个人口统计表,所以这些是我唯一关心的事情;哪个国家以及有多少独特的人与该国家相关联。
因此,我只可能在 Country 列中选择 WHERE 或 ORDER;主键上的聚集索引对我没有任何好处,我不是通过 PK 访问这些数据,而是通过另一列访问它。由于我只能在一张表上拥有一个聚集索引,因此将我的 PK 声明为聚集会阻止我在国家/地区使用聚集索引。
此外,这里有一篇关于Clustered vs Nonclustered Indexes的好文章,结果表明聚集索引在 SQL Server 6.5 中导致了插入性能问题(至少希望与我们这里的大多数人无关)。
请注意,在以后的版本中并非如此。
如果您的主键是 ,请
UNIQUEIDENTIFIER
确保指定它是NONCLUSTERED
。如果将其设置为集群,则每次插入都必须对记录进行一系列洗牌才能将新行插入正确的位置。这将降低性能。一个很常见的例子:
Customer
带有CustomerID
as 的表CLUSTERED PRIMARY KEY
OrderID (PK), CustomerID, OrderDate
带有和其他一些列的订单表OrderPositions
和OrderPositionID (PK), OrderId, ProductID, Amount, Price ...
当然,“这取决于”是 - 几乎总是 - 正确答案,但大多数应用程序(不是 BI-Reports)将基于客户(例如,您以客户 278 身份登录网站并单击“我的订单”或职员列出客户 4569 的所有订单,否则您的发票例程将汇总客户 137 的所有订单)。
在这种情况下,通过
OrderID
. 是的,您将有SELECT ... WHERE OrderId = ?
关于列出订单详细信息的查询,但这通常是简短且便宜的(3 次读取)索引搜索。另一方面,如果您
Order
通过 对表进行聚类CustomerID
,则每次查询表时都不必进行多次键查找CustomerId = ?
。CLUSTERED INDEX
应该是 always ,UNIQUE
否则 SQL Server 会添加一个不可见(= 不可用)UNIQUIFIER
的 INT 列以确保唯一性 - 添加真实(可用)数据然后添加一些随机(取决于插入顺序)的东西会更有意义。因为客户将(希望)下多于一份订单,我们将不得不添加
OrderID
或(如果您通常为此排序)OrderDate
(如果它是日期时间 - 否则客户将被限制为每天一个订单)并CLUSTERED INDEX
最终得到:CREATE UNIQUE CLUSTERED INDEX IX_Orders_UQ on Orders (CustomerID, OrderID)
相同的规则适用于
OrderPositions
表。通常大多数查询将列出特定顺序的所有位置,因此您应该使用OrderPositionID
asNONCLUSTERED
和 aUNIQUE CLUSTERED INDEX
on创建 PKOrderId, OrderPositionID
。顺便说一句:该
Customer
表由它的 PK 聚集是正确的(CustomerID
因为它是一个“顶级表”,并且在典型的应用程序中 - 主要通过它的 CustomerID 查询。纯查找表如
Genders
orInvoiceTypes
或PaymentType
是应按其 PK 聚集的表的另一个示例(因为您通常将它们加入GenderId
,InvoiceTypeId
或PaymentTypeId
)。当通过使用某种性能度量认为聚集索引比聚集 PK 对整个系统更有益时。一张表只能有一个聚集索引。
性能度量的示例是单个查询时间(速度)、总查询时间与表的集成(效率)以及必须将许多包含列添加到非常大的非聚集索引中以实现类似于聚集的性能(大小)。
当通常使用不唯一的索引检索数据、包含空值(不允许在 PK 中)或出于次要原因(例如复制或审计跟踪记录标识)添加 PK 时,可能会发生这种情况。