非聚集主键困境

时间:2015-08-01 01:29:49

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

假设我们必须为Stackoverflow问题定义最佳索引。但是,我们不要采用实际的帖子表的模式,让我们只包含那些实际相关的列:

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列不属于我的上层代码)
  4. 索引优化

    在上述情况下最好创建哪些指数,特别是如果我们说#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所涵盖的答案;
    3. 问题1

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

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

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

      问题2

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

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

      包含已注释掉的列会很棒,但是这样会使这个索引效率低下,因为它在缓存中会更糟糕...为什么我要问是否更好地创建这个索引而不是{ {1}}约束是因为我们可以在此索引中包含其他非键列,而在添加内部生成唯一索引的PK约束时我们无法执行相同操作...

      问题3

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

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

      • 它不是唯一的 - 尽管有人可能会争论这个问题,特别是如果我们将LastActivityDate更改为datetime并使用更高精度的函数datetime2 并将索引设置为sysdatetime()
      • 它很窄 - 非常
      • 这不是静态的 - 但我已经解释了它是如何变化的
      • 它一直在增加

1 个答案:

答案 0 :(得分:1)

  

因为我还在表上添加了非聚集主键(其中   必须设计独特),我想知道SQL是否仍然存在   在集群非唯一索引上提供自己唯一的密钥或将使用它   非聚集主键,以便唯一地标识每个记录?

当给定的非唯一聚簇索引键值不唯一时,SQL Server会添加一个4字节的“唯一符号”。所有非聚集索引叶节点(包括主键)将包括LastActivityDate和唯一性(如果存在)作为行定位器。这里只需要具有相同LastActivityDate的帖子的内部唯一符,所以我希望相对较少的行实际上需要一个唯一符。

  

因此,如果不使用主键来查找行存储上的记录(群集   index)实际创建PK是否有意义?会在这   更好的做法呢?

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

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

  

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

无论精确程度如何(单线程插入或重试逻辑除外),

LastActivityDate都无法保证是唯一的。一种方法可以是LastActivityDateId上的复合主键。需要使用两个值检索单个帖子。这将消除之前讨论过的单独的唯一索引Id的需要。

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