SQL 中的聚集索引 vs 非聚集索引(完整指南与示例)
Source: Dev.to
引言:为什么需要索引?
查询数据库时,速度和效率至关重要。索引是一种数据结构,指引数据库快速定位特定数据集,从而显著加快查询速度。
类比
- 书本 – 与其逐页翻找章节,不如查阅书后面的索引。
- 酒店 – 与其敲开每个房间的门,不如查看大堂地图找到 5001 号房间。
SQL 如何存储数据
当你创建表并插入数据时,SQL 会将数据存放在磁盘文件中,划分为固定大小的 8 KB 块,称为 页(page)。
数据页包括:
- 页头(Page Header) – 页的元数据。
- 数据行(Data Rows) – 实际的行数据(大小可变)。
- 偏移数组(Offset Array) – 内部映射,记录每行在页中的起始位置。
如果没有定义索引,表会以 堆(heap) 方式存储:
- 写入逻辑 – 新行按插入顺序追加到下一个可用页(未排序)。
- 读取逻辑(全表扫描) – 为查找特定记录(例如
CustomerID = 14),SQL 必须扫描每一页直到定位到该行。 - 权衡 – 写入快,但读取性能差。
聚集索引(Clustered Index)
聚集索引决定数据行的物理顺序。当你在某列上创建聚集索引(例如 User_ID)时,SQL 会按该列对所有现有页进行物理排序。
SQL 使用 B‑Tree(平衡树) 来导航已排序的数据:
- 根节点(Root node) – 指向中间页。
- 中间节点(Intermediate nodes) – 指向数据范围(例如左侧 1‑10,右侧 11‑20)。
- 叶节点(Leaf nodes) – 在聚集索引中,这些就是实际的数据页。
特点
- 每个表只能有 一个 聚集索引(数据只能按一种方式排序)。
- 适合作为主键:主键唯一且很少更新。更新聚集索引列需要移动整行,成本较高。
非聚集索引(Non‑Clustered Index)
非聚集索引在不改变底层表物理顺序的情况下提升读取性能。
创建非聚集索引时,SQL 会构建一个独立的 B‑Tree 结构:
- 叶节点 – 包含索引列的值以及 行标识符(Row Identifier,RID),后者指向行的精确位置(文件 ID、页号、偏移)。
- 导航 – SQL 先遍历非聚集 B‑Tree 到达叶节点,取回 RID,然后一次“跳转”到数据页获取整行数据。
特点
- 同一表上可以拥有 多个 非聚集索引。
- 适用于经常出现在
WHERE子句(如Last_Name)或连接条件中的列。
在 SQL Server 中创建索引
SQL Server 提供简洁的语法来创建索引。默认情况下,定义主键会创建聚集索引,且默认的索引结构是 B‑Tree。
-- 聚集索引
CREATE CLUSTERED INDEX idx_customers_id
ON sales.customers (customer_id);
-- 非聚集索引
CREATE NONCLUSTERED INDEX idx_customers_lastname
ON sales.customers (last_name);
(如果省略 NONCLUSTERED,SQL Server 默认创建非聚集索引。)
欲了解更多关于为表的聚集索引选择最佳唯一标识符的细节,请参阅我的相关帖子:How to Choose the Fastest Unique Identifier for a Clustered Index。