我应该在此SQL 2005表上创建唯一的聚簇索引或非唯一聚簇索引吗?

时间:2010-04-30 21:08:51

标签: sql-server-2005 indexing

我有一个存储数百万行的表。它看起来像这样:

Table_Docs
ID, Bigint (Identity col)
OutputFileID, int
Sequence, int
…(many other fields)

我们发现自己处于这样一种情况,即设计它的开发人员将OutputFileID作为聚集索引。它不是唯一的。这个ID可能有数千条记录。它对使用此表的任何进程都没有好处,因此我们计划将其删除。

这个问题,是什么改变它...我有两个候选人,ID身份栏是一个自然的选择。但是,我们有一个在该表上执行大量更新命令的进程,它使用Sequence来执行此操作。序列是非唯一的。大多数记录只包含一个,但大约20%可以有两个或多个具有相同序列的记录。

INSERT应用程序是一个VB6的crud,在桌面上抛出数千个插入命令。 Inserted值永远不会以任何特定顺序排列。所以一个插入的序列可能是12345,下一个可能是12245.我知道这可能会导致SQL移动大量数据以保持聚簇索引的顺序。但是,插入序列通常接近顺序。所有插入都将在聚簇表的末尾进行。例如:我有500万条记录,序列跨越1到5百万。 INSERT应用程序将在任何给定时间在该范围的末尾插入序列。重新排序数据应该是最小的(最多数万条记录)。

现在,UPDATE应用程序是我们的.NET明星。它在Sequence列上执行所有UPDATES。 “Update Table_Docs Set Feild1=This, Field2=That…WHERE Sequence =12345” - 每天数十万。更新完全,完全,随机,触及表格中的所有点。

所有其他进程只是在这个(网页)上做SELECT。常规指数涵盖那些。

所以我的问题是,什么更好...。在ID列上的唯一聚簇索引,使INSERT应用程序受益,或者序列上的非唯一聚簇索引,有益于UPDATE应用程序?

3 个答案:

答案 0 :(得分:4)

首先,我会绝对建议拥有聚集索引!

其次,您的聚集索引should be

  • 静态(永远或几乎不会改变)
  • 独特
  • 不断增加

所以INT IDENTITY是一个非常深思熟虑的选择。

当您的群集密钥不唯一时,SQL Server将为这些列值添加一个4字节的唯一符 - 从而使您的群集密钥和该表上的所有非聚集索引更大且不太理想。

所以在你的情况下,我会选择ID - 它是狭窄的,静态的,独特的和不断增加的 - 不能比这更优化!由于Sequence在UPDATE语句中被大量使用,所以肯定也会在其上放置非聚集索引!

请参阅Kimberly Tripp的优秀blog posts on choosing the right clustering key,了解有关该主题的精彩背景信息。

答案 1 :(得分:2)

作为一般规则,您希望聚簇索引是唯一的。如果不是,SQL Server实际上会为它添加一个隐藏的“uniquifier”以强制它是唯一的,这会增加开销。

因此,您最好使用ID列作为索引。

正如旁注所示,使用标识列作为主键通常称为代理键,因为它不是数据中固有的。如果您有一个独特的自然键可能是一个更好的选择。在这种情况下,它看起来像你没有,所以使用唯一的代理键是有道理的。

答案 2 :(得分:1)

关于插件乱序的最糟糕的事情是页面拆分。

SQL Server需要在现有索引页面中插入新记录并且在那里找不到任何位置时,它会占用页面中一半的记录并将它们移动到新的记录中。

说,你有这些记录填满整个页面:

1 2 3 4 5 6 7 8 9

并需要插入10。在这种情况下,SQL Server只会启动新页面。

但是,如果你有这个:

1 2 3 4 5 6 7 8 11

10应该在11之前。在这种情况下,SQL Server会将记录从6移至11到新页面中:

6 7 8 9 10 11

旧页面,因为它可以很容易看到,将保持填充一半(只有16的记录才能到达那里。

这会增加索引大小。

让我们创建两个样本表:

CREATE TABLE perfect (id INT NOT NULL PRIMARY KEY, stuffing VARCHAR(300))
CREATE TABLE almost_perfect (id INT NOT NULL PRIMARY KEY, stuffing VARCHAR(300))

;
WITH    q(num) AS
        (
        SELECT  1
        UNION ALL
        SELECT  num + 1
        FROM    q
        WHERE   num < 200000
        )
INSERT
INTO    perfect
SELECT  num, REPLICATE('*', 300)
FROM    q
OPTION (MAXRECURSION 0)

;
WITH    q(num) AS
        (
        SELECT  1
        UNION ALL
        SELECT  num + 1
        FROM    q
        WHERE   num < 200000
        )
INSERT
INTO    almost_perfect
SELECT  num + CASE num % 5 WHEN 0 THEN 2 WHEN 1 THEN 0 ELSE 1 END, REPLICATE('*', 300)
FROM    q
OPTION (MAXRECURSION 0)

EXEC sp_spaceused N'perfect'
EXEC sp_spaceused N'almost_perfect'

perfect         200000   66960 KB    66672 KB    264 KB  24 KB
almost_perfect  200000   128528 KB   128000 KB   496 KB  32 KB

即使记录发生故障的概率只有20%,表格也会变成两倍。

另一方面,在Sequence上使用群集密钥会减少I/O两次(因为可以使用单个聚簇索引搜索而不是两个非聚簇索引)。

因此,我将获取数据的一个示例子集,将其插入到Sequence上的聚簇索引的测试表中,并测量生成的表大小。

如果它小于同一个表的大小的两倍且索引位于ID,我会选择Sequence上的聚集索引(因为得到的总I/O将是更小)。

如果您决定在Sequence上创建聚簇索引,请将ID设为非聚集PRIMARY KEY,并在UNIQUE上创建聚簇索引Sequence, ID。这将使用有意义的ID而不是不透明的uniquiefier。