【问题标题】:Nonclustered primary key dilemma非聚集主键困境
【发布时间】:2015-10-23 18:59:46
【问题描述】:

假设我们必须为 Stackoverflow 问题定义最佳索引。但我们不要采用实际 Posts 表的架构,我们只包括那些实际相关的列:

create table Posts (
    Id int not null
        identity,
    PostTypeId tinyint not null,
    LastActivityDate datetime not null
        default getdate(),
    Title nvarchar(500) null, -- answers don't have titles
    Body nvarchar(max) not null,
    ...
)

我已经添加了Id 作为标识,尽管Data Stackexchange shows 没有一个表对它们有主键约束,也没有标识列。有许多只是唯一/非唯一的聚集/非聚集索引。

使用场景

所以基本上两个帖子的主要场景:

  1. 它们按时间顺序按其LastActivityDate 列(或者可能LastEditDate,我没有包括在上面,因为它不是那么重要)按降序显示
  2. 它们单独显示在问题详细信息中
  3. 答案按投票顺序显示在问题详细信息页面上(ScoreCount 列不属于我的上层代码)

索引优化

在上述情况下最好创建哪些索引,特别是如果我们说 #1 是最常见的情况,因此它必须非常快速地工作。

我想说更好的可能性之一是创建这些索引:

-- index 1
alter table Posts
add primary key nonclustered (Id);

-- index 2
create clustered index IX_Posts_LastActivityDate
on Posts(LastActivityDate desc);

-- index 3
create index IX_Posts_ParentId
on Posts(ParentId, PostTypeId)
include (ScoreCount);

这样我们基本上得到了三个索引,其中第二个是集群的。

所以为了让#1 工作得非常快,我在LastActivityDate 列上设置了聚集索引,因为当我们对它们进行范围比较时,聚集索引特别好。我们将按时间顺序对问题进行排序,从最新到最旧,因此我设置了排序方向,并且还在聚集索引中包含了类型。

那么我们解决了什么问题?

  1. 场景 #1 被索引 2 非常有效地覆盖,因为它已聚集并完全覆盖;我们还可以轻松高效地进行结果分页;
  2. 场景 #2 在某种程度上被唯一索引 1(获取问题)和非唯一索引 3 覆盖,以获取由ScoreCount 排序的所有相关答案(场景 #3);如果我们决定按时间顺序排列也包含在索引 2 中的答案;

问题 1

SQL 内部结构使得 SQL 隐式地将聚簇键添加到非聚簇索引,因此它可以在行存储中定位记录。

  • 如果聚集索引是唯一的,那么这就是将添加到非聚集索引的键,并且
  • 如果聚簇索引不唯一,SQL 应该会生成它自己的UniqueId 并使用它

由于我还在表上添加了一个非聚集主键(设计上必须是唯一的),我想知道 SQL 是否仍会在聚集的非聚集主键上提供自己的唯一键唯一索引还是会使用非聚集主键来唯一标识每条记录

问题 2

因此,如果不使用主键来定位行存储(聚集索引)上的记录,那么实际创建 PK 是否有意义?在这种情况下,这样做会更好吗?

create unique index UX_Posts_Id
on Posts(Id);
-- include (Title, Body, ScoreCount);

还包括注释掉的列会很好,但是那样会使这个索引效率低下,因为它在缓存中会更糟......为什么我要问是否创建这个索引而不是 @ 会更好987654332@ 约束是因为我们可以在此索引中包含额外的非键列,而当我们添加内部生成唯一索引的 PK 约束时我们不能这样做......

问题 3

我知道 LastActivityDate 的变化是聚集索引所不希望的,但我们必须考虑这样一个事实,即该列在变得或多或少静态之前更有可能发生一段时间的变化,所以它应该'不会导致过多的索引碎片,因为每当 LastActivityDate 更改时,记录大多会附加到末尾。某些任意页面上的索引碎片永远不会发生,因为某些新记录将插入到某些旧(er)页面中,因为LastActivityDate 只会增加。因此,大多数修改将发生在最后一页。

所以问题是这些更改是否有害,因为 LastActivityDate 不是集群索引键的最佳候选者:

  • 这不是唯一的 - 尽管有人可能会对此争论不休,尤其是如果我们将 datetime 更改为 datetime2 并使用更高精度的函数 sysdatetime() 并将索引设置为unique
  • 很窄 - 差不多
  • 它不是静态的 - 但我已经解释了它是如何变化的
  • 不断增加

【问题讨论】:

    标签: sql-server primary-key clustered-index sql-optimization non-clustered-index


    【解决方案1】:

    因为我还在表上添加了一个非聚集主键(它 必须按设计是唯一的),我想知道 SQL 是否仍然 在聚集的非唯一索引上提供自己的唯一键或将使用 非聚集主键来唯一标识每条记录?

    当给定的非唯一聚集索引键值不唯一时,SQL Server 会添加一个 4 字节的“唯一标识符”。所有非聚集索引叶节点,包括主键,都将包括 LastActivityDate 加上唯一符(如果存在)作为行定位符。只有具有相同 LastActivityDate 的帖子才需要内部唯一符,因此我预计相对较少的行实际上需要唯一符。

    因此,如果不使用主键来定位行存储上的记录(集群 index) 实际创建 PK 是否有意义?会在这 最好还是这样做?

    从数据建模的角度来看,每个关系表都应该有主键。可以根据需要将隐式创建的索引声明为集群或​​非集群以优化性能。如果LastActivity 是性能更好的选择,那么主键索引必须是非聚集的。此主键索引将提供检索单例帖子所需的索引。

    不幸的是,SQL Server 没有提供一种方法来指定主键和唯一约束定义中包含的列。这是一种可以改变规则并使用唯一索引而不是声明的主键约束的情况,以避免冗余索引的成本和包含列的好处。唯一索引在功能上与主键相同,可以被外键约束引用。

    所以问题是这些变化是否有害,因为 LastActivityDate 不是聚类索引键的最佳候选者

    无论精度水平如何(除非单线程插入或重试逻辑),都不能保证单独的LastActivityDate 是唯一的。一种方法可以是LastActivityDateId 上的复合主键。需要使用这两个值来检索单个帖子。这将消除对先前讨论的单独唯一索引 ID 的需要。

    对于LastActivityDate 作为最左侧的聚集索引键列,我最大的担忧是它可能会因最近的帖子而经常更改。这将需要大量行移动来维护逻辑键顺序,与当前静态 Id 键相比可能会显着影响并发性,并且每次更改时都需要更新非聚集索引行定位器值。因此,即使此聚集索引键对于许多查询可能是最佳的,但在高度事务性系统上的其他成本可能会超过收益。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-02
      • 2014-07-30
      • 2011-08-21
      • 2011-03-01
      • 2011-05-15
      相关资源
      最近更新 更多