规划和管理索引
目录
(1)、使用SQL Server Management Studio创建索引
正文
一、概述
索引是对表的一列或多列值进行排序的结构。
每个索引都有一个特定的搜索码与表中的记录关联。索引按顺序存储搜索码的值。
1、数据的存储和访问方式
(1)数据的存储方式
● 每个表的数据存储在数据页的集合中。
● 数据页无序存放。表中的行在数据页中也是无序存放的。
(2)数据的访问方式
● 使用表扫面访问数据:
通过遍历表中的所有数据查找满足条件的行。
● 使用索引扫描访问数据。
通过索引查找满足条件的行。
2、SQL Server 查询优化器
● 动态调整数据的访问方式
● 总能针对数据库的状态生成一个最佳的执行计划。
3、获得执行计划的信息
(1)、以图形方式显示执行计划
阅读执行计划的方法是从右到左、从上到下阅读。
示例:
(2)、SET SHOWPLAN_ALL ON
执行该语句,不执行T-SQL查询,只返回有关语句执行情况的详细信息,并估计语句对资源的需求。
示例代码:
SET SHOWPLAN_ALL ON
GO
SELECT * FROM Customer c
INNER JOIN Province p
ON c.provinceID=p.ID
(3)、SET SHOWPLAN_Text ON
该语句与 SET SHOWPLAN_ALL ON 相似,单输出格式简洁。
二、索引的类型
1、聚集索引
(1)、结构简介
聚集索引指示表中数据行按索引键的排序次序存储。
(2)、特点
● 每个表只能有一个聚集索引。
● 聚集索引改变数据的物理排序方式,使得数据行的物理顺序和索引中的键值顺序一致。
● 聚集索引的大小根据被索引的列的情况有所不同。
● 在创建索引时,SQL Server暂时使用当前数据库的磁盘空间作为工作空间,约为被索引的表的1.2倍,这些磁盘空间在创建索引后被自动收回。
2、非聚集索引
(1)、结构简介
非聚集索引具有完全独立于数据行的结构。数据行不按索引键的次序存储。
(2)、特点
● 如果创建索引时没有指定索引的类型,默认情况下为非聚集索引。
● 应当在创建非聚集索引之前创建聚集索引。
● 每个表最多可以创建249个非聚集索引。
● 最好在唯一值较多的列上创建非聚集索引。
三、使用索引的准则
1、创建索引的列
(1)、主关键字所在的列
主键列包含唯一值,利用主键能够迅速完成单行查找,而且主关键字还经常用来作为连接查询中与其他表进行关联的条件。
(2)、外部关键字所在的列或在连接查询中经常使用的列
使用JOIN子句进行连接查询时,需要使用外部关键字与父表进行关联。
(3)、按关键字的范围值进行搜索的列
查询中的where子句可以指定按某个关键字的范围值搜索数据。
如使用:between and 、>、>=、<和<=。
(4)、按关键字的排序顺序访问的列
经常使用order by子句对数据按某个顺序进行排序的列。
使用group by进行分组的列。
2、不建索引的列
(1)、在查询中很少涉及的列
大量使用索引将占用更多的磁盘空间,SQL Server维护索引将影响系统的性能。对于查询中几乎不会使用到的列,不需要创建索引。
(2)、包含较少的唯一值
在具有大量重复值的列(如:性别)进行索引时,返回的行在所有的行中占了较高的百分比。在这种情况下,使用索引检索数据反而会比使用表扫描检索数据时的性能更差。
(3)、更新性能比查询性能更重要的列
在被索引的列上修改数据时,SQL Server将更新相关的索引,维护索引需要更多的时间和资源开销。在保证表的更新性更重要的情况下,不要在列上使用索引,否则将导致过多的维护开销,影响系统性能。
(4)、由text、ntext、image数据类型定义的列
在SQL Server中,使用大对象数据类型的列不能被索引。
四、创建和管理索引
1、创建索引
(1)、使用SQL Server Management Studio创建索引
在表上右键选择“设计”,在列上右键选择“索引/键”,在“索引/键”窗口点击“添加”按钮,将产生一个新的“主/唯一键或索引”。如下图所示,编辑相应的属性(是否唯一,是否是聚集索引等)后关闭该窗口,对表的更改进行保存。
(2)、使用T-SQL语句创建索引
● 创建索引的基本语法如下:
CREATE [UNIQUE] [CLUSTERED | NONCLUSTERED] INDEX index_name
ON {table_name | view_name}
(column_name [ASC | DESC] [,…n])
[WITH FILLFACTOR = fillfactor,IGNORE_DUP_KEY,DROP_EXISTING]
● 相关参数说明
参数 | 说明 |
UNIQUE | 为表或视图创建唯一索引(不允许存在索引值相同的两行)。 |
CLUSTERED | 创建聚集索引。每个表中只能允许有一个聚集索引。如果没有指定CLUSTERED,则创建非聚集索引。 |
NONCLUSTERED | 创建非聚集索引,非聚集索引最多可以创建249个。 |
index_name | 索引名。索引名在表中必须唯一,但在数据库中不必唯一。 |
table_name | 包含要创建索引的列的表。只有表的所有者可以在表中创建索引 |
view_name | 要建立索引的视图的名称。视图索引的类型必须是唯一聚集索引。必须使用SCHEMABINDING定义视图才能在视图上创建索引。而且,索引中所有键列必须是精确的,不能在视图的float列上或者使用非确定性函数表达式的列上创建索引。 |
column_name | 应用索引的列。可以指定一个或多个列名,当指定多个列名时,将使用这些列的组合值创建组合索引。在table_name后的圆括号中列出组合索引中要包含的列(按排序优先级排列)。 |
ASC | DESC | 确定具体某个索引列的升序或降序排序方向,默认设置为ASC。 |
FILLFACTOR | 为索引指定填充因子,用来提高表的更新性能。 |
IGNORE_DUP_KEY | 如果为索引指定了IGNORE_DUP_KEY,当INSERT语句向唯一索引列插入重复的键值时,SQL Server将发出警告消息并忽略重复的行。 |
DROP_EXISTING | 指定应除去并重建已命名的先前存在的聚集索引或非聚集索引。指定的索引名必须与现有的索引名相同。 |
● 示例代码
--在Student表的ID上创建非聚集索引
IF EXISTS(SELECT * FROM sysindexes s WHERE s.[name]='IX_Student_ID')
DROP INDEX IX_Student_ID ON Student
GO
CREATE INDEX IX_Student_ID
ON Student(ID)
GO
--在Student表的Name上创建唯一的非聚集索引
IF EXISTS(SELECT * FROM sysindexes s WHERE s.[name]='IX_Student_Name')
DROP INDEX Student.IX_Student_Name
GO
CREATE UNIQUE INDEX IX_Student_Name
ON Student([Name])
GO
--在Student表的firstName和lastName上创建组合索引
IF EXISTS(SELECT * FROM sysindexes s WHERE s.[name]='IX_Student_FirstName_LastName')
DROP INDEX IX_Student_FirstName_LastName ON Student
GO
CREATE INDEX IX_Student_FirstName_LastName
ON Student(firstName,lastName)
GO
--使用DROP_EXISTING选项在Province表的[National]列上删除并重新创建现有索引。
CREATE NONCLUSTERED INDEX IX_Province_National
ON Province([National])
WITH (DROP_EXISTING=ON,FILLFACTOR=80)
GO
(3)、注意事项
● 在创建索引前考虑表中已经创建的索引数量,最好不要在一个表中创建大量的索引。
● 检查表中已经创建的索引定义,避免对同一列重叠创建索引。
● 选择在唯一值较高的列上创建索引。检查列中唯一数据值的数量,并与表中的行数进行比较,比较的结果就是该列的可选性。
● 一个索引包含的所有列的宽度之和不能超过900字节。例如:不能在宽度为char(300)、char(300)和char(301)的3个列上创建组合索引,因为总宽度超过了900字节。
2、组合索引
组合索引是指使一个索引中包含了一个以上的列,最多可以有16列组合到一个索引中。
3、唯一索引
● 唯一索引可以确保索引列中不包含重复的值。只有当唯一性是数据本身的特性时,指定唯一索引才有意义。
● 当创建或修改唯一索引时,可以设置选项(IGNORE_DUP_KEY)以忽略重复的键。如果已设置该选项,并且试图通过添加或更新将影响多行的数据来创建重复键(使用INSERT或UPDATE语句),则不能添加导致重复的行,或在更新时放弃这样的行。
例如:在已经含有“Jones”的表中试图用“Jones”更新“Smith”,则在结果表中只有一个“Jones”,而没有“Smith”。原有的“Smith”行丢失,因为UPDATE语句实际上是DELETE语句后跟一个INSERT语句。“Smith”已删除,而试图插入列一个“Jones”的操作失败。整个事务不能回滚,因为该选项的目的是允许事务,而不管是否存在重复项。
4、主键索引
在SQL Server中为表定义主键时将自动创建主键索引,主键索引是唯一索引的特殊类型。如果该表尚未创建聚集索引,并且在创建PRIMARY KEY约束时未指定索引的类型,则PRIMARY KEY约束会自动创建聚集索引。
5、填充因子FILLFACTOR
● 页拆分:如果向已满的索引页添加新行,数据库引擎将把大约一半的行移到新页中,一遍为该新行腾出空间。这种重组称为页拆分。页拆分可为新行腾出空间,但是执行页拆分可能需要花费一定的时间,此操作会消耗大量资源。此外,它还可能造成碎片,从而导致IO操作增加。
● 填充因子指定使用现有数据创建新索引时将每页填满到什么程度。页面不会维护在任何特定的填充水平上。
● 填充因子的值是从0到100的百分比数值,默认为0。值为0并不表示页面的填满程度为0%,而是类似于设置为100的情况,在索引页中预留很少量的空闲空间。
● 指定填充因子选项是用于微调性能。创建索引时,很少需要指定填充因子。
6、管理索引
(1)、查看索引
可以通过SQL Server Management Studio查看。
也可以使用系统存储过程sp_helpindex查看表上或视图上索引的信息。
示例:
EXEC sp_helpindex 'Student'
(2)、修改索引
修改索引的过程是首先删除表中准备修改的索引,然后使用新的索引定义重新创建该索引。
创建和修改索聚集索引时,SQL Server要在磁盘上对表的行进行重组,当表中存储了大量记录时,会产生很大的开销,可能要花很长时间。
(3)、删除索引
删除索引可以回收占用的磁盘空间。
删除聚集索引会导致重建同一个表上的所有非聚集索引。
可以使用SQL Server Management Studio删除索引。
也可以使用命令删除索引。
DROP INDEX Province.IX_Province_Name
若要删除为实现Primary Key或UNIQUE约束而创建的索引,必须首先删除约束。
五、维护索引
创建索引后,必须对索引进行维护,确保索引的统计信息是有效的,才能够提高查找速度。随着更新操作不断执行,数据会变的支离破碎,这些数据碎片会导致额外的页读取,妨碍数据的并行扫描。应该定期整理索引清除数据碎片,提高数据读取的性能。
1、维护索引的统计信息
● 使用DBCC SHOW_STATISTICS命令显示指定索引的当前分步统计
DBCC SHOW_STATISTICS ('Province',IX_Province_Name)
● 使用UPDATE STATISTICS命令更新表或者视图中的索引统计信息
UPDATE STATISTICS Province
● 使用sp_updatestats系统存储过程,对当前数据库中所有用户定义的表运行UPDATE STATISTICS
EXEC sp_updatestats
2、数据碎片
● 使用DBCC SHOWCONTIG命令显示指定的表或视图的数据和索引的碎片信息
DBCC SHOWCONTIG('Province',IX_Province_Name)
结果集如下图所示:
结果集内的信息如下表:
统计信息 | 说明 |
扫描页数 | 表或索引中的页数 |
扫描区数 | 表或索引中的区数 |
区切换次数 | 遍历表或索引的页时,DBCC语句从一个区移动到另一个区的次数 |
每个区的平均页数 | 页链中每个区的页数 |
扫描密度[最佳计数:实际计数] | 百分比。这是“最佳计数”与“实际计数”的比率。如果所有内容都是连续的,则该值为100;如果该值小于100,则存在一些碎片。“最佳计数”是指在一切都连续链接的情况下,区更改的理想数目。“实际计数”是指区更改的实际次数。 |
逻辑扫描碎片 | 扫描索引的叶级页时返回的出错页的百分比。此数与堆无关。对于出错页,在IAM中所指示的下一页与叶级页中下一页指针所指向的页不同。 |
区扫描碎片 | 扫描索引的叶级页时出错区所占的百分比。此数与堆无关。对于出错区,包含当前索引页的区在物理上不是包含上一个索引页的区的下一个区。 注意:如果索引跨越多个文件,则此数字无意义。 |
每页的平均可用字节数 | 扫描的页上平均可用字节数。此数字越大,则页的填充程度越低。如果索引不会有很多随机插入,则数字越小越好。此数字还受行大小影响,行越大,此数字就越大。 |
平均页密度(满) | 页的平均密度,以百分比表示。该值会考虑行大小。因此,该值可以更准确地指示页的填充程度。百分比越大越好。 |
● 索引的碎片级可以以两种方式确定。
(1)比较“区切换次数”和“扫描区数”的值。
“区切换次数”的值应尽可能接近于“扫描区数”的值。此比率将作为“扫描密度”值计算。此值应尽可能的大,可通过减少索引碎片得到改善。如果索引跨多个数据文件,这种确定碎片的方法不起作用。
(2)了解“逻辑扫描碎片”和“区扫描碎片”的值
“逻辑扫描碎片”和“区扫描碎片”(对于较小的区)的值是表的碎片级别的最好指示。这两个值应尽可能接近零,但0%~10%的值都是可接受的。
注意:如果索引涉及多个文件,则“区扫描碎片”的值将较高。若要减小这些值,必须减少索引碎片。
3、重建和整理索引
(1)、删除并重新创建聚集索引
● 删除表上的聚集索引,随后重新创建聚集索引,将对数据进行重新组织,能够使数据页填满,并且除去数据碎片。索引的填满程度可以使用FILLFACTOR选项进行配置。
● 这种方法的缺点是索引在删除和重新创建的周期内不能工作,创建聚集索引时,需要分析数据并对数据的物理存储顺序进行排序,对系统资源造成很大开销,如果中断创建索引的操作,那么已完成的工作将被撤销。
● 使用删除旧索引然后重新创建同一索引的方式重建聚集索引是一种昂贵的方法。删除聚集索引将导致重建所有非聚集索引,如果此后重新创建聚集索引,将再次重建非聚集索引。所以,使用这个方法重建聚集索引,将导致所有非聚集索引都被删除和重新创建两次。
(2)、DBCC INDEXDEFRAG命令
● 通过使用INDEXDEFRAG语句清理索引碎片,不必单独重建每个索引。
例如:整理TestDB数据库中Province表的IX_Province_Name索引碎片。
DBCC INDEXDEFRAG (TestDB,Province,IX_Province_Name)
● 整理碎片过程中允许用户中断该操作,而且已完成工作仍然有效。
● 这种方法的缺点是在重新组织数据方面没有删除/重建聚集索引的方式有效。如果索引的碎片相对较少,则整理该索引的速度比生成一个新索引要快。但是,对碎片太多的索引进行整理可能要比重建索引花更多的时间。
(3)、DROP_EXISTING子句
CREATE INDEX的WITH DROP_EXISTING子句可以对重建索引工作进行优化,用一个步骤重新创建索引,以避免重建两次非聚集索引的开销。
--使用DROP_EXISTING选项在Province表的[National]列上删除并重新创建现有索引。
CREATE NONCLUSTERED INDEX IX_Province_National
ON Province([National])
WITH (DROP_EXISTING=ON,FILLFACTOR=80)
GO