索引键列VS索引包含列

时间:2010-08-27 04:17:01

标签: sql-server-2005 tsql

有人可以解释这两个 - 索引关键列VS索引列吗?

目前,我的索引有4个索引键列和0个包含列。

由于

5 个答案:

答案 0 :(得分:107)

索引键列是索引的b树的一部分。包含的列不是。

取两个指数:

CREATE INDEX index1 ON table1 (col1, col2, col3)
CREATE INDEX index2 ON table1 (col1) INCLUDE (col2, col3)

index1更适合此类查询:

SELECT * FROM table1 WHERE col1 = x AND col2 = y AND col3 = z

index2更适合此类查询:

SELECT col2, col3 FROM table1 WHERE col1 = x

在第一个查询中,index1提供了一种快速识别感兴趣的行的机制。查询将(可能)作为索引查找执行,然后执行书签查找以检索完整行。

在第二个查询中,index2充当覆盖索引。 SQL Server根本不需要访问基表,因为索引提供了满足查询所需的所有数据。在这种情况下,index1也可以作为覆盖索引。

如果你想要一个覆盖索引,但是不想将所有列添加到b-tree中,因为你没有寻找它们,或者因为它们不是允许的数据类型(例如,XML)而不能,使用INCLUDE子句。

答案 1 :(得分:12)

让我们来看看这本书。书中的每个页面都有页码。本书中的所有信息都是根据此页码顺序显示的。在数据库术语中,页码是聚簇索引。现在想想本书末尾的术语表。这是按字母顺序排列的,允许您快速查找特定词汇表术语所属的页码。这表示非聚集索引,其中词汇表术语为关键列。

现在假设每个页面也在顶部显示“章节”标题。如果要查找词汇表术语的哪一章,则必须查找哪个页面#描述术语表术语,然后打开相应页面并查看页面上的章节标题。这清楚地表示密钥查找 - 当您需要从非索引列中查找数据时,您必须找到实际数据记录(聚簇索引)并查看此列值。包含的列有助于提高性能 - 考虑每个章节标题所包含的词汇表以及词汇表术语。 如果您需要找出词汇表术语所属的章节 - 您不需要打开实际页面 - 您可以在查找词汇表术语时获取它。

所以包含的列就像那些章节标题。非聚集索引(词汇表)具有添加属性作为非聚集索引的一部分。索引不按包含的列排序 - 它只是有助于加快查找的其他属性(例如,您不需要打开实际页面,因为信息已经在词汇表索引中)

示例:

创建表脚本

CREATE TABLE [dbo].[Profile](
    [EnrollMentId] [int] IDENTITY(1,1) NOT NULL,
    [FName] [varchar](50) NULL,
    [MName] [varchar](50) NULL,
    [LName] [varchar](50) NULL,
    [NickName] [varchar](50) NULL,
    [DOB] [date] NULL,
    [Qualification] [varchar](50) NULL,
    [Profession] [varchar](50) NULL,
    [MaritalStatus] [int] NULL,
    [CurrentCity] [varchar](50) NULL,
    [NativePlace] [varchar](50) NULL,
    [District] [varchar](50) NULL,
    [State] [varchar](50) NULL,
    [Country] [varchar](50) NULL,
    [UIDNO] [int] NOT NULL,
    [Detail1] [varchar](max) NULL,
    [Detail2] [varchar](max) NULL,
    [Detail3] [varchar](max) NULL,
    [Detail4] [varchar](max) NULL,
PRIMARY KEY CLUSTERED 
(
    [EnrollMentId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

存储过程脚本

CREATE Proc [dbo].[InsertIntoProfileTable]
As
BEGIN
SET NOCOUNT ON
Declare @currentRow int
Declare @Details varchar(Max)
Declare @dob Date
set @currentRow =1;
set @Details ='Let''s think about the book. Every page in the book has the page number. All information in this book is presented sequentially based on this page number. Speaking in the database terms, page number is the clustered index. Now think about the glossary at the end of the book. This is in alphabetical order and allow you to quickly find the page number specific glossary term belongs to. This represents non-clustered index with glossary term as the key column.        Now assuming that every page also shows "chapter" title at the top. If you want to find in what chapter is the glossary term, you have to lookup what page # describes glossary term, next - open corresponding page and see the chapter title on the page. This clearly represents key lookup - when you need to find the data from non-indexed column, you have to find actual data record (clustered index) and look at this column value. Included column helps in terms of performance - think about glossary where each chapter title includes in addition to glossary term. If you need to find out what chapter the glossary term belongs - you don''t need to open actual page - you can get it when you lookup the glossary term.      So included column are like those chapter titles. Non clustered Index (glossary) has addition attribute as part of the non-clustered index. Index is not sorted by included columns - it just additional attributes that helps to speed up the lookup (e.g. you don''t need to open actual page because information is already in the glossary index).'
while(@currentRow <=200000)
BEGIN
insert into dbo.Profile values( 'FName'+ Cast(@currentRow as varchar), 'MName' + Cast(@currentRow as varchar), 'MName' + Cast(@currentRow as varchar), 'NickName' + Cast(@currentRow as varchar), DATEADD(DAY, ROUND(10000*RAND(),0),'01-01-1980'),NULL, NULL, @currentRow%3, NULL,NULL,NULL,NULL,NULL, 1000+@currentRow,@Details,@Details,@Details,@Details)
set @currentRow +=1;
END

SET NOCOUNT OFF
END

GO

使用上述SP,您可以一次插入200000条记录

您可以看到“EnrollMentId”列上有聚集索引。

现在在“UIDNO”列上创建非聚集索引。

脚本

CREATE NONCLUSTERED INDEX [NonClusteredIndex-20140216-223309] ON [dbo].[Profile]
(
    [UIDNO] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

现在运行以下查询

select UIDNO,FName,DOB, MaritalStatus, Detail1 from dbo.Profile 
--Takes about 30-50 seconds and return 200,000 results.

查询2

select UIDNO,FName,DOB, MaritalStatus, Detail1 from dbo.Profile
where DOB between '01-01-1980' and '01-01-1985'
 --Takes about 10-15 seconds and return 36,479 records.

现在删除上面的非聚集索引并使用以下脚本重新创建

CREATE NONCLUSTERED INDEX [NonClusteredIndex-20140216-231011] ON [dbo].[Profile]
(
    [UIDNO] ASC,
    [FName] ASC,
    [DOB] ASC,
    [MaritalStatus] ASC,
    [Detail1] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

会抛出以下错误

Ms 1919,Level 16,State 1,Line 1 表'dbo.Profile'中的列'Detail1'的类型无效,无法用作索引中的键列。

因为我们不能将varchar(Max)数据类型用作键列。

现在使用以下脚本

创建包含列的非聚集索引
CREATE NONCLUSTERED INDEX [NonClusteredIndex-20140216-231811] ON [dbo].[Profile]
(
    [UIDNO] ASC
)
INCLUDE (   [FName],
    [DOB],
    [MaritalStatus],
    [Detail1]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

现在运行以下查询

select UIDNO,FName,DOB, MaritalStatus, Detail1 from dbo.Profile --Takes about 20-30 seconds and return 200,000 results.

查询2

select UIDNO,FName,DOB, MaritalStatus, Detail1 from dbo.Profile
where DOB between '01-01-1980' and '01-01-1985'
 --Takes about 3-5 seconds and return 36,479 records.

答案 2 :(得分:6)

包含的列不构成索引键的一部分,但它们确实存在于索引中。本质上,这些值将被复制,因此存在开销,但您的索引更有可能覆盖(即由查询优化器选择)更多查询。此重复还可以提高查询时的性能,因为数据库引擎可以返回值而无需查看表本身。

只有非聚簇索引才能包含列,因为在聚簇索引中,每列都被有效地包含在内。

答案 3 :(得分:0)

我想在其他答案中添加有关索引键列和包含列的更多详细信息,以及包含列使用的好处。对于此答案,我从Markus Winand发表于2019-04-30的帖子“仔细阅读索引包含子句”中获取了以下信息:https://use-the-index-luke.com/blog/2019-04/include-columns-in-btree-indexes

索引键列与包含列之间的区别的简要概述

要了解include子句,您必须首先了解使用 索引最多影响三层数据结构:

  • B树
  • B树的叶子节点级别的双向链表
  • 桌子

前两个结构共同构成一个索引,因此它们可以是 合并为一个项目,即“ B树索引”。 一般情况下,数据库软件会开始遍历B树以找到 叶节点级别的第一个匹配条目(1)。然后遵循 双向链表,直到找到所有匹配的条目(2)和 最后,它从表(3)中获取每个匹配的条目。

A database software traversing the B-tree

在加载几行时,B树对 整体努力。只要您需要获取少量 表中的所有行,此步骤将带头。无论哪种情况,很少或 多行—双链表通常是次要因素,因为它 将具有相似值的行彼此相邻存储,以便单个 读取操作可以获取100甚至更多行。关于优化>的最一般的想法是减少工作量以实现相同的目标。当涉及索引访问时,这意味着如果数据库软件不需要数据结构,则它会省略对数据结构的访问。仅索引扫描正是这样做的:如果出现以下情况,它将忽略表访问 索引的双向链接列表中提供了所需的数据。

常见的误解是索引仅对where子句有所帮助。 B树索引还可以帮助排序依据,分组依据,选择和其他子句。仅仅是索引的B树部分(而不是双向链表),其他子句就无法使用。

include子句允许我们区分列之间的区别 希望在整个索引(关键列)和我们拥有的列中拥有 只需要在叶节点(包括列)中。这意味着它允许我们 如果我们不需要非叶节点,请从非叶节点中删除它们。

包含的列如何影响查询执行的多个方面及其使用的好处

叶节点条目的顺序不包含列 考虑在内。索引仅按其键列排序。这有 两个结果:包含的列不能用于防止排序 也不认为它们具有唯一性。

“覆盖指数”一词有时用于 仅索引扫描或包含子句。重要的是给定的 索引可以通过仅索引扫描来支持给定查询。 该索引是否具有include子句或包含所有表 列无关。

带有include子句的新定义具有一些优点:

树的级别可能更少(<〜40%)

作为双向链接上方的树节点 列表不包含包含列,数据库 可以在每个块中存储更多分支,以便树可能具有 级别更少。

索引稍小(<〜3%)

由于树的非叶节点不包含 包含列,该索引的整体大小为 少一点。但是,索引的叶节点级别需要 无论如何要占用最大空间,以便在其余节点中潜在地节省成本 很小。

它记录了其目的

这绝对是include子句最被低估的好处:原因 列在索引中的原因是索引定义本身中的文档。扩展现有索引时,准确了解为什么要以定义索引的方式正确定义索引是非常重要的。您可以自由更改索引而不会破坏其他任何索引 查询是这种知识的直接结果。

   CREATE INDEX idx
    ON sales ( subsidiary_id )
     INCLUDE ( eur_value )

由于eur_value列在include子句中,因此不在 非叶节点,因此对于树的导航和 订购。在关键部分的末尾添加新列是相对的 安全。

   CREATE INDEX idx
    ON sales ( subsidiary_id, ts )
     INCLUDE ( eur_value )

即使对其他人造成负面影响的风险仍然很小 查询,通常值得冒险。

对包含的列进行过滤

直到现在,我们一直专注于include子句如何启用仅索引扫描。让我们 还要看看另一种有益的情况 索引中的列。

   SELECT * FROM sales
    WHERE subsidiary_id = ?
     AND notes LIKE '%search term%'

我已将搜索字词设为文字值,以显示前导和 尾随通配符-当然您会在自己的参数中使用bind参数 码。现在,让我们考虑一下此查询的正确索引。显然, subject_id必须位于第一位置。如果我们采取 上一个索引,它已经满足了这一要求:

   CREATE INDEX idx
    ON sales ( subsidiary_id, ts )
     INCLUDE ( eur_value )

数据库软件可以通过三步过程使用该索引 如开头所述:(1)它会使用B树来找到 给定子公司的第一个索引条目; (2)它会遵循 双链表以查找该子公司的所有销售; (3)它将 从表格中获取所有相关的销售,删除那些 注释列上的类似图案不匹配并返回 剩余的行。

问题是此过程的最后一步:表访问加载 行,而不知道是否将其纳入最终结果。相当 通常,表访问是总工作量的最大贡献者 运行查询。加载甚至没有选择的数据都是巨大的 表演不行。

此特定查询所面临的挑战是它使用固定的 像图案。普通的B树索引不支持搜索 模式。但是,B树索引仍支持对 模式。请注意重点:搜索与过滤。

换句话说,如果注释列出现在双向链接中 列表,数据库软件可以在之前应用类似模式 从表中获取该行(不是PostgreSQL,请参见下文)。这个 如果相似模式不匹配,则会阻止对表的访问。如果 表有更多列,仍然有一个表访问权限来获取那些 满足where子句的行的列数-由于select *。

   CREATE INDEX idx
    ON sales ( subsidiary_id, ts )
     INCLUDE ( eur_value, notes )

如果表中有更多列,则索引不会启用 仅索引扫描。但是,它可以使性能接近 如果只匹配行的部分,则执行类似索引的扫描 模式很低。在相反的情况下-如果所有行都匹配 模式-由于数量的增加,性能会稍差 索引大小。但是,盈亏平衡点很容易达到:总体而言 性能改善,通常需要足够的过滤器 删除一小部分行。您的里程会有所不同 取决于相关列的大小。

带有include子句的唯一索引

include子句的一个完全不同的方面:具有include子句的唯一索引仅考虑键列的唯一性。那 允许我们创建唯一索引,该索引在 叶节点,例如进行仅索引扫描。

   CREATE UNIQUE INDEX …
    ON … ( id )  
     INCLUDE ( payload )

此索引可防止id列中出现重复值,但它支持对下一个查询的仅索引扫描。

    SELECT payload   
      FROM …  
       WHERE id = ?  

请注意,并非严格要求include子句 行为:在唯一约束和唯一索引之间进行适当区分的数据库只需要将唯一键列作为最左侧列的索引即可。 列很好。

答案 4 :(得分:-1)

包含的列不构成索引键的一部分,但它们确实存在于索引中。基本上这些价值观将会重复 下面使用示例列

获取两种类型的索引
CREATE clustered INDEX NC_index1 ON tableName (column1, column1, column1,column4)
CREATE clustered INDEX NC_index2 ON tableName (column1) INCLUDE (column2, column3,column4)

NC_index1更适合此类查询:

SELECT * FROM tableName WHERE column1 = x AND column1 = y AND column1 = z and column4=n

而NC_index2更适合这种查询:

SELECT column1, column2 FROM tableName WHERE column1 = a

因为sql server不能在数据类型上创建索引(例如,XML,文本等)