身份列作为聚集索引是个坏主意?

Kai*_*Kai 8 sql-server clustered-index identity

对我来说,这只是几个月的 SQL Server 编程,所以我的知识在很多方面都不是很好。在一个已经存在的工作项目中,我遇到了许多带有聚集索引的大型复合主键的表。从我收集到的信息来看,具有聚集索引的大列/复合列对性能的影响非常大,有时逻辑解决方案是标识列。但与此同时,我遇到了很多人对标识列的过度使用进行抨击。

但我从未遇到过标识列是个坏主意的例子。

最近我们标准化了每个表都应该有一个标识列作为聚集索引 - 无论我们是否将它用作 PK,因为我们需要它用于某些导出目的。

所以我想要一些例子,在现实生活场景中,使用标识列作为聚集索引是一个坏主意。

虽然有时它让我们的生活变得轻松,但我从未遇到过它会被认为是糟糕的情况。

PS:我觉得我的问题有点幼稚,但它让我很烦恼,所以我不得不问一下。

小智 6

我通常使用标识列作为聚集主键。然而,在某些(罕见?)情况下,由于 LastPageInsertLatchContention,这并不理想。如果表中充满了数据,就会发生这种情况。由于身份键,所有这些 INSERT 都想写入表的最后一页(索引)。所以这个页面可以被锁定,并且使用另一个解决方案可能会更好。

详情。

  • 我认为“充满了大量数据”应该更明确:“插入量始终很高”是系统更直接的原因,而不仅仅是表很大。 (2认同)

Ser*_*ton 6

我从未见过不是索引的标识列,通常是主键。

现在我们需要区分主键(PK)和聚集索引(CI),首先是关于数据库模式的逻辑,主键是使表中的行与其他所有行不同的原因,外键对于其他表。标识列始终是候选键,但它是人为的,您可能希望自然的候选键作为 PK。

相反,聚集索引是关于如何从数据创建索引和存储索引的。只能有一个聚集索引,并且它将是唯一引用表中数据的索引。所有其他索引都将引用聚集的索引。

通常 PK 也是 CI,但这只是默认行为。我见过,有时还创建了不是 CI 的 PK:PK 是自然键,CI 是身份列。那是因为,简化索引的工作方式,CI 定义中的数据越小,索引越快,并且 CI 需要尽可能快,所以在 PK 很大的情况下,将标识列作为聚集索引并使PK成为非聚集索引将提高性能。

所以在我看来,使用标识列作为聚集索引并不是一个坏主意,但这并不意味着它也应该是主键。

我能想到的唯一一个标识列可能是一个糟糕选择的情况是,当传入的数据量如此之大时,即使创建标识也会影响性能。


Dav*_*oft 6

我想要一些在现实生活场景中的例子,其中使用标识列作为聚集索引是一个坏主意。

一般来说,只要身份聚集索引只是一个冗余的额外索引,这就是一个坏主意。您只能获得一个聚集索引,因此如果您选择了错误的索引,则会增加所有事务的成本。

每当您已经需要复合键或自然键时,将标识列作为聚集索引是一个坏主意。

应该使用复合键的两个常见场景是“链接表”和“嵌套表”,例如:

create table a(id int identity primary key)
create table b(id int identity primary key)
create table a_b
( 
  a_id int not null references a,
  b_id int not null references b,
  constraint pk_a_b primary key (a_id,b_id),
  constraint ak_a_b unique (b_id, a_id)
)
Run Code Online (Sandbox Code Playgroud)

添加标识列聚集索引是无用且有害的。

第二种常见的例子是“嵌套”表,其中单个复合 PK 是唯一必需的索引:

create table a(id int identity primary key)
create table a_detail
(
  a_id int not null references a,
  id int not null identity, 
  constraint pk_a_detail primary key (a_id,id) 
)
Run Code Online (Sandbox Code Playgroud)

自然键毫无争议的用例包括查找表,例如

create table region
(
  region_code char(3) not null primary key,
  name nvarchar(200),
  description nvarchar(200)
)
Run Code Online (Sandbox Code Playgroud)

稍微有争议的,但我认为是正确的,是使用顺序 UNIQUEIDENTIFIER 作为聚集 PK,因此这也是添加带有聚集索引的 IDENTITY 列是有害的场景。


Dav*_*ett 5

对哪些键/索引进行聚类并不是一门精确的科学 - 聚集索引的最佳用途可能因表的使用(以及该键中列的使用)而异。

由于不需要额外的行查找来查找在搜索索引后找到的行的数据,因此聚集键对于在一个范围内挑选出许多行的查询更有效。它也有助于单行查找,但差异并不明显。例如,我们有一个经常按对象所有者 ID(而不是作为主键的对象 ID)搜索的表,因此我们的应用程序将该列上的索引作为聚集键更有效,同样有时如果经常搜索日期范围内的行,最好在常用引用的日期列上使用聚集键。

如果给定表的 PK 通常是连接目标,那么将其 PK 聚类会有所帮助,因为对于某些连接操作,进一步页面查找的减少可能是一个很大的好处,当然,如果您有基于真实数据的 PK(而不是一个代理键,如自动递增数字或 UUID),它受范围查询的约束,它具有您期望的好处。这些原因就是为什么在考虑其他因素之前,将您的 PK 聚类通常是一个很好的开始位置,因此它是一个常见的建议(有时是自动应用的默认值)。

附带说明:如果您最终使用 UUID 列而不是递增的整数类型作为表上的 PK,那么在其上进行聚类可能对性能有害,因为通过将“随机”数据插入索引而创建的额外页面拆分(在聚集索引上拆分的每个页面也会导致表上所有其他索引上的额外 IO 活动),这会减慢插入速度并随着时间的推移加剧碎片问题。因此,在这种情况下,将不同的索引聚集在一起通常会更好(或者有时根本没有聚集索引,尽管这在 Azure [1] 的SQL Server 上是不可能的,并且没有聚集键的情况很少见)总体上是一个好处而不是一个坏处)。

[1] 在 Azure SQL 上有一个堆(一个没有集群键的表)已经有一段时间了,尽管在预 SQL Server 中发现类似的警告很少是一个好主意