P.K*_*P.K 1041 sql-server indexing performance clustered-index non-clustered-index
我对DB的了解有限,并且只使用DB作为应用程序员.我想知道Clustered
和Non clustered indexes
.我用谷歌搜索,发现的是:
聚簇索引是一种特殊类型的索引,它重新排序表中记录的物理存储方式.因此,表只能有一个聚簇索引.聚簇索引的叶节点包含数据页.非聚簇索引是一种特殊类型的索引,其中索引的逻辑顺序与磁盘上行的物理存储顺序不匹配.非聚簇索引的叶节点不包含数据页.相反,叶节点包含索引行.
我在SO中发现的是聚簇索引和非聚簇索引之间有什么区别?.
有人可以用简单的英语解释这个吗?
Shi*_*iji 1043
使用聚簇索引,行以与索引相同的顺序物理存储在磁盘上.因此,只能有一个聚簇索引.
对于非聚集索引,还有第二个列表,其中包含指向物理行的指针.虽然每个新索引都会增加写入新记录所需的时间,但您可以拥有许多非聚簇索引.
如果要返回所有列,通常从聚簇索引读取更快.您不必先进入索引,然后再进入表.
如果需要重新排列数据,则写入具有聚簇索引的表可能会更慢.
小智 583
聚簇索引意味着您要告诉数据库在磁盘上存储实际上彼此接近的近似值.这具有快速扫描/检索落入某些聚集索引值范围的记录的益处.
例如,您有两个表,Customer和Order:
Customer
----------
ID
Name
Address
Order
----------
ID
CustomerID
Price
Run Code Online (Sandbox Code Playgroud)
如果您希望快速检索某个特定客户的所有订单,您可能希望在Order表的"CustomerID"列上创建聚簇索引.这样,具有相同CustomerID的记录将在磁盘(群集)上彼此靠近地物理存储,这加速了它们的检索.
PS CustomerID上的索引显然不是唯一的,因此您需要添加第二个字段来"unquify"索引或让数据库为您处理,但这是另一个故事.
关于多个索引.每个表只能有一个聚簇索引,因为它定义了数据的物理排列方式.如果你想要一个类比,想象一个有很多桌子的大房间.您可以将这些表格放在一起形成多行,也可以将它们全部拉到一起形成一个大型会议桌,但不能同时形成两种方式.一个表可以有其他索引,然后它们将指向聚簇索引中的条目,而这些条目最终将说明在哪里找到实际数据.
Mar*_*ith 292
在SQL Server面向行的存储中,聚簇索引和非聚簇索引都组织为B树.
(图片来源)
聚簇索引和非聚簇索引之间的主要区别在于聚簇索引的叶级别是表.这有两个含义.
非聚簇索引也可以通过使用INCLUDE
子句(自SQL Server 2005)来明确包含所有非键列但它们是次要表示,并且总是存在另一个数据副本(表本身).
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)
Run Code Online (Sandbox Code Playgroud)
上面的两个指数几乎相同.上层索引页面包含键列的值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
Run Code Online (Sandbox Code Playgroud)
然后检查页面布局
SELECT page_id,
X,
geometry::Point(page_id, X, 0).STBuffer(1)
FROM T
CROSS APPLY sys.fn_PhysLocCracker( %% physloc %% )
ORDER BY page_id
Run Code Online (Sandbox Code Playgroud)
结果到处都是.按键顺序的第一行(值为1 - 用下面的箭头突出显示)几乎在最后一个物理页面上.
可以通过重建或重组索引来减少或删除碎片,以增加逻辑顺序和物理顺序之间的相关性.
跑完之后
ALTER INDEX ix ON T REBUILD;
Run Code Online (Sandbox Code Playgroud)
我得到了以下内容
如果表没有聚集索引,则称为堆.
可以在堆或聚簇索引上构建非聚簇索引.它们总是包含一个返回基表的行定位器.在堆的情况下,这是一个物理行标识符(rid),由三个组件组成(File:Page:Slot).对于聚簇索引,行定位器是逻辑的(聚簇索引键).
对于后一种情况,如果非聚集索引已经自然地将CI密钥列包括为NCI密钥列或INCLUDE
-d列,则不添加任何内容.否则,将丢失的CI密钥列静默地添加到NCI中.
SQL Server始终确保键列对于两种类型的索引都是唯一的.但是,对于未声明为唯一的索引强制执行此操作的机制在两种索引类型之间有所不同.
uniquifier
对于具有复制现有行的键值的任何行,都会添加聚簇索引.这只是一个递增的整数.
对于未声明为唯一SQL Server的非聚簇索引,将行定位器静默添加到非聚簇索引键中.这适用于所有行,而不仅仅是那些实际重复的行.
聚簇与非聚集命名法也用于列存储索引.本文增强了SQL Server列存储的状态
虽然列存储数据并未真正"聚集"在任何键上,但我们决定保留传统的SQL Server约定,将主索引称为聚簇索引.
kmo*_*ote 138
我意识到这是一个非常古老的问题,但我想我会提供一个类比来帮助说明上面的好答案.
如果你走进一个公共图书馆,你会发现这些书都是按照特定的顺序排列的(很可能是杜威十进制系统或DDS).这对应于书籍的"聚集索引".如果你想要的书的DDS#是005.7565 F736s
,那么你首先要找到标记的书架001-099
或类似的书架.(堆栈末尾的此endcap符号对应于索引中的"中间节点".)最后,您将向下钻取到标记的特定架子005.7450 - 005.7600
,然后您将扫描直到找到具有指定DDS#的书,并且那一点你找到了你的书.
但是如果你没有带着你记忆的书的DDS#进入图书馆,那么你需要第二个索引来帮助你.在古代,您会在图书馆前面找到一个很棒的抽屉柜,称为"卡片目录".其中有数千张3x5卡片 - 每本书一张,按字母顺序排列(可能是标题).这对应于"非聚集索引".这些卡片目录以分层结构组织,因此每个抽屉将标有其包含的卡片范围(Ka - Kl
例如,即"中间节点").再一次,你会钻进去,直到你找到你的书,但在这种情况下,一旦找到它(即"叶子节点"),你就没有这本书本身,而只是一张带有索引号的卡片(DDS#),您可以使用它在聚集索引中找到实际的书.
当然,没有什么可以阻止图书管理员复印所有卡片并在单独的卡片目录中以不同的顺序对它们进行分类.(通常至少有两个这样的目录:一个按作者名称排序,一个按标题排序.)原则上,您可以根据需要拥有尽可能多的"非群集"索引.
小智 68
下面列出了聚簇索引和非聚簇索引的一些特征:
create Index index_name(col1, col2, col.....)
.Dan*_*plo 48
一个非常简单的,非技术性的经验法则是聚簇索引通常用于您的主键(或者,至少是一个唯一列),非聚簇索引用于其他情况(可能是外键) .实际上,SQL Server默认会在主键列上创建聚簇索引.您将了解到,聚集索引与数据在磁盘上的物理排序方式有关,这意味着它对于大多数情况来说是一个很好的全面选择.
abd*_* kk 38
聚集指数
聚簇索引确定表中DATA的物理顺序.因此,表只有1个聚簇索引.
喜欢"字典"不需要任何其他索引,它已根据单词索引
非聚集索引
非聚集索引类似于Book中的索引.数据存储在一个位置.索引存储在另一个地方,索引具有指向数据存储位置的指针.因此,一个表有超过1个非聚簇索引.
比如"化学书",在凝视时有一个单独的索引指向章节位置,在"结束"有另一个索引指向常见的词位置
聚集索引基本上是一个树状组织的表。聚簇索引实际上不是将记录存储在未排序的堆表空间中,而是具有叶节点的 B+树索引,叶节点按簇键列值排序,存储实际的表记录,如下图所示。
聚集索引是 SQL Server 和 MySQL 中的默认表结构。尽管即使表没有主键,MySQL 也会添加隐藏的簇索引,但如果表具有主键列,SQL Server 始终会构建一个簇索引。否则,SQL Server 将存储为堆表。
聚集索引可以加速通过聚集索引键过滤记录的查询,就像通常的 CRUD 语句一样。由于记录位于叶节点中,因此在按主键值定位记录时无需额外查找额外的列值。
例如,在 SQL Server 上执行以下 SQL 查询时:
SELECT PostId, Title
FROM Post
WHERE PostId = ?
Run Code Online (Sandbox Code Playgroud)
可以看到 Execution Plan 使用了 Clustered Index Seek 操作来定位包含该Post
记录的 Leaf Node ,扫描 Clustered Index 节点只需要两次逻辑读取:
|StmtText |
|-------------------------------------------------------------------------------------|
|SELECT PostId, Title FROM Post WHERE PostId = @P0 |
| |--Clustered Index Seek(OBJECT:([high_performance_sql].[dbo].[Post].[PK_Post_Id]), |
| SEEK:([high_performance_sql].[dbo].[Post].[PostID]=[@P0]) ORDERED FORWARD) |
Table 'Post'. Scan count 0, logical reads 2, physical reads 0
Run Code Online (Sandbox Code Playgroud)
由于聚集索引通常是使用主键列值构建的,如果您想加快使用其他列的查询速度,则必须添加辅助非聚集索引。
二级索引将在其叶节点中存储主键值,如下图所示:
所以,如果我们Title
在Post
表的列上创建一个二级索引:
CREATE INDEX IDX_Post_Title on Post (Title)
Run Code Online (Sandbox Code Playgroud)
我们执行以下 SQL 查询:
SELECT PostId, Title
FROM Post
WHERE Title = ?
Run Code Online (Sandbox Code Playgroud)
我们可以看到,一个Index Seek操作用于定位IDX_Post_Title
索引中的Leaf Node ,可以提供我们感兴趣的SQL查询投影:
|StmtText |
|------------------------------------------------------------------------------|
|SELECT PostId, Title FROM Post WHERE Title = @P0 |
| |--Index Seek(OBJECT:([high_performance_sql].[dbo].[Post].[IDX_Post_Title]),|
| SEEK:([high_performance_sql].[dbo].[Post].[Title]=[@P0]) ORDERED FORWARD)|
Table 'Post'. Scan count 1, logical reads 2, physical reads 0
Run Code Online (Sandbox Code Playgroud)
由于关联的PostId
主键列值存储在IDX_Post_Title
叶节点中,因此该查询不需要额外的查找来定位Post
聚集索引中的行。
聚集索引
聚集索引根据键值对表或视图中的数据行进行排序和存储。这些是索引定义中包含的列。每个表只能有一个聚集索引,因为数据行本身只能按一种顺序排序。
表中的数据行按排序顺序存储的唯一时间是表包含聚集索引时。当表具有聚集索引时,该表称为聚集表。如果表没有聚集索引,则其数据行存储在称为堆的无序结构中。
非聚集
非聚集索引具有与数据行分离的结构。非聚集索引包含非聚集索引键值,每个键值条目都有一个指向包含键值的数据行的指针。从非聚集索引中的索引行到数据行的指针称为行定位符。行定位器的结构取决于数据页是存储在堆中还是聚簇表中。对于堆,行定位符是指向行的指针。对于聚簇表,行定位符是聚簇索引键。
您可以将非键列添加到非聚集索引的叶级别以绕过现有索引键限制,并执行完全覆盖的索引查询。有关更多信息,请参阅创建包含列的索引。有关索引键限制的详细信息,请参阅 SQL Server 的最大容量规范。
参考:https : //docs.microsoft.com/en-us/sql/relational-databases/indexes/clustered-and-nonclustered-indexes-描述
让我提供一个关于“聚类索引”的教科书定义,它摘自《Database Systems: The Complete Book》中的 15.6.1 :
我们还可以谈论聚类索引,它是一个或多个属性上的索引,使得该索引的搜索键具有固定值的所有元组出现在大约能容纳它们的尽可能少的块上。
为了理解这个定义,我们看一下课本上提供的例15.10:
R(a,b)
按属性排序a
并按该顺序存储、打包到块中的关系肯定是聚类的。on 的索引a
是聚类索引,因为对于给定的a
值 a1,具有该值的所有元组a
都是连续的。因此,它们看起来被打包成块,除了可能包含a
值 a1 的第一个和最后一个块,如图 15.14 所示。然而,b 上的索引不太可能聚集,因为具有固定值的元组将分布在整个文件中,除非和 的b
值非常密切相关。a
b
请注意,该定义并不强制数据块在磁盘上必须是连续的;它只是说带有搜索键的元组被打包到尽可能少的数据块中。
一个相关的概念是聚类关系。如果一个关系的元组被打包成尽可能少的块来容纳这些元组,则该关系是“聚集的”。换句话说,从磁盘块的角度来看,如果它包含来自不同关系的元组,那么这些关系就不能聚集(即,有一种更紧凑的方式来存储这种关系,通过将其他磁盘块中的该关系的元组与元组不属于当前磁盘块中的关系)。显然,R(a,b)
在上面的例子中是聚类的。
为了将两个概念连接在一起,聚集关系可以具有聚集索引和非聚集索引。然而,对于非聚集关系,聚集索引是不可能的,除非索引建立在关系的主键之上。
“集群”这个词被广泛传播到数据库存储端的所有抽象级别(三个抽象级别:元组、块、文件)。一个称为“集群文件”的概念,它描述了一个文件(一组块(一个或多个磁盘块)的抽象)是否包含来自一个关系或不同关系的元组。它与聚簇索引概念无关,因为它是在文件级别。
然而,有些教材喜欢根据聚簇文件定义来定义聚簇索引。这两种类型的定义在聚集关系层面上是相同的,无论它们是以数据磁盘块还是文件来定义聚集关系。从本段的链接中,
在以下情况下,文件上属性 A 的索引是聚类索引: 所有具有属性值 A = a 的元组都按顺序(= 连续)存储在数据文件中
连续存储元组与“元组被打包到尽可能少的块中以容纳这些元组”相同(一个谈论文件,另一个谈论磁盘有细微差别)。这是因为连续存储元组是实现“打包成尽可能少的块来容纳这些元组”的方法。