在 SQL Server 中,面向行的存储聚集索引和非聚集索引都被组织为 B 树。

(图片来源)
聚集索引和非聚集索引的主要区别在于聚集索引的叶级是表。这有两个含义。
- 聚集索引叶页上的行始终包含表中每个(非稀疏)列的内容(值或指向实际值的指针)。
- 聚集索引是表的主副本。
非聚集索引也可以通过使用INCLUDE
子句(自 SQL Server 2005 起)显式包含所有非键列来执行第 1 点,但它们是辅助表示,并且周围总是存在数据的另一个副本(表本身)。
CREATE TABLE T
(
A INT,
B INT,
C INT,
D INT
)
CREATE UNIQUE CLUSTERED INDEX ci ON T(A, B)
CREATE UNIQUE NONCLUSTERED INDEX nci ON T(A, B) INCLUDE (C, D)
上面的两个索引几乎相同。上层索引页包含键列的值,A, B
叶级页包含A, B, C, D
每个表只能有一个聚集索引,因为数据行本身只能按一种顺序排序。
上面引用的 SQL Server 在线书籍引起了很多混乱
在我看来,它会更好地表述为。
每个表只能有一个聚集索引,因为聚集索引的叶级行是表行。
这本书的在线报价并没有错,但您应该清楚,非聚集索引和聚集索引的“排序”是逻辑的,而不是物理的。如果您按照链表读取叶级别的页面并按插槽数组顺序读取页面上的行,那么您将按排序顺序读取索引行,但实际上页面可能未排序。人们普遍认为,对于聚集索引,行总是以与索引键相同的顺序物理存储在磁盘上,这是错误的。
这将是一个荒谬的实现。例如,如果将一行插入到 4GB 表的中间,SQL Server 不必在文件中复制 2GB 数据来为新插入的行腾出空间。
相反,会发生页面拆分。聚集索引和非聚集索引的叶级别的每个页面都具有File: Page
按逻辑键顺序排列的下一页和上一页的地址 ( )。这些页面不必是连续的或按密钥顺序排列。
例如链接的页面链可能是1:2000 <-> 1:157 <-> 1:7053
当页面拆分发生时,从文件组中的任何位置(从混合扩展区、小型表或属于该对象的非空统一扩展区或新分配的统一扩展区)分配新页面。如果文件组包含多个文件,这甚至可能不在同一个文件中。
逻辑顺序和连续性与理想化物理版本的不同程度就是逻辑碎片化程度。
在一个新创建的带有单个文件的数据库中,我运行了以下命令。
CREATE TABLE T
(
X TINYINT NOT NULL,
Y CHAR(3000) NULL
);
CREATE CLUSTERED INDEX ix
ON T(X);
GO
--Insert 100 rows with values 1 - 100 in random order
DECLARE @C1 AS CURSOR,
@X AS INT
SET @C1 = CURSOR FAST_FORWARD
FOR SELECT number
FROM master..spt_values
WHERE type = 'P'
AND number BETWEEN 1 AND 100
ORDER BY CRYPT_GEN_RANDOM(4)
OPEN @C1;
FETCH NEXT FROM @C1 INTO @X;
WHILE @@FETCH_STATUS = 0
BEGIN
INSERT INTO T (X)
VALUES (@X);
FETCH NEXT FROM @C1 INTO @X;
END
然后检查页面布局
SELECT page_id,
X,
geometry::Point(page_id, X, 0).STBuffer(1)
FROM T
CROSS APPLY sys.fn_PhysLocCracker( %% physloc %% )
ORDER BY page_id
结果到处都是。键顺序的第一行(值为 1 - 用下面的箭头突出显示)几乎位于最后一个物理页面上。

通过重建或重组索引以增加逻辑顺序和物理顺序之间的相关性,可以减少或消除碎片。
运行后
ALTER INDEX ix ON T REBUILD;
我得到了以下

如果表没有聚集索引,则称为堆。
非聚集索引可以建立在堆或聚集索引上。它们始终包含返回基表的行定位器。在堆的情况下,这是一个物理行标识符 (rid),由三个组件组成 (File:Page: Slot)。在聚集索引的情况下,行定位符是逻辑的(聚集索引键)。
对于后一种情况,如果非聚集索引已经自然地包含 CI 键列作为 NCI 键列或INCLUDE
-d 列,则不会添加任何内容。否则,丢失的 CI 键列会被默默地添加到 NCI。
SQL Server 始终确保键列对于两种类型的索引都是唯一的。但是,对于未声明为唯一的索引执行此操作的机制在两种索引类型之间有所不同。
uniquifier
对于键值与现有行重复的任何行,都会添加聚集索引。这只是一个升序整数。
对于未声明为唯一的非聚集索引,SQL Server 以静默方式将行定位符添加到非聚集索引键中。这适用于所有行,而不仅仅是那些实际重复的行。
聚集与非聚集命名法也用于列存储索引。论文增强 SQL Server 列存储状态
尽管列存储数据并没有真正“聚集”在任何键上,但我们决定保留将主索引称为聚集索引的传统 SQL Server 约定。