我有一个存储数百万行的表。它看起来像这样:
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应用程序?
答案 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
旧页面,因为它可以很容易看到,将保持填充一半(只有1
到6
的记录才能到达那里。
这会增加索引大小。
让我们创建两个样本表:
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。