聚集索引必须唯一吗?

94

如果集群索引不唯一会发生什么?插入的行是否会流向某些“溢出”页面,从而导致性能下降?

它是否可以“强制”唯一化?最好的方法是什么?

我之所以提问,是因为我目前正在使用聚集索引来将我的表划分为逻辑部分,但性能一般,最近我得到了建议将我的聚集索引设置为唯一。我想听听第二个意见。

5个回答

105

它们不一定要是唯一的,但鼓励使用唯一值。
到目前为止,我还没有遇到过想在非唯一列上创建CI的情况。

在非唯一列上创建CI会发生什么?

如果聚集索引不是唯一索引,SQL Server将通过添加称为唯一化器的内部生成值来使任何重复键唯一。

这会导致性能下降吗?

添加唯一化器肯定会增加计算和存储的开销。
这种开销是否会明显取决于几个因素:

  • 表中包含的数据量有多少。
  • 插入速率如何。
  • CI在选择时使用的频率(当不存在覆盖索引时,几乎总是使用)。

编辑
评论中Remus指出,确实存在创建非唯一CI的用例。我还没有遇到过这些场景只是显示了我自己的暴露度或能力不足(选择自己喜欢的)。


39
+1因为你所说的都是正确的,但我想补充一点:当范围扫描某个(非唯一)列是主要的访问模式时,非唯一CI是相当常见的。 - Remus Rusanu
1
@Remus Rusanu:我曾经考虑过在我的场景陈述中添加免责声明,例如“但这并不意味着什么”。感谢您指出了一个可能有用的场景。 - Lieven Keersmaekers
4
@Remus: 所以你的意思是这种情况是针对一个非唯一列,例如 'Departmentid' ,您查询类似于 'DepartmentId BETWEEN 1 and 100' 的内容? 编辑 啊,我明白你的意思了,是的,日志表中的日期列也是一个很好的例子。 - littlegreen
嘿,我有一个事件流表,在其中存在多个具有相同“AggregateId”的行,该列为GUID类型。对表执行的唯一查询是获取给定AggregateId的所有事件。我想知道这应该是聚集索引还是非聚集索引? - Shayan C
@ShayanC - 如果检索性能是您的主要目标,我建议将其作为CI,这样在检索给定ID的所有行时可以节省IO。不过,对于所有性能方案,唯一确定的方法是进行测量。 - Lieven Keersmaekers
我使用非唯一聚类键来获取数百万用户的用户边界数据。查询通常会为单个用户获取少量记录(1-100条),因此数据在{业务ID,用户ID}上进行聚类。这将使每个用户的数据聚集在一个单独的数据页上,当获取单个用户的所有记录时,使获取非常高效。顺便说一句,这也消除了任何类型的用户数据查询所需的任何辅助索引,因为用户的所有数据都在单个数据页上,任何扫描均完全在内存中进行。 - Triynko

33

我喜欢看看索引女王金伯利·特里普在这个话题上有什么说法:

我要从聚集键的推荐开始,原因有几个。首先,这是一个容易做出的决定,其次,早期做出这个决定有助于主动预防某些类型的碎片化。如果您能预防某些基表碎片化类型,那么您可以最小化一些维护活动(其中一些在SQL Server 2000中,以及在SQL Server 2005中较少)需要将表离线。好的,我稍后会谈到重建的内容.....

让我们从我在聚集键中寻找的关键要素开始:

* Unique
* Narrow
* Static

为什么要选择唯一的聚集键?
聚集键应该是唯一的,因为聚集键(如果存在)被用作所有非聚集索引的查找键。以书后的索引为例,如果你需要找到索引指向的数据,那么这个索引条目必须是唯一的,否则你要找的是哪个索引条目呢?因此,当你创建聚集索引时,它必须是唯一的。但是,SQL Server并不要求你的聚集键必须基于唯一的列。你可以选择任何你想要的列来创建聚集索引。在内部,如果聚集键不是唯一的,SQL Server会通过在数据中添加一个4字节的整数来使其唯一。因此,如果聚集索引创建在非唯一的列上,不仅在索引创建时会有额外的开销,还会浪费磁盘空间,在插入和更新操作上会有额外的成本,在SQL Server 2000中,聚集索引重建时还会有额外的成本(因为选择了不合适的聚集键,这种情况更加常见)。

来源: 越来越频繁的聚类键辩论 - 再次出现!


一个问题是,Queen建议使用newsequentialid来唯一标识数据,但如果您不指定,SQL Server会生成自己的唯一标识符。那么,是否仍有必要添加自己的顺序ID呢? - littlegreen
3
@littlegreen说,如果你坚持使用GUID(在聚集索引中确实非常不好),那么至少要使用newsequentialid()来获得几乎按顺序排列的GUID。但是,如果添加自己的唯一ID(我总是喜欢INT IDENTITY),然后你就可以使用它(例如建立一个FK关系)。SQL Server添加的唯一标识符对你来说是不可见的,因此它们只是你无法利用的开销。 - marc_s
1
@littlegreen:更好的方法是,将您的聚集索引设置在(ID INT IDENTITY)上,并将其他字段(如果需要)放入单独的非聚集索引中。聚集索引应尽可能小-毕竟,聚集索引列也被添加到该表中每个非聚集索引的每个条目中,所以不要浪费字节来创建宽聚集索引! - marc_s
1
是的,但这样我就失去了将所有部门数据分组以及能够一次性插入/删除/检索整个部门的好处。我的数据将变得分散,对整个部门甚至整个公司进行插入/删除操作将会很慢。我的查询只在单个公司上运行,并且经常需要更新整个数据集。 - littlegreen
@littlegreen: true - 我猜你只能进行一些性能测试,看看哪个选项对你来说最好。 - marc_s
显示剩余2条评论

9
集群索引必须是唯一的吗?
不需要,有时它们不是唯一的更好。
考虑一个带有半随机唯一EmployeeId和每个员工的DepartmentId的表:如果您的选择语句是
SELECT * FROM EmployeeTable WHERE DepartmentId=%DepartmentValue%

如果DepartmentId不是唯一索引,那么最好将其作为聚集索引来提高性能(尤其是因为这样可以确保给定的DepartmentId内的所有记录都被聚集)。例如,Clustered Index Design Guidelines中就指出:

除了少数例外情况外,每个表都应该定义一个聚集索引,该索引基于以下列或列组:

  • 可用于频繁使用的查询。
  • 提供高度的唯一性。
  • 可用于范围查询。
例如,如果大多数查询都想选择给定城镇内的记录,则选择“Country”作为聚集索引并不好。

是的,这就是我之前的想法,但我也得到了完全相反的建议,所以我想知道哪个是真的。你有任何参考资料吗? - littlegreen
@littlegreen,我编辑了我的答案,试图回答你的问题。 - ChrisW
谢谢。好的,我明白你的观点了。但是如果你经常一次性插入整个国家的数据,使用(国家,城镇)的聚集索引可能会让我感到麻烦,因为它需要对数据进行排序。另一方面,在插入之前进行排序并不会带来太多麻烦... - littlegreen
3
在您的示例中,一个在{DepartmentID, EmployeeID}上的唯一聚集索引会更好。为什么要让系统创建一个唯一标识符(uniqueifier),而您现有的字段可以提供更少开销的唯一性(可能是四个字节的INT),并且可以让您在索引内运行更多查询? - user565869

-1

如果您正在调整旧数据库,则这是一个及时雨。我正在解决20年历史的数据库性能问题。它有具有3到8个列的非聚集主键。我可以选择一个具有广泛分布的列,而不是使用所有8个列来确保唯一性,并且应用了Uniqueifier。它是一个Int,但是通过使用像项目ID这样的列,它可以处理2147483647个唯一的项目ID,足以满足大多数用例。如果不够,请将第二或第三列添加到群集中。这可以在应用程序层不进行任何编码修改的情况下完成。20年的生产和管理不必要求进行重大重写。


1
目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

-1
关于“聚集索引是否必须唯一?”的问题 不是的! 想象一下这种情况, 你有100条记录 你想要ID = 50-59(ID值)的记录 聚集索引会扫描记录,直到找到ID = 50 它会收集记录,直到遇到ID = 60并停止(聚集索引知道在59之后不会再有记录) 所以聚集可以被看作是ORDER BY的特殊情况
现在 如果你的表有一个ID列来确保记录的唯一性,并且有一个UID用于插入记录的用户,你可以将其聚集在UID上,这样前端就可以通过UID请求记录,而主键仍然是ID。 这种情况取决于你如何使用数据。

1
这些都已经在2010年发布的三个答案中涵盖了。他们的共同观点是:这取决于访问路径。你只是又举了一个例子。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接