SQL索引


  SQL索引在数据库优化中占有一个很大的比例, 一个好的索引的设计,能够让你的效率提高几十甚至几百倍,在这里将带你一步步揭开他的神奇面纱。



1.1 什么是索引?

  SQL索引有两种,聚集索引和非聚集索引,索引主要目的是提高了SQL Server系统的性能,加快数据的查询速度与降低系统的响应时间 

以下举两个简单的样例:

图书馆的样例:一个图书馆那么多书,怎么管理呢?建立一个字母开头的文件夹,比如:a开头的书,在第一排,b开头的在第二排,这样在找什么书就好说了,这个就是一个聚集索引,但是非常多人借书找某某作者的,不知道书名怎么办?图书管理员在写一个文件夹,某某作者的书分别在第几排,第几排。这就是一个非聚集索引

字典的样例:字典前面的文件夹,能够依照拼音和部首去查询,我们想查询一个字,仅仅须要依据拼音或者部首去查询。就能够高速的定位到这个汉字了,这个就是索引的优点,拼音查询法就是聚集索引,部首查询就是一个非聚集索引.

    看了上面的样例,以下的一句话大家就非常easy理解了:聚集索引存储记录是物理上连续存在,而非聚集索引是逻辑上的连续,物理存储并不连续。就像字段。聚集索引是连续的,a后面肯定是b,非聚集索引就不连续了。就像图书馆的某个作者的书,有可能在第1个货架上和第10个货架上。另一个小知识点就是:聚集索引一个表仅仅能有一个,而非聚集索引一个表能够存在多个。

 

1.2 索引的存储机制

    首先,无索引的表,查询时,是依照顺序存续的方法扫描每一个记录来查找符合条件的记录,这样效率十分低下,举个样例。假设我们将字典的汉字随即打乱,没有前面的依照拼音或者部首查询,那么我们想找一个字。依照顺序的方式去一页页的找,这样效率有多底,大家能够想象。

       聚集索引和非聚集索引的根本差别是表记录的排列顺序和与索引的排列顺序是否一致,事实上理解起来很easy。还是举字典的样例:假设依照拼音查询,那么都是从a-z的。是具有连续性的,a后面就是b,b后面就是c, 聚集索引就是这种,他是和表的物理排列顺序是一样的,比如有id为聚集索引。那么1后面肯定是2,2后面肯定是3。所以说这种搜索顺序的就是聚集索引。非聚集索引就和依照部首查询是一样是,可能依照偏房查询的时候,依据偏旁‘弓’字旁,索引出两个汉字,张和弘,可是这两个事实上一个在100页,一个在1000页,(这里仅仅是举个样例)。他们的索引顺序和数据库表的排列顺序是不一样的。这个样的就是非聚集索引。

      原理明确了,那他们是怎么存储的呢?在这里简单的说一下,聚集索引就是在数据库被开辟一个物理空间存放他的排列的值,比如1-100,所以当插入数据时。他会又一次排列整个整个物理空间,而非聚集索引事实上能够看作是一个含有聚集索引的表,他仅仅仅包括原表中非聚集索引的列和指向实际物理表的指针。

他仅仅记录一个指针。事实上就有点和堆栈几乎相同的感觉了

 

1.3 什么情况下设置索引


动作描写叙述

使用聚集索引 

 使用非聚集索引

 外键列

 应

 应

 主键列

 应

 应

 列常常被分组排序(order by)

 应

 应

 返回某范围内的数据

 应

 不应

 小数目的不同值

 应

 不应

 大数目的不同值

 不应

 应

 频繁更新的列

不应 

 应

 频繁改动索引列

 不应

 应

 一个或极少不同值

 不应

 不应

 

建立索引的原则:

1) 定义主键的数据列一定要建立索引。

2) 定义有外键的数据列一定要建立索引。

3) 对于常常查询的数据列最好建立索引。

4) 对于须要在指定范围内的高速或频繁查询的数据列;

5) 经经常使用在WHERE子句中的数据列。

6) 常常出如今keywordorder by、group by、distinct后面的字段,建立索引。假设建立的是复合索引,索引的字段顺序要和这些keyword后面的字段顺序一致。否则索引不会被使用。

7) 对于那些查询中非常少涉及的列。反复值比較多的列不要建立索引。

8) 对于定义为textimagebit的数据类型的列不要建立索引。

9) 对于常常存取的列避免建立索引 

9) 限制表上的索引数目。对一个存在大量更新操作的表,所建索引的数目一般不要超过3个。最多不要超过5个。索引虽说提高了訪问速度,但太多索引会影响数据的更新操作。

10) 对复合索引,依照字段在查询条件中出现的频度建立索引。在复合索引中,记录首先依照第一个字段排序。对于在第一个字段上取值同样的记录。系统再依照第二个字段的取值排序,以此类推。

因此仅仅有复合索引的第一个字段出如今查询条件中,该索引才可能被使用,因此将应用频度高的字段,放置在复合索引的前面,会使系统最大可能地使用此索引,发挥索引的作用。

 

1.4 怎样创建索引

  1.41 创建索引的语法:

CREATE [UNIQUE][CLUSTERED | NONCLUSTERED]  INDEX  index_name  

ON {table_name | view_name} [WITH [index_property [,....n]]

说明:

UNIQUE: 建立唯一索引。

CLUSTERED: 建立聚集索引。

NONCLUSTERED: 建立非聚集索引。

Index_property: 索引属性。

 UNIQUE索引既能够採用聚集索引结构。也能够採用非聚集索引的结构,假设不指明採用的索引结构,则SQL Server系统默觉得採用非聚集索引结构。

  1.42 删除索引语法:

DROP INDEX table_name.index_name[,table_name.index_name]

说明:table_name: 索引所在的表名称。

index_name : 要删除的索引名称。

  1.43 显示索引信息:

使用系统存储过程:sp_helpindex 查看指定表的索引信息。

运行代码例如以下:

Exec sp_helpindex book1;

1.5 索引使用次数、索引效率、占用CPU检測、索引缺失


  当我们明确了什么是索引,什么时间创建索引以后。我们就会想。我们创建的索引究竟效率运行的怎么样?好不好?我们创建的对不正确?

  首先我们来认识一下DMV,DMV (dynamic management view)动态管理视图和函数返回特定于实现的内部状态数据。推出SQL Server 2005时,微软介绍了很多被称为dmvs的系统视图,让您能够探測SQL Server 的健康状况,诊断问题。或查看SQL Server实例的执行信息。统计数据是在SQL Server执行的时候開始收集的。而且在SQL Server每次启动的时候。统计数据将会被重置。当你删除或者又一次创建其组件时。某些dmv的统计数据也能够被重置。比如存储过程和表,而其他的dmv信息在执行dbcc命令时也能够被重置。

  当你使用一个dmv时,你须要紧记SQL Server收集这些信息有多长时间了,以确定这些从dmv返回的数据究竟有多少可用性。假设SQL Server仅仅执行了非常短的一段时间。你可能不想去使用一些dmv统计数据,由于他们并非一个可以代表SQL Server实例可能遇到的真实工作负载的样本。

还有一方面。SQL Server仅仅能维持一定量的信息,有些信息在进行SQL Server性能管理活动的时候可能丢失,所以假设SQL Server已经执行了相当长的一段时间,一些统计数据就有可能已被覆盖。

  因此,不论什么时候你使用dmv,当你查看从SQL Server 2005dmvs返回的相关资料时。请务必将以上的观点装在脑海中。

仅仅有当你确信从dmvs获得的信息是准确和完整的,你才干变更数据库或者应用程序代码。

以下就看一下dmv究竟能带给我们那些好的功能呢?

  1.51 索引使用次数

我们下看一下以下两种查询方式返回的结果(这两种查询的查询用途一致)

----

declare @dbid int

select @dbid = db_id()

select objectname=object_name(s.object_id), s.object_id, indexname=i.name, i.index_id

            , user_seeks, user_scans, user_lookups, user_updates

from sys.dm_db_index_usage_stats s,

            sys.indexes i

where database_id = @dbid and objectproperty(s.object_id,'IsUserTable') = 1

and i.object_id = s.object_id

and i.index_id = s.index_id

order by (user_seeks + user_scans + user_lookups + user_updates) asc


返回查询结果

 

 

②:使用多的索引排在前面

SELECT  objects.name ,

        databases.name ,

        indexes.name ,

        user_seeks ,

        user_scans ,

        user_lookups ,

        partition_stats.row_count

FROM    sys.dm_db_index_usage_stats stats

        LEFT JOIN sys.objects objects ON stats.object_id = objects.object_id

        LEFT JOIN sys.databases databases ON databases.database_id = stats.database_id

        LEFT JOIN sys.indexes indexes ON indexes.index_id = stats.index_id

                                         AND stats.object_id = indexes.object_id

        LEFT  JOIN sys.dm_db_partition_stats partition_stats ON stats.object_id = partition_stats.object_id

                                                              AND indexes.index_id = partition_stats.index_id

WHERE   1 = 1

--AND databases.database_id = 7

        AND objects.name IS NOT NULL

        AND indexes.name IS NOT NULL

        AND user_scans>0

ORDER BY user_scans DESC ,

        stats.object_id ,

        indexes.index_id


返回查询结果

 

 

user_seeks : 通过用户查询运行的搜索次数。

 
 个人理解: 此统计索引搜索的次数


我们能够清晰的看到。那些索引用的多。那些索引没用过,大家能够依据查询出来的东西去分析自己的数据索引和表

1.52 索引提高了多少性能


新建了索引究竟添加了多少数据的效率呢?究竟提高了多少性能呢?执行例如以下SQL能够返回连接缺失索引动态管理视图,发现最实用的索引和创建索引的方法:


SELECT  

avg_user_impact AS average_improvement_percentage,  

avg_total_user_cost AS average_cost_of_query_without_missing_index,  

'CREATE INDEX ix_' + [statement] +  

ISNULL(equality_columns, '_') + 

ISNULL(inequality_columns, '_') + ' ON ' + [statement] +  

' (' + ISNULL(equality_columns, ' ') +  

ISNULL(inequality_columns, ' ') + ')' +  

ISNULL(' INCLUDE (' + included_columns + ')', '')  

AS create_missing_index_command 

FROM sys.dm_db_missing_index_details a INNER JOIN  

sys.dm_db_missing_index_groups b ON a.index_handle = b.index_handle 

INNER JOIN sys.dm_db_missing_index_group_stats c ON  

b.index_group_handle = c.group_handle 

WHERE avg_user_impact > = 40

 


返回结果

 

 

尽管用户可以改动性能提高的百分比。但以上查询返回全部可以将性能提高40%或更高的索引。

你可以清晰的看到每一个索引提高的性能和效率了

1.53 :最占用CPU、运行时间最长命令

这个和索引无关,可是还是在这里提出来。由于他也属于DMV带给我们的功能吗,他能够让你轻松查询出,那些sql语句占用你的cpu最高

 

SELECT TOP 100 execution_count,

           total_logical_reads /execution_count AS [Avg Logical Reads],

           total_elapsed_time /execution_count AS [Avg Elapsed Time],

                db_name(st.dbid) as [database name],

           object_name(st.dbid) as [object name],

           object_name(st.objectid) as [object name 1],

           SUBSTRING(st.text, (qs.statement_start_offset / 2) + 1, 

           ((CASE statement_end_offset WHEN - 1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) 

             / 2) + 1) AS statement_text

  FROM sys.dm_exec_query_stats AS qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st

 WHERE execution_count > 100

 ORDER BY 1 DESC;


 

返回结果:

 

 

运行时间最长的命令

SELECT TOP 10 COALESCE(DB_NAME(st.dbid),

DB_NAME(CAST(pa.value as int))+'*',

'Resource') AS DBNAME,

SUBSTRING(text,

-- starting value for substring

        CASE WHEN statement_start_offset = 0

OR statement_start_offset IS NULL

THEN 1

ELSE statement_start_offset/2 + 1 END,

-- ending value for substring

        CASE WHEN statement_end_offset = 0

OR statement_end_offset = -1

OR statement_end_offset IS NULL

THEN LEN(text)

ELSE statement_end_offset/2 END -

CASE WHEN statement_start_offset = 0

OR statement_start_offset IS NULL

THEN 1

ELSE statement_start_offset/2  END + 1

)  AS TSQL,

total_logical_reads/execution_count AS AVG_LOGICAL_READS

FROM sys.dm_exec_query_stats

CROSS APPLY sys.dm_exec_sql_text(sql_handle) st

OUTER APPLY sys.dm_exec_plan_attributes(plan_handle) pa

WHERE attribute = 'dbid'

ORDER BY AVG_LOGICAL_READS DESC ;


 

 

看到了吗?直接能够定位到你的sql语句,优化去吧。还等什么呢?

1.54:缺失索引

缺失索引就是帮你查找你的数据库缺少什么索引。告诉你那些字段须要加上索引,这样你就能够依据提示加入你数据库缺少的索引了

SELECT TOP 10

[Total Cost] = ROUND(avg_total_user_cost * avg_user_impact * (user_seeks + user_scans),0)

, avg_user_impact

, TableName = statement

, [EqualityUsage] = equality_columns

, [InequalityUsage] = inequality_columns

, [Include Cloumns] = included_columns

FROM    sys.dm_db_missing_index_groups g

INNER JOIN sys.dm_db_missing_index_group_stats s

ON s.group_handle = g.index_group_handle

INNER JOIN sys.dm_db_missing_index_details d

ON d.index_handle = g.index_handle

ORDER BY [Total Cost] DESC;


查询结果例如以下:

 

 

 

1.6  适当创建索引覆盖

  如果你在Sales(SelesID,SalesDate,SalesPersonID,ProductID,Qty)的外键列(ProductID)上创建了一个索引,如果ProductID列是一个高选中性列,那么不论什么在where子句中使用索引列(ProductID)select查询都会更快。如果在外键上没有创建索引。将会发生所有扫描。但还有办法能够进一步提升查询性能。

  如果Sales表有10,000行记录,以下的SQL语句选中400(总行数的4%): 

SELECT SalesDate, SalesPersonID FROM Sales WHERE ProductID = 112

  我们来看看这条SQL语句在SQL运行引擎中是怎样运行的:

  1)Sales表在ProductID列上有一个非聚集索引。因此它查找非聚集索引树找出ProductID=112的记录;

  2)包括ProductID = 112记录的索引页也包括全部的聚集索引键(全部的主键键值,即SalesID);

  3)针对每个主键(这里是400)SQL Server引擎查找聚集索引树找出真实的行在相应页面中的位置;

  SQL Server引擎从相应的行查找SalesDateSalesPersonID列的值。

  在上面的步骤中,对ProductID = 112的每一个主键记录(这里是400)SQL Server引擎要搜索400次聚集索引树以检索查询中指定的其他列(SalesDateSalesPersonID)

  假设非聚集索引页中包含了聚集索引键和其他两列(SalesDate,SalesPersonID)的值,SQL Server引擎可能不会运行上面的第34步。直接从非聚集索引树查找ProductID列速度还会快一些。直接从索引页读取这三列的数值。

  幸运的是。有一种方法实现了这个功能。它被称为覆盖索引,在表列上创建覆盖索引时。须要指定哪些额外的列值须要和聚集索引键值(主键)一起存储在索引页中。以下是在Sales ProductID列上创建覆盖索引的样例: 

    CREATE INDEX NCLIX_Sales_ProductID--Index name
  ON dbo.Sales(ProductID)--Column on which index is to be created
  INCLUDE(SalesDate, SalesPersonID)--Additional column values to include

  应该在那些select查询中常使用到的列上创建覆盖索引。但覆盖索引中包含过多的列也不行,由于覆盖索引列的值是存储在内存中的,这样会消耗过多内存,引发性能下降。

  

1.7 索引碎片


在数据库性能优化一:数据库自身优化一文中已经讲到了这个问题,再次就不做过多的反复地址:http://www.cnblogs.com/AK2012/archive/2012/12/25/2012-1228.html

 

1.8 索引实战(摘抄)


之所以这章摘抄。是由于以下这个文章已经写的太好了,预计我写出来也无法比这个好了,所以就摘抄了

人们在使用SQL时往往会陷入一个误区,即太关注于所得的结果是否正确。而忽略了不同的实现方法之间可能存在的性能差异,这样的性能差异在大型的或是复杂的数据库环境中(如联机事务处理OLTP或决策支持系统DSS)中表现得尤为明显。

笔者在工作实践中发现,不良的SQL往往来自于不恰当的索引设计、不充份的连接条件和不可优化的where子句。

在对它们进行适当的优化后,其执行速度有了明显地提高!

以下我将从这三个方面分别进行总结:

为了更直观地说明问题。全部实例中的SQL执行时间均经过測试。不超过1秒的均表示为(< 1秒)。

----

測试环境: 主机:HP LH II---- 主频:330MHZ---- 内存:128----

操作系统:Operserver5.0.4----

数据库:Sybase11.0.3

 

一、不合理的索引设计----

例:表record620000行,试看在不同的索引下,以下几个 SQL的执行情况:

---- 1.在date上建有一非个群集索引

select count(*) from record where date >'19991201' and date < '19991214'and amount >2000 (25秒)

select date ,sum(amount) from record group by date(55秒)

select count(*) from record where date >'19990901' and place in ('BJ','SH') (27秒)

---- 分析:----

date上有大量的反复值。在非群集索引下。数据在物理上随机存放在数据页上。在范围查找时。必须运行一次表扫描才干找到这一范围内的所有行。


---- 2.在date上的一个群集索引

select count(*) from record where date >'19991201' and date < '19991214' and amount >2000 (14秒)

select date,sum(amount) from record group by date(28秒)

select count(*) from record where date >'19990901' and place in ('BJ','SH')(14秒)

---- 分析:---- 在群集索引下。数据在物理上按顺序在数据页上,反复值也排列在一起,因而在范围查找时。能够先找到这个范围的起末点,且仅仅在这个范围内扫描数据页。避免了大范围扫描,提高了查询速度。


---- 3.在place。date。amount上的组合索引

select count(*) from record where date >'19991201' and date < '19991214' and amount >2000 (26秒)

select date,sum(amount) from record group by date(27秒)

select count(*) from record where date >'19990901' and place in ('BJ, 'SH')(< 1秒)


---- 分析:---- 这是一个不非常合理的组合索引,由于它的前导列是place,第一和第二条SQL没有引用place,因此也没有利用上索引;第三个SQL使用了place。且引用的全部列都包括在组合索引中,形成了索引覆盖,所以它的速度是非常快的。


---- 4.在date,place,amount上的组合索引

select count(*) from record where date >'19991201' and date < '19991214' and amount >2000(< 1秒)

select date,sum(amount) from record group by date(11秒)

select count(*) from record where date >'19990901' and place in ('BJ','SH')(< 1秒)

---- 分析:---- 这是一个合理的组合索引。

它将date作为前导列,使每一个SQL都能够利用索引,而且在第一和第三个SQL中形成了索引覆盖。因而性能达到了最优。


---- 5.总结:----

缺省情况下建立的索引是非群集索引。但有时它并非最佳的。合理的索引设计要建立在对各种查询的分析和预測上。

一般来说:

①.有大量反复值、且常常有范围查询(between, >,< >=,< =)和order bygroup by发生的列,可考虑建立群集索引;

②.常常同一时候存取多列,且每列都含有反复值可考虑建立组合索引。

③.组合索引要尽量使关键查询形成索引覆盖,其前导列一定是使用最频繁的列。

 

二、不充份的连接条件:

例:表card7896行,在card_no上有一个非聚集索引,表account191122行,在account_no上有一个非聚集索引。试看在不同的表连接条件下。两个SQL的运行情况:


select sum(a.amount) from account a,card b where a.card_no = b.card_no(20秒)

select sum(a.amount) from account a,card b where a.card_no = b.card_no and a.account_no=b.account_no(< 1秒)


---- 分析:---- 在第一个连接条件下。最佳查询方案是将account作外层表,card作内层表。利用card上的索引,其I/O次数可由下面公式估算为:

外层表account上的22541+(外层表account191122*内层表card上相应外层表第一行所要查找的3页)=595907I/O

在第二个连接条件下,最佳查询方案是将card作外层表。account作内层表。利用account上的索引。其I/O次数可由下面公式估算为:外层表card上的1944+(外层表card7896*内层表account上相应外层表每一行所要查找的4页)= 33528I/O

可见。仅仅有充份的连接条件,真正的最佳方案才会被运行。

总结:

1.多表操作在被实际运行前,查询优化器会依据连接条件,列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表。内外表的选择可由公式:外层表中的匹配行数*内层表中每一次查找的次数确定,乘积最小为最佳方案。

2.查看运行方案的方法-- set showplanon,打开showplan选项。就能够看到连接顺序、使用何种索引的信息。想看更具体的信息。需用sa角色运行dbcc(3604,310,302)

 

三、不可优化的where子句

1.例:下列SQL条件语句中的列都建有恰当的索引。但运行速度却很慢:


select * from record where substring(card_no,1,4)='5378'(13秒)

select * from record where amount/30< 1000(11秒)

select * from record where convert(char(10),date,112)='19991201'(10秒)

分析:

where子句中对列的不论什么操作结果都是在SQL执行时逐列计算得到的,因此它不得不进行表搜索,而没有使用该列上面的索引;

假设这些结果在查询编译时就能得到。那么就能够被SQL优化器优化,使用索引,避免表搜索,因此将SQL重写成以下这样:


select * from record where card_no like'5378%'(< 1秒)

select * from record where amount< 1000*30(< 1秒)

select * from record where date= '1999/12/01'(< 1秒)

你会发现SQL明显快起来!

2.例:表stuff200000行,id_no上有非群集索引。请看以下这个SQL

select count(*) from stuff where id_no in('0','1')(23秒)

分析:---- where条件中的'in'在逻辑上相当于'or',所以语法分析器会将in ('0','1')转化为id_no ='0' or id_no='1'来运行。

我们期望它会依据每一个or子句分别查找。再将结果相加,这样能够利用id_no上的索引;

但实际上(依据showplan,它却採用了"OR策略",即先取出满足每一个or子句的行。存入暂时数据库的工作表中,再建立唯一索引以去掉反复行。最后从这个暂时表中计算结果。

因此,实际过程没有利用id_no上索引。而且完毕时间还要受tempdb数据库性能的影响。

实践证明,表的行数越多,工作表的性能就越差。当stuff620000行时。运行时间竟达到220秒!还不如将or子句分开:

select count(*) from stuff where id_no='0'
select count(*) from stuff where id_no='1'

得到两个结果,再作一次加法合算。由于每句都使用了索引,运行时间仅仅有3秒。在620000行下,时间也仅仅有4秒。

或者。用更好的方法。写一个简单的存储过程:

create proc count_stuff asdeclare @a intdeclare @b intdeclare @c intdeclare @d char(10)
begin
select @a=count(*) from stuff where id_no='0'
select @b=count(*) from stuff where id_no='1'
end
select @c=@a+@b
select @d=convert(char(10),@c)
print @d


直接算出结果,运行时间同上面一样快!

 

---- 总结:---- 可见,所谓优化即where子句利用了索引,不可优化即发生了表扫描或额外开销。


1.不论什么对列的操作都将导致表扫描,它包含数据库函数、计算表达式等等,查询时要尽可能将操作移至等号右边。

2.inor子句常会使用工作表。使索引失效;假设不产生大量反复值,能够考虑把子句拆开;拆开的子句中应该包括索引。

3.要善于使用存储过程,它使SQL变得更加灵活和高效。

从以上这些样例能够看出,SQL优化的实质就是在结果正确的前提下。用优化器能够识别的语句。充份利用索引,降低表扫描的I/O次数,尽量避免表搜索的发生。事实上SQL的性能优化是一个复杂的过程。上述这些仅仅是在应用层次的一种体现,深入研究还会涉及数据库层的资源配置、网络层的流量控制以及操作系统层的整体设计。


posted @ 2017-08-20 21:13  jzdwajue  阅读(262)  评论(0编辑  收藏  举报