SQL Server中的索引

SQL SERVER提供了多种索引。如果以存储结构结构来区分,有聚集索引和非聚集索引;如果以数据的唯一性来区分,则有唯一索引和非唯一索引;若以键列个数来区分,有单列索引与多列索引等。

聚集索引

聚集索引表示表中存储的数据按照索引的顺序存储,确定表中数据的物理顺序,即索引的底层(或称叶级别)包含该表的实际数据行。聚集索引类似于电话簿,联系人按姓氏排列数据。由于聚集索引规定数据在表中的物理存储顺序,因此一个表只能包含一个聚集索引。但该索引可以包含多个列(组合索引),就像电话簿按姓氏和名字进行组织一样。
  聚集索引的索引文件本身就是数据文件,即B+Tree的数据域存储的就是实际的数据。这棵树的叶节点data域保存了完整的数据记录,而这个索引的key是数据表的主键(15、18、20、30、49、50…),如下图所示:

《SQL Server中的索引》

  第二索引存储的也是
相应记录聚集索引的键值而不是地址,所以当以第二索引查找时,会先根据第二索引找到主键,再根据主键索引找到实际的数据。所以所有第二索引都引用聚集索引键值作为data域:

《SQL Server中的索引》

聚集索引的缺点

  • 聚集索引的缺点是对表进行修改速度较慢,这是为了保持表中的记录的物理顺序与索引的顺序一致慢(时间花费在“物理存储的排序”上,也就是首先要找到位置然后插入),降低了执行速度。
  • 第二索引访问需要两次索引查找,而不是一次,因为叶子节点不会保存引用的行的物理位置,而是保持了行的主键值
  • 聚集表可会比全表扫描慢,尤其在表存储得比较稀疏或因为分页而没有顺序存储的时候。

聚集索引的优点

  • 可以把相关数据保存在一起。
  • 数据访问快。聚集索引把索引和数据都保存到同一棵B-Tree中,因此从聚集索引中取得数据通常在非聚集索引进行查找要快。

使用聚集索引的误区
  一般我们指定一个表的主键,如果这个表之前没有聚集索引,同时建立主键时候没有强制指定使用非聚集索引,SQL会默认在此字段上创建一个聚集索引,而主键都是唯一的。
  虽然聚集索引的约束是唯一性,但是要求字段不一定是要唯一,聚集索引可以创建在任何一列你想创建的字段上,这是从理论上讲,实际情况并不能随便指定,否则在性能上会是恶梦。同时主键不等于就是聚集索引
  使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为 ID号是自动生成的,我们并不知道每条记录的ID号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。

非聚集索引

非聚集索引中叶节点的信息也是按聚集索引的键值按顺序存储(如下图的B-Tree中的叶子结点,也是和上图一样按照15、18、20…这样存储的),但是叶节点的信息存储的并不是实际的数据,是相应数据对象的存放地址指针。而实际数据存储在一个地方,按另一种顺序存储,也就是索引和数据是分开存放的。所以非聚集索引的叶节仍然是索引节点,只不过是用索引指向了实际的数据,如下图所示:

《SQL Server中的索引》

  可以为在表中查找数据时常用的每个列创建一个非聚集索引。例如,一本介绍园艺的书可能会包含一个植物通俗名称索引,和一个植物学名索引,因为这是读者查找信息的两种最常用的方法。

  SQL Server默认情况下建立的索引是非聚集索引,一个表可以拥有多个非聚集索引,每个非聚集索引提供访问数据的不同排序顺序。

  SQL Server 通过使用存储在非聚集索引的
行内的
聚集索引键搜索聚集索引来检索数据行,所以非聚集索引可能会比预想的大, 因为非聚集索引将聚集索引键作为其行指针存储,因此使聚集索引键尽可能小很重要。

聚集索引和非聚集索引的对比
  聚集索引和非聚集索引的根本区别是表记录的排列顺序和与索引的排列顺序是否一致,聚集索引表记录的排列顺序与索引的排列顺序一致(因为索引和数据放在一起),优点是查询速度快,因为一旦具有第一个索引值的纪录被找到,具有连续索引值的记录也一定物理的紧跟其后。非聚集索引指定了表中记录的逻辑顺序,但记录的物理顺序和索引的顺序不一致。
  对聚簇索引的主要限制是每个表只能建立一个聚簇索引。但是,一个表可以有不止一个非聚簇索引。实际上,对每个表你最多可以建立249个非聚簇索引。你也可以对一个表同时建立聚簇索引和非聚簇索引。

包含非键列的索引

可以将非键列(称为包含列。键列就是在索引中所包含的列,当然非键列就是该索引之外的列了)添加到非聚集索引的叶级别,从而通过涵盖查询来提高查询性能。 也就是说,查询中引用的所有列都作为键列或非键列包含在索引中,它通常称为“覆盖查询”。 非键列具有下列优点:

  • 它们可以是不允许作为索引键列的数据类型。
  • 当查询中的所有列都作为键列或非键列包含在索引中时,带有包含性非键列的索引可以显著提高查询性能。因为在计算索引键列数或索引键大小时, 数据库引擎不考虑非键列的大小。这样可以实现性能提升,因为查询优化器可以在索引中找到所有列值,而无需访问表或聚集索引数据,从而减少磁盘 I/O 操作。这样比起复合索引是有性能上的提升的,因为索引的大小变小了

设计建议

  • 把常用的where后面的条件查询的字段作为索引的键列,而需要返回的字段就作为索引包含的非键列
  • 如果where的是两个或两个以上的谓词的话,这个索引就可以创建为复合索引了。以前天真的认为要返回的字段只能通过在复合索引中入这些字段,不管它是否会用来做谓词。
  • 将非键列包含在非聚集索引中,以避免超过当前索引大小的限制(最大键列数为 16,最大索引键大小为 900 字节)。 数据库引擎计算索引键列数或索引键大小时,不考虑非键列。虽然可以设置include,但是也尽量不要使用太多的字段作为索引包含的非键列。
  • 所有非键列的总大小只受 INCLUDE 子句中所指定列的大小限制。例如,varchar(max) 列限制为 2 GB。

限制和局限

  • 只能对非聚集索引定义非键列。
  • 除了 text、 ntext和 image 之外的所有数据类型都可以用作非键列。
  • 精确或不精确的确定性计算列都可以是非键列。
  • 只要允许将计算列数据类型作为非键索引列,从 image、 ntext和 text 数据类型派生的计算列就可以作为非键索引列。
  • 除非先删除某一表的索引,否则无法从该表中删除非键列。
  • 除进行下列更改外,不能对非键列进行其他更改:
  • 将列的为空性从 NOT NULL 改为 NULL。
  • 增加 varchar、 nvarchar或 varbinary 列的长度。
  • 不能同时在 INCLUDE 列表和键列列表中指定列名

索引设计原则

对于一张数据表,索引的有无和建立方式的不同将会导致不同的查询结果,选择什么样的索引基于用户对数据的查询条件,这些条件体现于WHERE子句和JOIN表达式中。
  一般来说建立索引的原则包括以下内容:

  • 经常在主键列上建立索引,而且是聚集索引。
  • 有大量重复值且经常有范围查询和排序、分组发生的列,或非常频繁地被访问的列可考虑建立聚集索引。
  • 经常同时存取多列,且每列都含有重复值的,可以考虑建立复合索引来覆盖一个或一组查询,并把查询引用最频繁地列作为前导列。
  • 如果知道索引建的所有值都是唯一的,那么确保把索引定义成唯一索引。
  • 对于小型表进行索引可能不会产生优化效果,因为SQL Server在遍历索引以搜索数据时,花费的时间可能会比简单的表扫描还长。
  • 聚集索引中不要包含经常修改的列。
  • 来自聚集索引的键值由所有非聚集索引作为查找键使用,因此存储在每个非聚集索引的叶条目内,所以键宽的列不宜用作聚集索引。

创建索引

创建聚集索引

聚集索引按下列方式实现:

  • PRIMARY KEY 和 UNIQUE 约束
  • 在创建 PRIMARY KEY 约束时,如果不存在该表的聚集索引且未指定唯一非聚集索引,则将自动对一列或多列创建唯一聚集索引。 主键列不允许空值。
  • 在创建 UNIQUE 约束时,默认情况下将创建唯一非聚集索引,以便强制 UNIQUE 约束。 如果不存在该表的聚集索引,则可以指定唯一聚集索引。
  • 将索引创建为约束的一部分后,会自动将索引命名为与约束名称相同的名称。
  • 独立于约束的索引
    指定非聚集主键约束后,您可以对非主键列的列创建聚集索引。

限制和局限

  • 创建聚集索引结构后,旧(源)结构和新(目标)结构的各自的文件和文件组都需要磁盘空间。 在完成事务提交后,才会释放旧结构。 排序也需要其他临时磁盘空间。
  • 如果对具有多个现有非聚集索引的堆创建聚集索引,则必须重新生成所有非聚集索引,以使它们包含聚集键值而非行标识符 (RID)。 同样,如果删除具有多个非聚集索引的表的聚集索引,在 DROP 操作过程中,将重新生成非聚集索引。 对于大型表,这可能需要很长时间。
    对大型表创建索引的首选方法是先创建聚集索引,然后创建任何非聚集索引。 在对现有表创建索引时,请考虑将 ONLINE 选项设置为 ON。 如果设置为 ON,则不会持有长期表锁。 这使对基础表的查询或更新可以继续进行。
  • 聚集索引的索引键不能包含在 ROW_OVERFLOW_DATA分配单元中具有现有数据的 varchar列(针对具有溢出数据的表)。 如果对 varchar 列创建了聚集索引,并且 IN_ROW_DATA 分配单元中存在现有数据,则对该列执行的将数据推送到行外的后续插入或更新操作将会失败。

创建非聚集索引

可以通过下列方法实现非聚集索引:

  • UNIQUE 约束
    在创建 UNIQUE 约束时,默认情况下将创建唯一非聚集索引,以便强制 UNIQUE 约束。 如果不存在该表的聚集索引,则可以指定唯一聚集索引。
  • 独立于约束的索引
    默认情况下,如果未指定聚集,将创建非聚集索引。 对于每个表可创建的最大非聚集索引数为 999。 这包括使用 PRIMARY KEY 或 UNIQUE 约束创建的任何索引,但不包括 XML 索引。
  • 索引视图的非聚集索引
    对视图创建唯一的聚集索引后,便可以创建非聚集索引。

创建唯一索引

唯一索引可通过以下方式实现:

  • PRIMARY KEY 或 UNIQUE 约束
    在创建 PRIMARY KEY 约束时,如果不存在该表的聚集索引且未指定唯一非聚集索引,则将自动对一列或多列创建唯一聚集索引。 主键列不允许空值。
    在创建 UNIQUE 约束时,默认情况下将创建唯一非聚集索引,以便强制 UNIQUE 约束。 如果不存在该表的聚集索引,则可以指定唯一聚集索引。
  • 独立于约束的索引
    可以为一个表定义多个唯一非聚集索引。
  • 索引视图
    若要创建索引视图,请对一个或多个视图列定义唯一聚集索引。 视图将执行,并且结果集存储在该索引的页级别中,其存储方式与表数据存储在聚集索引中的方式相同。

限制和局限

  • 如果数据中存在重复的键值,则不能创建唯一索引、UNIQUE 约束或 PRIMARY KEY 约束。
  • 唯一非聚集索引可以包括包含性非键列。

创建索引语法

CREATE [ UNIQUE ] [ CLUSTERED | NONCLUSTERED ] INDEX index_name   
    ON table_or_view_name ( column [ ASC | DESC ] [ ,...n ] )   
    [ INCLUDE ( column_name [ ,...n ] ) ]  
    [ WHERE <filter_predicate> ]  
    [ WITH ( <relational_index_option> [ ,...n ] ) ]  
    [ ON { partition_scheme_name ( column_name )   
         | filegroup_name   
         | default   
         }  
    ]  
    [FILESTREAM_ON{ filestream_filegroup_name|partition_scheme_name | "NULL" }]

<relational_index_option> ::=
{  
    PAD_INDEX = { ON | OFF }  
  | FILLFACTOR = fillfactor  
  | SORT_IN_TEMPDB = { ON | OFF }  
  | IGNORE_DUP_KEY = { ON | OFF }  
  | STATISTICS_NORECOMPUTE = { ON | OFF }  
  | STATISTICS_INCREMENTAL = { ON | OFF }  
  | DROP_EXISTING = { ON | OFF }  
  | ONLINE = { ON | OFF }  
  | ALLOW_ROW_LOCKS = { ON | OFF }  
  | ALLOW_PAGE_LOCKS = { ON | OFF }  
  | MAXDOP = max_degree_of_parallelism  
  | DATA_COMPRESSION = { NONE | ROW | PAGE}   
     [ ON PARTITIONS ( { <partition_number_expression> | <range> }   
     [ , ...n ] ) ]  
}  
  • UNIQUE:为表或视图创建唯一索引。
    无论 IGNORE_DUP_KEY 是否设置为 ON,数据库引擎都不允许为已包含重复值的列创建唯一索引。 否则,数据库引擎会显示错误消息。 必须先删除重复值,然后才能为一列或多列创建唯一索引。 唯一索引中使用的列应设置为 NOT NULL,因为在创建唯一索引时,会将多个 Null 值视为重复值。
  • CLUSTERED | NONCLUSTERED:用于指定创建的索引为聚集索引/非聚集索引。
    如果指定CLUSTERED,则创建聚集索引,键值的逻辑顺序决定表中对应行的物理顺序。 如果没有指定 CLUSTERED,则创建非聚集索引。
  • column
    索引所基于的一列或多列。 指定两个或多个列名,可为指定列的组合值创建组合索引。 在table_or_view_name后面的括号内列出要包含在复合索引列,它们按排序优先级顺序。
    可以成为单个组合索引键组合最多 32 列。 组合索引键中的所有列必须在同一个表或视图中。
    属于大型对象 (LOB) 数据类型的列ntext,文本, varchar (max), nvarchar (max), varbinary (max), xml,或映像不能指定为索引的键列。
  • [ ASC |DESC]:确定特定索引列的升序或降序排序方向。 默认值为 ASC。
  • INCLUDE (column [ ,…n ] )
    指定要添加到非聚集索引的叶级别的非键列。 非聚集索引可以唯一,也可以不唯一。
    在 INCLUDE 列表中列名不能重复,且不能同时用于键列和非键列。 如果对表定义了聚集索引,则非聚集索引始终包含聚集索引列。
  • WHERE <filter_predicate>
    通过指定索引中要包含哪些行来创建筛选索引。 筛选索引必须是对表的非聚集索引。
    筛选谓词使用简单比较逻辑且不能引用计算列、UDT 列、空间数据类型列或 hierarchyID 数据类型列。 比较运算符不允许使用 NULL 文本的比较。 请改用 IS NULL 和 IS NOT NULL 运算符。
    筛选索引不适用于 XML 索引和全文检索。 对于 UNIQUE 索引,仅选定的行必须具有唯一的索引值。 筛选索引不允许有 IGNORE_DUP_KEY 选项。
  • PAD_INDEX = {ON |OFF }:设置创建索引期间中间级别页中可用空间的百分比。
    对于非叶级索引页需要使用PAD_INDEX选项设置其预留空间的大小,至少应为两行。该选项只有在指定了FILLFACTOR时才有用,因为PAD_INDEX使用由FILLFACTOR指定数值的百分比。
  • FILLFACTOR:填充因子。
    指定在SQL Server创建索引的过程中,各索引页叶级的填满程度。如果为 FILLFACTOR 指定的百分比不够大,无法容纳一行,数据库引擎将在内部覆盖该百分比以允许最小值。如果没有显式设置填充因子,则它默认为0,即尽最大可能来填充页。
    FILLFACTOR 设置仅在创建或重新生成索引时应用。 数据库引擎并不会在页中动态保持指定的可用空间百分比。
  • SORT_IN_TEMPDB = {ON |OFF }:指定是否存储在临时排序结果tempdb。 默认为 OFF。
  • IGNORE_DUP_KEY = {ON |OFF }:指定在插入操作尝试向唯一索引插入重复键值时的错误响应。
    IGNORE_DUP_KEY 选项仅适用于创建或重新生成索引后发生的插入操作。该选项在CREATE INDEX、ALTER INDEX的时候不起作用。此子句要和UNIQUE关键字同时使用。
  • DROP_EXISTING = {ON |OFF }:用于指定应删除并重新创建同名的先前存在的聚集索引或非聚集索引。
  • DATA_COMPRESSION:创建压缩的索引。ROW表示使用行压缩

【示例】

A. 创建简单的非聚集行存储索引
  下面的示例使用两个列从 dbo 创建索引 IX_FF。FactFinance 表。 下一个语句重新生成与一个多个列的索引,并使现有的名称。

-- Uses AdventureWorks  

CREATE INDEX IX_FF ON dbo.FactFinance ( FinanceKey ASC, DateKey ASC );
  
--Rebuild and add the OrganizationKey
CREATE INDEX IX_FF ON dbo.FactFinance (FinanceKey,DateKey,OrganizationKey DESC)
WITH ( DROP_EXISTING = ON );

B.创建唯一非聚集索引

IF EXISTS (SELECT name from sys.indexes  
             WHERE name = N'AK_UnitMeasure_Name')  
    DROP INDEX AK_UnitMeasure_Name ON Production.UnitMeasure;  
GO  
CREATE UNIQUE INDEX AK_UnitMeasure_Name   
    ON Production.UnitMeasure(Name);  

C.使用 IGNORE_DUP_KEY 选项

CREATE TABLE #Test (C1 nvarchar(10), C2 nvarchar(50), C3 datetime);  
GO  
CREATE UNIQUE INDEX AK_Index ON #Test (C2)  
    WITH (IGNORE_DUP_KEY = ON);  
GO  
INSERT INTO #Test VALUES (N'OC', N'Ounces', GETDATE());  
INSERT INTO #Test SELECT * FROM Production.UnitMeasure;  
GO  
SELECT COUNT(*)AS [Number of rows] FROM #Test;  
GO  
DROP TABLE #Test;  
GO  

从 Production.UnitMeasure 表中插入的、不违反唯一性约束的行将成功插入。 会发出警告并忽略重复行,即违反唯一性约束的行就不会被插入到#Test中,但不会回滚整个事务。但是如果IGNORE_DUP_KEY设置为OFF,那么即使 Production.UnitMeasure 表中只有一行违反 UNIQUE 索引约束,也不会将其中任何一行插入该表。

D.创建带有包含性 (非键) 列的索引
  以下示例创建具有一个键列 (PostalCode) 和四个非键列(AddressLine1、AddressLine2、City、StateProvinceID)的非聚集索引。 然后执行该索引覆盖的查询。 若要显示的索引上的所选查询优化器,查询菜单中的SQL Server Management Studio,选择显示实际的执行计划之前执行查询。

IF EXISTS (SELECT name FROM sys.indexes  
            WHERE name = N'IX_Address_PostalCode')  
    DROP INDEX IX_Address_PostalCode ON Person.Address;  
GO  
CREATE NONCLUSTERED INDEX IX_Address_PostalCode  
    ON Person.Address (PostalCode)  
    INCLUDE (AddressLine1, AddressLine2, City, StateProvinceID);  
GO  
SELECT AddressLine1, AddressLine2, City, StateProvinceID, PostalCode  
FROM Person.Address  
WHERE PostalCode BETWEEN N'98000' and N'99999';  
GO  

E.创建已分区的索引
  以下示例为 AdventureWorks2012 数据库中现有分区方案 TransactionsPS1 创建非聚集分区索引。 此示例假定安装了分区索引示例。

IF EXISTS (SELECT name FROM sys.indexes  
    WHERE name = N'IX_TransactionHistory_ReferenceOrderID'  
    AND object_id = OBJECT_ID(N'Production.TransactionHistory'))  
DROP INDEX IX_TransactionHistory_ReferenceOrderID  
    ON Production.TransactionHistory;  
GO  
CREATE NONCLUSTERED INDEX IX_TransactionHistory_ReferenceOrderID  
    ON Production.TransactionHistory (ReferenceOrderID)  
    ON TransactionsPS1 (TransactionDate);  
GO  
    原文作者:zoyoto
    原文地址: https://www.jianshu.com/p/417521e459df
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞