假设我已经有一个主键,它确保了唯一性。我的主键也是记录的排序索引。但是,我对磁盘中记录的物理顺序(如果有的话)的主键任务感到好奇。实际的问题是我可以为这些记录创建一个单独的聚集索引吗?
2 回答
根据与@Catcall 的讨论,这是在聚集表上测试覆盖二级索引的大小和性能特征的尝试。
所有测试都是在 MS SQL Server 2008 R2 Express 上完成的(在一个功率相当不足的 VM 内)。
尺寸
首先,我用二级索引创建了一个聚集表,并用一些测试数据填充它:
CREATE TABLE THE_TABLE (
FIELD1 int,
FIELD2 int NOT NULL,
CONSTRAINT THE_TABLE_PK PRIMARY KEY (FIELD1)
);
CREATE INDEX THE_TABLE_IE1 ON THE_TABLE (FIELD2) INCLUDE (FIELD1);
DECLARE @COUNT int = 1;
WHILE @COUNT <= 1000000 BEGIN
INSERT INTO THE_TABLE (FIELD1, FIELD2) VALUES (@COUNT, @COUNT);
SET @COUNT = @COUNT + 1;
END;
EXEC sp_spaceused 'THE_TABLE';
最后一行给了我以下结果......
name rows reserved data index_size unused
THE_TABLE 1000000 27856 KB 16808 KB 11008 KB 40 KB
因此,索引的 B-Tree (11008 KB) 实际上小于表的 B-Tree (16808 KB)。
速度
我在表中的数据范围内生成了一个随机数,然后将其作为从表中选择整行的标准。重复 10000 次,测量总时间:
DECLARE @I int = 1;
DECLARE @F1 int;
DECLARE @F2 int;
DECLARE @END_TIME DATETIME2;
DECLARE @START_TIME DATETIME2 = SYSDATETIME();
WHILE @I <= 10000 BEGIN
SELECT @F1 = FIELD1, @F2 = FIELD2
FROM THE_TABLE
WHERE FIELD1 = (SELECT CEILING(RAND() * 1000000));
SET @I = @I + 1;
END;
SET @END_TIME = SYSDATETIME();
SELECT DATEDIFF(millisecond, @START_TIME, @END_TIME);
最后一行产生的平均时间(10 次测量)为 181.3 毫秒。
当我将查询条件更改为:WHERE FIELD2 = ...
,所以使用二级索引时,平均时间为 195.2 毫秒。
执行计划:
因此(选择 PK 与选择覆盖二级索引的性能)似乎是相似的。对于大量数据,我怀疑二级索引可能会稍微快一些(因为它看起来更紧凑,因此对缓存友好),但我在测试中还没有达到这一点。
字符串测量
使用varchar(50)
as 类型FIELD1
和FIELD2
插入长度在 22 到 28 个字符之间的字符串给出了类似的结果。
尺寸为:
name rows reserved data index_size unused
THE_TABLE 1000000 208144 KB 112424 KB 95632 KB 88 KB
平均时间为: search on 254.7 msFIELD1
和 fir 296.9 ms FIELD2
。
结论
如果聚集表具有覆盖二级索引,则该索引将具有与表本身相似的空间和时间特征(可能稍慢,但幅度不大)。如果有效,您将拥有两个 B 树,它们对数据进行不同的排序,但在其他方面非常相似,从而实现拥有“第二个集群”的目标。
这取决于您的数据库管理系统。并非所有这些都实现了聚集索引。那些这样做的人有可能以不同的方式实施它们。据我所知,每个实现聚集索引的平台还提供了选择聚集索引中的列的方法,尽管通常主键是默认值。
在 SQL Server 中,您可以像这样创建非聚集主键和单独的聚集索引。
create table test (
test_id integer primary key nonclustered,
another_column char(5) not null unique clustered
);
我认为在 Oracle 中与此最接近的是索引组织表。我可能是错的。这与在 SQL Server 中创建具有聚集索引的表并不完全相同。
SQL Server 中的单个表不能有多个聚集索引。表的行一次只能按一个顺序存储。实际上,我想您可以将行存储在多个不同的订单中,但是您必须为每个订单复制全部或部分表。(虽然我在写这个答案的时候并不知道,DB2 UDB 支持多个聚集索引,这是一个相当古老的功能。它的设计和实现与 SQL Server 有很大的不同。)
主键的工作是保证唯一性。虽然这项工作通常是通过在主键列上创建唯一索引来完成的,但严格来说,唯一性和索引是两个不同的东西,具有两个不同的目标。唯一性旨在数据完整性;索引旨在提高速度。
主键声明并不打算为您提供有关磁盘上行顺序的任何信息。实际上,它通常会为您提供有关磁盘上索引条目顺序的一些信息。(因为主键通常使用唯一索引来实现。)
如果您从具有聚集索引的表中选择行,您仍然不能保证这些行将按照它们存储在磁盘上的相同顺序返回给用户。粗略地说,聚集索引帮助查询优化器更快地找到行,但它不控制这些行返回给用户的顺序。保证行返回给用户的顺序的唯一ORDER BY
方法是使用显式子句。(这似乎是一个相当常见的混淆点。当聚集索引上的裸 SELECT 没有按照他们期望的顺序返回行时,很多人似乎感到惊讶。)