群集和非群集索引实际上意味着什么?

P.K*_*P.K 1041 sql-server indexing performance clustered-index non-clustered-index

我对DB的了解有限,并且只使用DB作为应用程序员.我想知道ClusteredNon clustered indexes.我用谷歌搜索,发现的是:

聚簇索引是一种特殊类型的索引,它重新排序表中记录的物理存储方式.因此,表只能有一个聚簇索引.聚簇索引的叶节点包含数据页.非聚簇索引是一种特殊类型的索引,其中索引的逻辑顺序与磁盘上行的物理存储顺序不匹配.非聚簇索引的叶节点不包含数据页.相反,叶节点包含索引行.

我在SO中发现的是聚簇索引和非聚簇索引之间有什么区别?.

有人可以用简单的英语解释这个吗?

Shi*_*iji 1043

使用聚簇索引,行以与索引相同的顺序物理存储在磁盘上.因此,只能有一个聚簇索引.

对于非聚集索引,还有第二个列表,其中包含指向物理行的指针.虽然每个新索引都会增加写入新记录所需的时间,但您可以拥有许多非聚簇索引.

如果要返回所有列,通常从聚簇索引读取更快.您不必先进入索引,然后再进入表.

如果需要重新排列数据,则写入具有聚簇索引的表可能会更慢.

  • 物理上与存储在磁盘上的实际位相同 (130认同)
  • 你应该通过"身体"来澄清你的意思. (39认同)
  • @Pete并非如此.SQL Server当然不能保证所有数据文件都布置在光盘的连续物理区域中,并且文件系统碎片没有.在数据文件中按顺序存在聚簇索引甚至是不正确的.不是这种情况的程度是逻辑碎片的程度. (39认同)
  • 只需快速评论备份Martin Smith的观点 - 聚簇索引不能保证磁盘上的顺序存储.准确管理数据放置在磁盘上的位置是操作系统的工作,而不是DBMS.但它表明项目通常根据聚类键进行排序.这意味着,如果数据库增长10GB,操作系统可能决定将5GB的10GB块放在磁盘的不同部分.覆盖10GB的聚簇表将按顺序存储在每个2GB块上,但这些2GB块可能不是顺序存储的. (36认同)
  • 请参阅 [msdn](http://technet.microsoft.com/en-us/library/ms186342.aspx)“当您创建 PRIMARY KEY 约束时,将自动创建一个或多个列上的唯一聚集索引**如果** 表上的聚集索引尚不存在”,这意味着不必必须是同一列。 (16认同)
  • 任何读过这个答案的人都应该向下滚动到@Martin Smith的[答案](http://stackoverflow.com/a/24470091/3002584)并看看为什么声称`使用聚簇索引,行存储在磁盘上与索引`相同的顺序是完全错误的. (6认同)
  • 我知道这很晚了,但是这段视频给出了很好的图形解释。https://www.youtube.com/watch?v=ITcOiLSfVJQ (3认同)
  • 嗯,如果你想要取回所有的列,为什么_it从聚簇索引读取通常会更快?也许你的意思_all行_? (3认同)
  • 支持使用技术上不准确但易于理解的简化来用“简单的英语”解释该概念。当有人要求简化解释时,挑剔技术细节会适得其反。 (2认同)

小智 583

聚簇索引意味着您要告诉数据库在磁盘上存储实际上彼此接近的近似值.这具有快速扫描/检索落入某些聚集索引值范围的记录的益处.

例如,您有两个表,Customer和Order:

Customer
----------
ID
Name
Address

Order
----------
ID
CustomerID
Price
Run Code Online (Sandbox Code Playgroud)

如果您希望快速检索某个特定客户的所有订单,您可能希望在Order表的"CustomerID"列上创建聚簇索引.这样,具有相同CustomerID的记录将在磁盘(群集)上彼此靠近地物理存储,这加速了它们的检索.

PS CustomerID上的索引显然不是唯一的,因此您需要添加第二个字段来"unquify"索引或让数据库为您处理,但这是另一个故事.

关于多个索引.每个表只能有一个聚簇索引,因为它定义了数据的物理排列方式.如果你想要一个类比,想象一个有很多桌子的大房间.您可以将这些表格放在一起形成多行,也可以将它们全部拉到一起形成一个大型会议桌,但不能同时形成两种方式.一个表可以有其他索引,然后它们将指向聚簇索引中的条目,而这些条目最终将说明在哪里找到实际数据.

  • @Caltor一点也不!实际上,文档和名称本身都是误导性的.拥有"聚集索引"实际上与索引关系不大.从概念上讲,你真正拥有的是"一个聚集在索引_x_上的表". (8认同)
  • 据说CI应该总是用于PK (4认同)
  • 因此,对于聚簇索引,索引或表中的记录是否紧密存储在一起? (4认同)
  • @Caltor**表.**索引按定义排序.例如,btree将被排序,以便人们可以简单地进行地址算术来搜索.集群的想法是将表格提供给特定索引的性能.为了清楚起见,表的记录将被重新排序以匹配索引最初在**中的顺序**. (4认同)
  • @JohnOrtizOrdoñez:当然,你几乎可以使用任何存储在行中的东西,所以没有`XML`,`VARCHAR(MAX)`或`VARBINARY(MAX)`.请注意,通常在日期字段*first*上进行聚类是有意义的,因为聚簇索引对于范围扫描最有效,这在日期类型中最常见.因人而异. (3认同)
  • 这句话在一秒钟就把我的概念卖了!谢谢*“”如果您希望快速检索一个特定客户的所有订单,则可能希望在Order表的“ CustomerID”列上创建一个聚集索引……物理上彼此靠近存储在磁盘上(聚集),以加快检索速度。“ * (2认同)

Mar*_*ith 292

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

在此输入图像描述

(图片来源)

聚簇索引和非聚簇索引之间的主要区别在于聚簇索引的叶级别表.这有两个含义.

  1. 聚集索引的叶页上的行总是包含一些表中的每个(非稀疏)的列(或价值,或指向的实际值).
  2. 聚簇索引是表的主副本.

非聚簇索引也可以通过使用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约定,将主索引称为聚簇索引.

  • @brainstorm:令人惊讶的是,一些错误的陈述如何重复作为福音.群集表明,至少从顺序读取的角度来看,*以"索引*"的相同顺序将行物理存储在磁盘上是"理想的",但这与说它会导致它们相差甚远实际上以这种方式存储. (11认同)
  • @brainstorm是的我知道这一点.可能是因为[这个MSDN页面](http://msdn.microsoft.com/en-us/library/ms190457.aspx)上的措辞,但是看到那些措辞有些误导​​你只需要看看[碎片主题](http://technet.microsoft.com/en-us/library/cc966523.aspx#EHAA) (8认同)
  • @MartinSmith现在,先生,这是一个答案.我很乐意在回复列表的顶部看到它,但正如SO所说,"快速和简单"得到了提升. (6认同)
  • @MartinSmith我已经复制并确认了您对"SQL Server 2014"的测试结果.在初始插入后,我得到了"95%"的索引碎片.在`index rebuild`之后,碎片是'0%`并且值被排序.我想知道,我们可以说:"表中的数据行以排序顺序存储的唯一时间是它的聚簇索引碎片是0"吗? (4认同)
  • @Terkhos是的,水平数字是页面,垂直数字是索引键.第二个图表具有完全不同的页码,显示重建在文件的后面使用了一个新区域.完全未分段的索引将按键顺序使用连续页面.第二张图非常接近. (4认同)
  • @Manachi在回答原始问题5年后给出了这个答案.它的目的是纠正这些答案的一些误导性方面.OP的(现在8岁)突发奇想并不是我的担忧.其他读者可能会欣赏较低级别的观点. (4认同)
  • 噢,天哪,`sys.fn_PhysLocCracker`函数非常适合可视化到底发生了什么! (2认同)
  • 很好的解释很有道理。谢谢兄弟提供的这些信息。该标记应被标记为正确答案,因为被标记的标记包含错误的陈述。 (2认同)

kmo*_*ote 138

我意识到这是一个非常古老的问题,但我想我会提供一个类比来帮助说明上面的好答案.

集群指数

如果你走进一个公共图书馆,你会发现这些书都是按照特定的顺序排列的(很可能是杜威十进制系统或DDS).这对应于书籍的"聚集索引".如果你想要的书的DDS#是005.7565 F736s,那么你首先要找到标记的书架001-099或类似的书架.(堆栈末尾的此endcap符号对应于索引中的"中间节点".)最后,您将向下钻取到标记的特定架子005.7450 - 005.7600,然后您将扫描直到找到具有指定DDS#的书,并且那一点你找到了你的书.

非集群指数

但是如果你没有带着你记忆的书的DDS#进入图书馆,那么你需要第二个索引来帮助你.在古代,您会在图书馆前面找到一个很棒的抽屉柜,称为"卡片目录".其中有数千张3x5卡片 - 每本书一张,按字母顺序排列(可能是标题).这对应于"非聚集索引".这些卡片目录以分层结构组织,因此每个抽屉将标有其包含的卡片范围(Ka - Kl例如,即"中间节点").再一次,你会钻进去,直到你找到你的书,但在这种情况下,一旦找到它(即"叶子节点"),你就没有这本书本身,而只是一张带有索引号的卡片(DDS#),您可以使用它在聚集索引中找到实际的书.

当然,没有什么可以阻止图书管理员复印所有卡片并在单独的卡片目录中以不同的顺序对它们进行分类.(通常至少有两个这样的目录:一个按作者名称排序,一个按标题排序.)原则上,您可以根据需要拥有尽可能多的"非群集"索引.

  • 我也许可以扩展这种类比来描述“包含”列**,它可以与非聚簇索引一起使用:可以想象卡片目录中的卡片不仅包括一本书,还包括*本书所有已发布版本的列表*,按出版日期进行数字组织。就像在“包含的列”中一样,此信息仅存储在叶级别(因此减少了馆员必须创建的卡片数量)。 (2认同)
  • 很好的类比——确实有助于形象化! (2认同)

小智 68

下面列出了聚簇索引和非聚簇索引的一些特征:

聚集索引

  1. 聚簇索引是唯一标识SQL表中的行的索引.
  2. 每个表只能有一个聚簇索引.
  3. 您可以创建一个涵盖多个列的聚簇索引.例如:create Index index_name(col1, col2, col.....).
  4. 默认情况下,具有主键的列已具有聚簇索引.

非聚集索引

  1. 非聚集索引就像简单索引.它们仅用于快速检索数据.不确定有独特的数据.

  • 对点1稍作修正.聚簇索引*不一定唯一地标识SQL表中的行.这是PRIMARY KEY的功能 (33认同)
  • @Nigel,主键还是唯一索引? (4认同)

Dan*_*plo 48

一个非常简单的,非技术性的经验法则是聚簇索引通常用于您的主键(或者,至少是一个唯一列),非聚簇索引用于其他情况(可能是外键) .实际上,SQL Server默认会在主键列上创建聚簇索引.您将了解到,聚集索引与数据在磁盘上的物理排序方式有关,这意味着它对于大多数情况来说是一个很好的全面选择.


abd*_* kk 38

聚集指数

聚簇索引确定表中DATA的物理顺序.因此,表只有1个聚簇索引.

喜欢"字典"不需要任何其他索引,它已根据单词索引

非聚集索引

非聚集索引类似于Book中的索引.数据存储在一个位置.索引存储在另一个地方,索引具有指向数据存储位置的指针.因此,一个表有超过1个非聚簇索引.

比如"化学书",在凝视时有一个单独的索引指向章节位置,在"结束"有另一个索引指向常见的词位置


Vla*_*cea 8

聚集索引

聚集索引基本上是一个树状组织的表。聚簇索引实际上不是将记录存储在未排序的堆表空间中,而是具有叶节点的 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)

非聚集索引

由于聚集索引通常是使用主键列值构建的,如果您想加快使用其他列的查询速度,则必须添加辅助非聚集索引。

二级索引将在其叶节点中存储主键值,如下图所示:

非聚集索引

所以,如果我们TitlePost表的列上创建一个二级索引:

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聚集索引中的行。

  • 你的回复非常适合[这个模因](https://pbs.twimg.com/media/DvXFSZJUcAAVnua.jpg) (2认同)

mar*_*ker 6

聚集索引

聚集索引根据键值对表或视图中的数据行进行排序和存储。这些是索引定义中包含的列。每个表只能有一个聚集索引,因为数据行本身只能按一种顺序排序。

表中的数据行按排序顺序存储的唯一时间是表包含聚集索引时。当表具有聚集索引时,该表称为聚集表。如果表没有聚集索引,则其数据行存储在称为堆的无序结构中。

非聚集

非聚集索引具有与数据行分离的结构。非聚集索引包含非聚集索引键值,每个键值条目都有一个指向包含键值的数据行的指针。从非聚集索引中的索引行到数据行的指针称为行定位符。行定位器的结构取决于数据页是存储在堆中还是聚簇表中。对于堆,行定位符是指向行的指针。对于聚簇表,行定位符是聚簇索引键。

您可以将非键列添加到非聚集索引的叶级别以绕过现有索引键限制,并执行完全覆盖的索引查询。有关更多信息,请参阅创建包含列的索引。有关索引键限制的详细信息,请参阅 SQL Server 的最大容量规范。

参考:https : //docs.microsoft.com/en-us/sql/relational-databases/indexes/clustered-and-nonclustered-indexes-描述


xxk*_*kkk 5

让我提供一个关于“聚类索引”的教科书定义,它摘自《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值非常密切相关。ab

图15.14

请注意,该定义并不强制数据块在磁盘上必须是连续的;它只是说带有搜索键的元组被打包到尽可能少的数据块中。

一个相关的概念是聚类关系。如果一个关系的元组被打包成尽可能少的块来容纳这些元组,则该关系是“聚集的”。换句话说,从磁盘块的角度来看,如果它包含来自不同关系的元组,那么这些关系就不能聚集(即,有一种更紧凑的方式来存储这种关系,通过将其他磁盘块中的该关系的元组与元组不属于当前磁盘块中的关系)。显然,R(a,b)在上面的例子中是聚类的。

为了将两个概念连接在一起,聚集关系可以具有聚集索引和非聚集索引。然而,对于非聚集关系,聚集索引是不可能的,除非索引建立在关系的主键之上。

“集群”这个词被广泛传播到数据库存储端的所有抽象级别(三个抽象级别:元组、块、文件)。一个称为“集群文件”的概念,它描述了一个文件(一组块(一个或多个磁盘块)的抽象)是否包含来自一个关系或不同关系的元组。它与聚簇索引概念无关,因为它是在文件级别。

然而,有些教材喜欢根据聚簇文件定义来定义聚簇索引。这两种类型的定义在聚集关系层面上是相同的,无论它们是以数据磁盘块还是文件来定义聚集关系。从本段的链接中,

在以下情况下,文件上属性 A 的索引是聚类索引: 所有具有属性值 A = a 的元组都按顺序(= 连续)存储在数据文件中

连续存储元组与“元组被打包到尽可能少的块中以容纳这些元组”相同(一个谈论文件,另一个谈论磁盘有细微差别)。这是因为连续存储元组是实现“打包成尽可能少的块来容纳这些元组”的方法。