SQL Server索引的创建与维护
Posted Do_GH
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SQL Server索引的创建与维护相关的知识,希望对你有一定的参考价值。
文章目录
创建索引
SQL Server的索引较为常用的有聚集索引、非聚集索引、唯一索引等,为数据表添加合理的索引可以提高数据的查询效率。
CREATE [ UNIQUE ] [ CLUSTERED | NONCLUSTERED ] INDEX index_name
ON <object> ( 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" ]
[ ; ]
<object> ::=
database_name.schema_name.table_or_view_name | schema_name.table_or_view_name | table_or_view_name
<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
| RESUMABLE = ON | OFF
| MAX_DURATION = <time> [MINUTES]
| ALLOW_ROW_LOCKS = ON | OFF
| ALLOW_PAGE_LOCKS = ON | OFF
| OPTIMIZE_FOR_SEQUENTIAL_KEY = ON | OFF
| MAXDOP = max_degree_of_parallelism
| DATA_COMPRESSION = NONE | ROW | PAGE
[ ON PARTITIONS ( <partition_number_expression> | <range>
[ , ...n ] ) ]
<filter_predicate> ::=
<conjunct> [ AND <conjunct> ]
<conjunct> ::=
<disjunct> | <comparison>
<disjunct> ::=
column_name IN (constant ,...n)
<comparison> ::=
column_name <comparison_op> constant
<comparison_op> ::=
IS | IS NOT | = | <> | != | > | >= | !> | < | <= | !<
<range> ::=
<partition_number_expression> TO <partition_number_expression>
【参数】
索引类型 | 描述 |
---|---|
UNIQUE | 为表或视图创建唯一索引,作为唯一索引的字段不可重复且不允许为空 |
CLUSTERED | 为表或视图创建聚集索引,一个表或视图只能存在一个聚集索引 |
NONCLUSTERED | 为表或视图创建非聚集索引 |
无论是使用PRIMARY KEY和UNIQUE约束隐式创建索引,还是使用CREATE INDEX显式创建索引,每个表都最多包含999个非聚集索引
如果未另行指定,默认索引未NONCLUSTERED
【参数】
参数名称 | 描述 |
---|---|
index_name | 索引名称(唯一) |
column | 索引所基于的一列或多列。指定两个或多个列名,可为指定列的组合值创建组合索引。 |
ASC| DESC | 确定特定索引列的升序或降序。默认为ASC |
INCLUDE | 指定要添加到非聚集索引的叶级别的非键列。 非聚集索引可以唯一,也可以不唯一。 |
一个组合索引键中最多可组合 32 列。 组合索引键中的所有列必须在同一个表或视图中。 对于聚集索引,组合索引值允许的最大大小为 900 字节,对于非聚集索引则为 1,700 字节。 对于 SQL 数据库 和 SQL Server 2016 (13.x) 以前的版本,此限制为 16 列和 900 字节。
【包含列】
指定要添加到非聚集索引的叶级别的非键列。 非聚集索引可以唯一,也可以不唯一。
在 INCLUDE 列表中列名不能重复,且不能同时用于键列和非键列。 如果对表定义了聚集索引,则非聚集索引始终包含聚集索引列。
【筛选索引】
通过指定索引中要包含哪些行来创建筛选索引。 筛选索引必须是对表的非聚集索引。 为筛选索引中的数据行创建筛选统计信息。
【指定分区】
语法 | 描述 |
---|---|
partition_scheme_name ( column_name ) | 指定分区方案,根据索引列映射到分区的文件组中。指定的分区索引列必须与分区方案中使用的分区函数参数的数据类型、长度、精度相匹配。 |
filegroup_name | 指定文件组创建指定索引。 |
default | 在表或视图所在的文件组或分区方案上创建指定索引。 |
【索引相关选项】
语法 | 描述 |
---|---|
PAD_INDEX | 是否指定索引填充 ON fillfactor 指定的可用空间百分比应用于索引的中间级页。 OFF或未指定时,考虑到中间级页上的键集,将中间级页填充到接近其容量的程度,以留出足够的空间,使之至少能够容纳索引的最大的一行。 |
FILLFACTOR | 指定索引页的填充率,值为1-100的整数。 |
SORT_IN_TEMPDB | 指定是否在 tempdb 中存储临时排序结果。 |
IGNORE_DUP_KEY | 指定在插入操作尝试向唯一索引插入重复键值时的错误响应。 ON 向唯一索引插入重复键值时将出现警告消息。 只有违反唯一性约束的行才会失败。 OFF 向唯一索引插入重复键值时将出现错误消息。 整个 INSERT 操作将被回滚。 |
STATISTICS_NORECOMPUTE | 指定是否重新计算分布统计信息。 默认为 OFF。 ON 不会自动重新计算过时的统计信息 OFF 启用统计信息自动更新功能。 |
STATISTICS_INCREMENTAL | 为 ON 时,根据分区统计信息创建统计信息。 为 OFF 时,删除统计信息树并且 SQL Server 重新计算统计信息。 默认为 OFF。 |
DROP_EXISTING | 用于删除并重新生成具有已修改列规范的现有聚集或非聚集索引,同时为该索引设置相同的名称。 默认为 OFF。 ON 删除并重新生成指定的现有索引,该索引必须与index_name参数具有相同的名称。 OFF 不删除或重新生成指定的索引,若索引存在则会报错。 |
ONLINE | 指定在索引操作期间基础表和关联的索引是否可用于查询和数据修改操作。 |
RESUMABLE | 指定联机索引操作是否可恢复。(使用对象:SQL Server(从 SQL Server 2019 (15.x) 开始)和 Azure SQL 数据库) |
MAX_DURATION | 指示可恢复联机索引操作在暂停之前执行的时间(以分钟为单位指定的整数值)。(SQL Server(从 SQL Server 2019 (15.x) 开始)和 Azure SQL 数据库) |
ALLOW_ROW_LOCKS | 指定是否允许行锁。 默认值为 ON。 |
ALLOW_PAGE_LOCKS | 指定是否允许使用页锁。 默认值为 ON。 |
OPTIMIZE_FOR_SEQUENTIAL_KEY | 指定是否针对最后一页插入进行优化。 默认为 OFF。 |
MAXDOP | 在索引操作期间替代 max degree of parallelism 配置选项。 有关详细信息,请参阅 配置 max degree of parallelism 服务器配置选项。 使用 MAXDOP 可以限制在执行并行计划的过程中使用的处理器数量。 最大数量为 64 个处理器。 |
DATA_COMPRESSION | 为指定的索引、分区号或分区范围指定数据压缩选项。 无 不压缩索引或分区 row 使用行压缩来压缩索引或分区 page 使用页压缩来压缩索引或分区。 |
聚集索引与非聚集索引
索引是与表或视图关联的磁盘上结构,可以加快从表或视图中检索行的速度。 索引包含由表或视图中的一列或多列生成的键。 这些键存储在一个结构(B 树)中,使 SQL Server 可以快速有效地查找与键值关联的行。
- 聚集
- 聚集索引根据数据行的键值在表或视图中排序和存储这些数据行。 索引定义中包含聚集索引列。 每个表只能有一个聚集索引,因为数据行本身只能按一个顺序存储。
- 只有当表包含聚集索引时,表中的数据行才按排序顺序存储。 如果表具有聚集索引,则该表称为聚集表。 如果表没有聚集索引,则其数据行存储在一个称为堆的无序结构中。
- 非聚集
- 非聚集索引具有独立于数据行的结构。 非聚集索引包含非聚集索引键值,并且每个键值项都有指向包含该键值的数据行的指针。
- 从非聚集索引中的索引行指向数据行的指针称为行定位器。 行定位器的结构取决于数据页是存储在堆中还是聚集表中。 对于堆,行定位器是指向行的指针。 对于聚集表,行定位器是聚集索引键。
聚集索引与非聚集索引可以是唯一,即当前表中索引列不允许重复,也可以不是唯一
当在创建主键时,会默认将主键作为唯一的聚集索引,这里需要注意的是,作为主键的列并不一定适合作为聚集索引。例如现有一张商品信息表,需要要求商品编号是唯一值,则需将商品编号作为唯一值,当时日常的查询并不会按照商品编号查询,而是根据价格范围、购买量或浏览量进行查询或排序,所以此时商品编号作为全表的聚集索引就显得很鸡肋。因此,可以将商品编号作为唯一的非聚集索引,这样即可保障商品编号的唯一性,也可以腾出聚集索引的位置设置更理想的列。
【聚集索引设计】
一般情况下,定义聚集索引键时使用的列越少越好。 考虑具有下列一个或多个属性的列:
-
唯一或包含许多不重复的值
例如,雇员 ID 唯一地标识雇员。 EmployeeID 列的聚集索引或主键约束可提高基于雇员 ID 号搜索雇员信息的查询的性能。 另外,可对 LastName、 FirstName、 MiddleName 列创建聚集索引,因为经常以这种方式分组和查询雇员记录,而且这些列的组合还可提供高区分度。 -
按顺序被访问
例如,产品 ID 唯一地标识 Production.Product 数据库的 AdventureWorks2012 表中的产品。 在其中指定顺序搜索的查询(如 WHERE ProductID BETWEEN 980 and 999)将从 ProductID的聚集索引受益。 这是因为行将按该键列的排序顺序存储。 -
经常用于对表中检索到的数据进行排序
按该列对表进行聚集(即物理排序)是一个好方法,它可以在每次查询该列时节省排序操作的成本。
聚集索引不适用于具有下列属性的列:
-
频繁更改的列
这将导致整行移动,因为 数据库引擎 必须按物理顺序保留行中的数据值。 这一点要特别注意,因为在大容量事务处理系统中数据通常是可变的。 -
宽键
宽键是若干列或若干大型列的组合。 所有非聚集索引将聚集索引中的键值用作查找键。 为同一表定义的任何非聚集索引都将增大许多,这是因为非聚集索引项包含聚集键,同时也包含为此非聚集索引定义的键列。
【非聚集索引设计】
非聚集索引与聚集索引具有相同的 B 树结构,它们之间的显著差别在于以下两点:
- 基础表的数据行不按非聚集键的顺序排序和存储。
- 非聚集索引的叶级别是由索引页而不是由数据页组成。
非聚集索引行中的行定位器或是指向行的指针,或是行的聚集索引键,如下所述:
- 如果表是堆(意味着该表没有聚集索引),则行定位器是指向行的指针。 该指针由文件标识符 (ID)、页码和页上的行数生成。 整个指针称为行 ID (RID)。
- 如果表有聚集索引或索引视图上有聚集索引,则行定位器是行的聚集索引键。
对于索引使用的每个分区,非聚集索引在 index_id >1 的 sys.partitions
中都有对应的一行。 默认情况下,一个非聚集索引有单个分区。 如果一个非聚集索引有多个分区,则每个分区都有一个包含该特定分区的索引行的 B 树结构。 例如,如果一个非聚集索引有四个分区,那么就有四个 B 树结构,每个分区中一个。
重新设计索引键大小较大的非聚集索引,以便只有用于搜索和查找的列为键列。 将覆盖查询的所有其他列设置为包含性非键列。 这样,将具有覆盖查询所需的所有列,但索引键本身较小,而且效率高。
避免添加不必要的列。 添加过多的索引列(键列或非键列)会对性能产生下列影响:
- 一页上能容纳的索引行将更少。 这样会使 I/O 增加并降低缓存效率。
- 需要更多的磁盘空间来存储索引。 特别是,将 varchar(max) 、 nvarchar(max) 、 varbinary(max) 或 xml 数据类型添加为非键索引列会显著增加磁盘空间要求。 这是因为列值被复制到了索引叶级别。 因此,它们既驻留在索引中,也驻留在基表中。
- 索引维护可能会增加对基础表或索引视图执行修改、插入、更新或删除操作所需的时间。
填充因子
提供填充因子选项是为了优化索引数据存储和性能。 当创建或重新生成索引时,填充因子的值可确定每个叶级页上要填充数据的空间百分比,以便在每一页上保留一些剩余空间作为以后扩展索引的可用空间。 例如,指定填充因子的值为 80 表示每个叶级页上将有 20% 的空间保留为空,以便随着向基础表中添加数据而为扩展索引提供空间。 在索引行之间保留可用空间,而不是在索引的末尾保留。
填充因子的值是 1 到 100 之间的百分比,服务器范围的默认值为 0,这表示将完全填充叶级页。
【页拆分】
正确选择填充因子值可提供足够的空间,以便随着向基础表中添加数据而扩展索引,从而降低页拆分的可能性。如果向已满的索引页添加新行, 数据库引擎 将把大约一半的行移到新页中,以便为该新行腾出空间。 这种重组称为页拆分。 页拆分可为新记录腾出空间,但是执行页拆分可能需要花费一定的时间,此操作会消耗大量资源。 此外,它还可能造成碎片,从而导致 I/O 操作增加。 如果经常发生页拆分,可通过使用新的或现有的填充因子值来重新生成索引,从而重新分发数据。 有关详细信息,请参阅 重新组织和重新生成索引。
虽然采用较小的非零填充因子值可减少随着索引的增长而拆分页的需求,但是索引将需要更多的存储空间,并且会降低读取性能。 即使对于面向许多插入和更新操作的应用程序,数据库的读取次数一般也会超过数据库写入次数的 5 到 10 倍。 因此,指定一个不同于默认值的填充因子会降低数据库的读取性能,而降低量与填充因子设置的值成反比。 例如,当填充因子的值为 50 时,数据库的读取性能会降低两倍。 读取性能降低是因为索引包含较多的页,因此增加了检索数据所需的磁盘 I/O 操作。
【将数据添加到表的末尾】
如果新数据在表中均匀分布,则不是 0 或 100 的非零填充因子对性能有利。 但是,如果所有数据都添加到表的末尾,则不会填充索引页中的可用空间。 例如,如果索引键列是 IDENTITY 列,则新行的键将总是增加,并且索引行在逻辑意义上将添加到索引的末尾。 如果将用加长行的大小的数据来更新现有行,则请使用小于 100 的填充因子。 每页上的额外字节将有助于把行中的额外长度造成的页拆分降低到最小限度。
重新组织和重新生成
索引碎片
在B树索引中,当索引包含的页中,索引中的逻辑排序(基于索引中的键值)与索引页的物理排序不匹配时,就存在碎片。
无论何时对基础数据执行插入、更新或删除操作,数据库引擎 都会自动修改索引。 例如,在表中添加行可能会导致拆分行存储索引中的现有页,以腾出空间来插入新行。 随着时间的推移,这些修改可能会导致索引中的数据分散在数据库中(含有碎片)。
对于使用完全或范围索引扫描读取多个页面的查询,碎片多的索引会降低查询性能,因为读取查询所需的数据可能需要额外的 I/O。 查询可能需要大量的小型 I/O 请求来读取相同数量的数据,而不是使用少量的大型 I/O 请求。
存储子系统的顺序 I/O 性能优于随机 I/O 性能时,索引碎片可能会降低性能,因为读取碎片索引需要更多的随机 I/O。
页面密度
数据库中的每个页面包含的行数可以变化。 如果行占用了页面上的所有空间,则页面密度为 100%。 如果页面是空白的,则页面密度为 0%。 如果密度为 100% 的页面拆分为两个页面以容纳新行,则这两个新页面的密度约为 50%。
页面密度较低时,存储相同数量的数据需要更多的页面。 这意味着,读写此数据需要更多的 I/O,缓存此数据需要更多的内存。 内存有限时,要缓存的查询所需页面较少,从而导致磁盘 I/O 增加。 因此,页面密度较低会降低性能。
当 数据库引擎 向页面添加行时,如果索引的填充因子设置为 100 或 0(该值在此上下文中起等效作用)以外的值,则不会完全填充页面。 这会导致页面密度较低,同样会增加 I/O 开销,并降低性能。
页面密度较低可能会增加中间 B 树级别的数量。 这会适度增加在索引扫描和索引查找中找到查找叶级别页面的 CPU 和 I/O 开销。
当查询优化器编译查询计划时,会考虑读取查询所需数据需要的 I/O 开销。 页面密度较低时,需要读取的页面数量更多,因此 I/O 开销更高。 这可能会影响对查询计划的选择。 例如,页面密度因页面拆分而随时间降低时,优化器可能会使用不同的性能和资源消耗配置文件为同一查询编译其他计划。
下面的示例可确定当前数据库中所有行存储索引的平均碎片和页面密度。 它通过 SAMPLED 模式快速返回可操作的结果。 若要获得更准确的结果,请使用 DETAILED 模式。 这需要扫描所有索引页面,并且可能需要很长时间。
SELECT OBJECT_SCHEMA_NAME(ips.object_id) AS schema_name,
OBJECT_NAME(ips.object_id) AS object_name,
i.name AS index_name,
i.type_desc AS index_type,
ips.avg_fragmentation_in_percent,
ips.avg_page_space_used_in_percent,
ips.page_count,
ips.alloc_unit_type_desc
FROM sys.dm_db_index_physical_stats(DB_ID(), default, default, default, 'SAMPLED') AS ips
INNER JOIN sys.indexes AS i
ON ips.object_id = i.object_id
AND
ips.index_id = i.index_id
ORDER BY page_count DESC;
-- Syntax for SQL Server and Azure SQL Database
ALTER INDEX index_name | ALL ON <object>
REBUILD
[ PARTITION = ALL ] [ WITH ( <rebuild_index_option> [ ,...n ] ) ]
| [ PARTITION = partition_number [ WITH ( <single_partition_rebuild_index_option> ) [ ,...n ] ]
| DISABLE
| REORGANIZE [ PARTITION = partition_number ] [ WITH ( <reorganize_option> ) ]
| SET ( <set_index_option> [ ,...n ] )
| RESUME [WITH (<resumable_index_options>,[...n])]
| PAUSE
| ABORT
[ ; ]
<object> ::=
database_name.schema_name.table_or_view_name | schema_name.table_or_view_name | table_or_view_name
<rebuild_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
| ONLINE =
ON [ ( <low_priority_lock_wait> ) ]
| OFF
| RESUMABLE = ON | OFF
| MAX_DURATION = <time> [MINUTES
| ALLOW_ROW_LOCKS = ON | OFF
| ALLOW_PAGE_LOCKS = ON | OFF
| MAXDOP = max_degree_of_parallelism
| DATA_COMPRESSION = NONE | ROW | PAGE | COLUMNSTORE | COLUMNSTORE_ARCHIVE
[ ON PARTITIONS ( <partition_number> [ TO <partition_number>] [ , ...n ] ) ]
<single_partition_rebuild_index_option> ::=
SORT_IN_TEMPDB = ON | OFF
| MAXDOP = max_degree_of_parallelism
| RESUMABLE = ON | OFF
| MAX_DURATION = <time> [MINUTES
| DATA_COMPRESSION = NONE | ROW | PAGE | COLUMNSTORE | COLUMNSTORE_ARCHIVE
| ONLINE = ON [ ( <low_priority_lock_wait> ) ] | OFF
<reorganize_option>::=
LOB_COMPACTION = ON | OFF
| COMPRESS_ALL_ROW_GROUPS = ON | OFF
<set_index_option>::=
ALLOW_ROW_LOCKS = ON | OFF
| ALLOW_PAGE_LOCKS = ON | OFF
| OPTIMIZE_FOR_SEQUENTIAL_KEY = ON | OFF
| IGNORE_DUP_KEY = ON | OFF
| STATISTICS_NORECOMPUTE = ON | OFF
| COMPRESSION_DELAY= 0 | delay [Minutes]
<resumable_index_option> ::=
MAXDOP = max_degree_of_parallelism
| MAX_DURATION =<time> [MINUTES]
| <low_priority_lock_wait>
<low_priority_lock_wait>::=
WAIT_AT_LOW_PRIORITY ( MAX_DURATION = <time> [ MINUTES ] ,
ABORT_AFTER_WAIT = NONE | SELF | BLOCKERS )
【参数】
参数 | 描述 |
---|---|
index_name | 索引名称 |
ALL | 全部索引 |
object | 指定的数据表 |
当指定某一索引名时,只修改指定索引的属性,当指定为ALL时,将会修改该表的所有索引属性。
【重建索引】
当指定REBUILD时,重建索引会将索引列、索引类型、唯一属性和排序顺序重新生成同时会启用已禁用的索引,等同于DBCC DBREINDEX
。
PARTITION | 当索引为分区索引时,指定重新生成或重新组织索引的一个分区,PARTITION = ALL时将重新生成所有分区 |
【禁用索引】
将索引标记为已禁用,从而不能由 数据库引擎使用。 可禁用任何索引。 已禁用的索引的索引定义保留在没有基础索引数据的系统目录中。 禁用聚集索引将阻止用户访问基础表数据。 若要启用索引,请使用 ALTER INDEX REBUILD
或 CREATE INDEX WITH DROP_EXISTING
。
【重新组织】
对于行存储索引,REORGANIZE 指定要重新组织索引叶级别。
当
ALTER INDEX REORGANIZE
使用显式事务(例如,BEGIN TRAN …COMMIT/ROLLBACK 中的ALTER INDEX
)而不是默认隐式事务模式时,REORGANIZE
的锁定行为会变得更加严格,这可能会导致阻塞。
LOB_COMPACTION
- 指定要压缩包含以下这些大型对象 (LOB) 数据类型的数据的所有页面:图像、文本、ntext、varchar(max)、nvarchar(max)、varbinary(max) 和 xml。 压缩这些数据可以减少磁盘上的数据大小。
- 对于聚集索引,这会压缩表中包含的所有 LOB 列。
- 对于非聚集索引,这会压缩作为索引中非键(已包括)列的所有 LOB 列。
REORGANIZE ALL
对所有索引执行LOB_COMPACTION
。 对于每个索引,这会压缩聚集索引、基础表中的所有 LOB 列 或是非聚集索引中的包含列。
Microsoft 建议客户考虑并采用以下索引维护策略:
- 不要假定索引维护始终会显著改进工作负载。
- 衡量重新组织或重新生成索引对工作负载的查询性能的特定影响。 可通过查询存储使用 A/B 测试技术衡量“维护前”和“维护后”的性能。
- 如果发现重新生成索引会提高性能,请尝试将其替换为更新统计信息。 这样可能会获得相似的提升。 在这种情况下,你可能不需要那么频繁地(或完全不需要)重新生成索引,而可以改为定期执行统计信息更新。 若要获得某些统计信息,你可能需要使用 WITH SAMPLE … PERCENT 或 WITH FULLSCAN 子句提高采样率(这种情况并不常见)。
- 随着时间的推移监视索引碎片和页面密度,以确定这些值的升降与查询性能是否相关。 如果增加碎片或降低页面密度导致性能下降到让人无法接受的程度,则重新组织或重新生成索引。 通常,重新组织或重新生成性能下降的查询所使用的特定索引即可。 这样就不必因维护数据库中每个索引产生更高的资源成本。
- 通过在碎片/页面密度和性能之间建立关联,你还可以确定维护索引的频率。 不要假定必须按固定计划执行维护。 更好的策略是监视碎片和页面密度,并在性能下降到不可接受的程度前根据需要运行索引维护。
- 如果已确定需要维护索引并且可接受其资源成本,则在资源使用率低的时段(如果存在这样的时段)内执行维护,并牢记资源使用模式可能会随时间而变化。
描述的聚集索引和非聚集索引
SQL Server 索引体系结构和设计指南
CREATE INDEX(Transact-SQL)
ALTER INDEX (Transact-SQL)
优化索引维护以提高查询性能并减少资源消耗
以上是关于SQL Server索引的创建与维护的主要内容,如果未能解决你的问题,请参考以下文章