非集群主键困境
Nonclustered primary key dilemma
假设我们必须为 Whosebug 问题定义最佳索引。但是,我们不要采用实际 Posts table 的架构,我们只包括那些实际相关的列:
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 table 中的 none 对它们有主键约束,也没有标识列。有很多 unique/non-unique clustered/non-clustered 个索引。
使用场景
所以基本上帖子的两个主要场景:
- 它们按时间顺序按
LastActivityDate
列降序显示(或者可能 LastEditDate
我没有在上面包括因为它不是那么重要)
- 它们单独显示在问题详情中
- 答案按投票顺序显示在问题详细信息页面上(
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 被索引 2 非常有效地覆盖,因为它是聚集的并且完全覆盖;我们还可以轻松高效地进行结果分页;
- 场景 #2 在某种程度上被唯一索引 1(获取问题)和非唯一索引 3 覆盖,以获取按
ScoreCount
排序的所有相关答案(场景 #3);如果我们决定按时间顺序排列也包含在索引 2 中的答案;
问题 1
SQL 内部结构使得 SQL 隐式地将聚集键添加到非聚集索引 以便它可以在行存储中定位记录。
- 如果聚簇索引是唯一的,那么这就是将添加到非聚簇索引的键,并且
- 如果聚类索引是非唯一的,SQL 应该生成它自己的
UniqueId
并使用那个
因为我还在 table 上添加了一个 非集群主键 (这在设计上必须是唯一的),我想知道是否 SQL 仍将在聚集非唯一索引上提供自己的唯一键,或者 它将使用非聚集主键来唯一标识每个记录 吗?
问题 2
因此,如果不使用主键来定位行存储(聚集索引)上的记录,那么实际创建 PK 是否有意义?在这种情况下,这样做会更好吗?
create unique index UX_Posts_Id
on Posts(Id);
-- include (Title, Body, ScoreCount);
最好也包括注释掉的列,但那样会使该索引效率低下,因为它在缓存方面会更糟...为什么我要问是否创建该索引而不是创建该索引会更好primary key
约束是因为我们可以在此索引中包含其他非键列,而当我们添加内部生成唯一索引的 PK 约束时我们不能这样做...
问题 3
我知道 LastActivityDate
更改是聚簇索引所不希望的,但我们必须考虑这样一个事实,即此列在变得或多或少静态之前更有可能更改一段时间,所以它不应该导致过多的索引碎片,因为每当 LastActivityDate 发生变化时,记录大部分都会附加到末尾。一些任意页面上的索引碎片永远不会发生,因为一些新记录将被插入到一些旧的(呃)页面中,因为 LastActivityDate
只会增加。因此,大多数修改将发生在最后一页。
所以问题是这些更改是否有害,因为 LastActivityDate
不是集群索引键的最佳候选者:
- 它不是唯一的 - 虽然有人可能会争论这一点,特别是如果我们将
datetime
更改为 datetime2
并使用更高精度的函数 sysdatetime()
并将索引设置为 unique
- 它很窄 - 差不多
- 它不是静态的 - 但我已经解释了它是如何变化的
- 它一直在增加
Since I've also added a nonclustered primary key on the table (which
must by design be unique), I would like to know whether SQL will still
supply its own unique key on clustered non-unique index or will it use
nonclustered primary key to uniquely identify each records instead?
SQL 当给定的非唯一聚集索引键值不唯一时,服务器添加一个 4 字节 "uniqueifier"。所有非聚集索引叶节点(包括主键)都将包括 LastActivityDate 加上唯一标识符(如果存在)作为行定位符。此处仅对于具有相同 LastActivityDate 的帖子才需要内部唯一标识符,因此我预计实际上需要唯一标识符的行相对较少。
So if primary key isn't used to locate records on row store (clustered
index) does it even make sense to actually create a PK? Would in this
case be better to rather do this?
从数据建模的角度来看,每个关系 table 都应该有主键。可以根据优化性能的需要将隐式创建的索引声明为聚簇或非聚簇。如果 LastActivity
是性能更好的选择,那么主键索引必须是非聚集的。这个主键索引将提供检索单例帖子所需的索引。
不幸的是,SQL服务器没有提供一种方法来指定主键和唯一约束定义中包含的列。在这种情况下,可以改变规则并使用唯一索引而不是声明的主键约束,以避免冗余索引的成本和包含列的好处。唯一索引在功能上等同于主键,可以被外键约束引用。
So the question is whether these changes can be harmful as
LastActivityDate isn't the best candidate for clustering index key
LastActivityDate
无论精度水平如何(禁止单线程插入或重试逻辑),都永远不能保证是唯一的。一种方法是 LastActivityDate
和 Id
上的复合主键。需要使用这两个值来检索个别帖子。这将消除对先前讨论的单独唯一索引 Id 的需要。
我对 LastActivityDate
作为最左边的聚簇索引键列的最大担忧是,它可能会因最近的帖子而经常更改。这将需要大量的行移动来维护逻辑键顺序,与当前静态 Id 键相比可能会显着影响并发性,并且需要在每次更改时更新非聚集索引行定位器值。因此,即使这个聚簇索引键可能是许多查询的最佳选择,但在高度事务性系统上的其他成本可能会超过收益。
假设我们必须为 Whosebug 问题定义最佳索引。但是,我们不要采用实际 Posts table 的架构,我们只包括那些实际相关的列:
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 table 中的 none 对它们有主键约束,也没有标识列。有很多 unique/non-unique clustered/non-clustered 个索引。
使用场景
所以基本上帖子的两个主要场景:
- 它们按时间顺序按
LastActivityDate
列降序显示(或者可能LastEditDate
我没有在上面包括因为它不是那么重要) - 它们单独显示在问题详情中
- 答案按投票顺序显示在问题详细信息页面上(
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 被索引 2 非常有效地覆盖,因为它是聚集的并且完全覆盖;我们还可以轻松高效地进行结果分页;
- 场景 #2 在某种程度上被唯一索引 1(获取问题)和非唯一索引 3 覆盖,以获取按
ScoreCount
排序的所有相关答案(场景 #3);如果我们决定按时间顺序排列也包含在索引 2 中的答案;
问题 1
SQL 内部结构使得 SQL 隐式地将聚集键添加到非聚集索引 以便它可以在行存储中定位记录。
- 如果聚簇索引是唯一的,那么这就是将添加到非聚簇索引的键,并且
- 如果聚类索引是非唯一的,SQL 应该生成它自己的
UniqueId
并使用那个
因为我还在 table 上添加了一个 非集群主键 (这在设计上必须是唯一的),我想知道是否 SQL 仍将在聚集非唯一索引上提供自己的唯一键,或者 它将使用非聚集主键来唯一标识每个记录 吗?
问题 2
因此,如果不使用主键来定位行存储(聚集索引)上的记录,那么实际创建 PK 是否有意义?在这种情况下,这样做会更好吗?
create unique index UX_Posts_Id
on Posts(Id);
-- include (Title, Body, ScoreCount);
最好也包括注释掉的列,但那样会使该索引效率低下,因为它在缓存方面会更糟...为什么我要问是否创建该索引而不是创建该索引会更好primary key
约束是因为我们可以在此索引中包含其他非键列,而当我们添加内部生成唯一索引的 PK 约束时我们不能这样做...
问题 3
我知道 LastActivityDate
更改是聚簇索引所不希望的,但我们必须考虑这样一个事实,即此列在变得或多或少静态之前更有可能更改一段时间,所以它不应该导致过多的索引碎片,因为每当 LastActivityDate 发生变化时,记录大部分都会附加到末尾。一些任意页面上的索引碎片永远不会发生,因为一些新记录将被插入到一些旧的(呃)页面中,因为 LastActivityDate
只会增加。因此,大多数修改将发生在最后一页。
所以问题是这些更改是否有害,因为 LastActivityDate
不是集群索引键的最佳候选者:
- 它不是唯一的 - 虽然有人可能会争论这一点,特别是如果我们将
datetime
更改为datetime2
并使用更高精度的函数sysdatetime()
并将索引设置为unique
- 它很窄 - 差不多
- 它不是静态的 - 但我已经解释了它是如何变化的
- 它一直在增加
Since I've also added a nonclustered primary key on the table (which must by design be unique), I would like to know whether SQL will still supply its own unique key on clustered non-unique index or will it use nonclustered primary key to uniquely identify each records instead?
SQL 当给定的非唯一聚集索引键值不唯一时,服务器添加一个 4 字节 "uniqueifier"。所有非聚集索引叶节点(包括主键)都将包括 LastActivityDate 加上唯一标识符(如果存在)作为行定位符。此处仅对于具有相同 LastActivityDate 的帖子才需要内部唯一标识符,因此我预计实际上需要唯一标识符的行相对较少。
So if primary key isn't used to locate records on row store (clustered index) does it even make sense to actually create a PK? Would in this case be better to rather do this?
从数据建模的角度来看,每个关系 table 都应该有主键。可以根据优化性能的需要将隐式创建的索引声明为聚簇或非聚簇。如果 LastActivity
是性能更好的选择,那么主键索引必须是非聚集的。这个主键索引将提供检索单例帖子所需的索引。
不幸的是,SQL服务器没有提供一种方法来指定主键和唯一约束定义中包含的列。在这种情况下,可以改变规则并使用唯一索引而不是声明的主键约束,以避免冗余索引的成本和包含列的好处。唯一索引在功能上等同于主键,可以被外键约束引用。
So the question is whether these changes can be harmful as LastActivityDate isn't the best candidate for clustering index key
LastActivityDate
无论精度水平如何(禁止单线程插入或重试逻辑),都永远不能保证是唯一的。一种方法是 LastActivityDate
和 Id
上的复合主键。需要使用这两个值来检索个别帖子。这将消除对先前讨论的单独唯一索引 Id 的需要。
我对 LastActivityDate
作为最左边的聚簇索引键列的最大担忧是,它可能会因最近的帖子而经常更改。这将需要大量的行移动来维护逻辑键顺序,与当前静态 Id 键相比可能会显着影响并发性,并且需要在每次更改时更新非聚集索引行定位器值。因此,即使这个聚簇索引键可能是许多查询的最佳选择,但在高度事务性系统上的其他成本可能会超过收益。