SQL优化:索引优化

SQL索引

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

  1.1 什么是索引?web

  SQL索引有两种,汇集索引和非汇集索引,索引主要目的是提升了SQL Server系统的性能,加快数据的查询速度与减小系统的响应时间 sql

下面举两个简单的例子:数据库

        图书馆的例子:一个图书馆那么多书,怎么管理呢?创建一个字母开头的目录,例如:a开头的书,在第一排,b开头的在第二排,这样在找什么书就好说了,这个就是一个汇集索引,但是不少人借书找某某做者的,不知道书名怎么办?图书管理员在写一个目录,某某做者的书分别在第几排,第几排,这就是一个非汇集索引api

        字典的例子:字典前面的目录,能够按照拼音和部首去查询,咱们想查询一个字,只须要根据拼音或者部首去查询,就能够快速的定位到这个汉字了,这个就是索引的好处,拼音查询法就是汇集索引,部首查询就是一个非汇集索引.性能优化

    看了上面的例子,下面的一句话你们就很容易理解了:网络

  1. 汇集索引存储记录是物理上连续存在,而非汇集索引是逻辑上的连续,物理存储并不连续。
  2. 就像字段,汇集索引是连续的,a后面确定是b,非汇集索引就不连续了,就像图书馆的某个做者的书,有可能在第1个货架上和第10个货架上。
  3. 还有一个小知识点就是:汇集索引一个表只能有一个,而非汇集索引一个表能够存在多个。

 

   1.2 索引的存储机制app

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

       汇集索引和非汇集索引的根本区别表记录的排列顺序和与索引的排列顺序是否一致,其实理解起来很是简单,仍是举字典的例子:若是按照拼音查询,那么都是从a-z的,是具备连续性的,a后面就是b,b后面就是c, 汇集索引就是这样的,他是和表的物理排列顺序是同样的,例若有id为汇集索引,那么1后面确定是2,2后面确定是3,因此说这样的搜索顺序的就是汇集索引。ide

        非汇集索引就和按照部首查询是同样是,可能按照偏房查询的时候,根据偏旁‘弓’字旁,索引出两个汉字,张和弘,可是这两个其实一个在100页,一个在1000页,(这里只是举个例子),他们的索引顺序和数据库表的排列顺序是不同的,这个样的就是非汇集索引

      原理明白了,那他们是怎么存储的呢?在这里简单的说一下,汇集索引就是在数据库被开辟一个物理空间存放他的排列的值,例如1-100,因此当插入数据时,他会从新排列整个整个物理空间,而非汇集索引其实能够看做是一个含有汇集索引的表,他只仅包含原表中非汇集索引的列和指向实际物理表的指针。他只记录一个指针,其实就有点和堆栈差很少的感受了

 

  1.3 什么状况下设置索引 

动做描述

使用汇集索引 

 使用非汇集索引

 外键列

 应

 应

 主键列

 应

 应

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

 应

 应

 返回某范围内的数据

 应

 不该

 小数目的不一样值

 应

 不该

 大数目的不一样值

 不该

 应

 频繁更新的列

不该 

 应

 频繁修改索引列

 不该

 应

 一个或极少不一样值

 不该

 不该

 

  • 创建索引的原则:

1) 定义主键的数据列必定要创建索引。

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

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

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

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

6) 常常出如今关键字order by、group by、distinct后面的字段,创建索引。若是创建的是复合索引,索引的字段顺序要和这些关键字后面的字段顺序一致,不然索引不会被使用。

7) 对于那些查询中不多涉及的列,重复值比较多的列不要创建索引。

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

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

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

10) 对复合索引,按照字段在查询条件中出现的频度创建索引。在复合索引中,记录首先按照第一个字段排序。对于在第一个字段上取值相同的记录,系统再按照第二个字段的取值排序,以此类推。所以只有复合索引的第一个字段出如今查询条件中,该索引才可能被使用,所以将应用频度高的字段,放置在复合索引的前面,会使系统最大可能地使用此索引,发挥索引的做用。

 

  • 索引的不足之处

上面都在说使用索引的好处,但过多的使用索引将会形成滥用。所以索引也会有它的缺点:

  1. 虽然索引大大提升了查询速度,同时却会下降更新表的速度,如对表进行INSERT、UPDATE和DELETE。由于更新表时,MySQL不只要保存数据,还要保存一下索引文件。
  2. 创建索引会占用磁盘空间的索引文件。通常状况这个问题不太严重,但若是你在一个大表上建立了多种组合索引,索引文件的会膨胀很快。索引只是提升效率的一个因素,若是你的MySQL有大数据量的表,就须要花时间研究创建最优秀的索引,或优化查询语句。


使用索引时,有如下一些技巧和注意事项:

  • 索引不会包含有NULL值的列

只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。因此咱们在数据库设计时不要让字段的默认值为NULL。

  • 使用短索引

对串列进行索引,若是可能应该指定一个前缀长度。例如,若是有一个CHAR(255)的列,若是在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。短索引不只能够提升查询速度并且能够节省磁盘空间和I/O操做。

  • 索引列排序

MySQL查询只使用一个索引,所以若是where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。所以数据库默认排序能够符合要求的状况下不要使用排序操做尽可能不要包含多个列的排序,若是须要最好给这些列建立复合索引

  • like语句操做

通常状况下不鼓励使用like操做,若是非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可使用索引

  • 不要在列上进行运算

将在每一个行上进行运算,这将致使索引失效而进行全表扫描,所以咱们能够改为

1
select * from users where adddate<‘2007-01-01’;  


  • 不使用NOT IN和<>操做


1.4 如何建立索引

  1.41 建立索引的语法:

1.42 删除索引语法:

 
1
DROP INDEX table_name.index_name[,table_name.index_name]
2
3
说明:table_name: 索引所在的表名称。
4
5
index_name : 要删除的索引名称。

1.43 显示索引信息:

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

执行代码以下:

 1.44查询索引(都可) 

 
1
show index from table_name;
2
show keys from table_name;
3
desc table_Name;

  1.44组合索引

不少时候,咱们在mysql中建立了索引,可是某些查询仍是很慢,根本就没有使用到索引!通常来讲,多是某些字段没有建立索引,或者是组合索引中字段的顺序与查询语句中字段的顺序不符。

看下面的例子:
假设有一张订单表(orders),包含order_id和product_id二个字段。
一共有31条数据。符合下面语句的数据有5条。执行下面的sql语句:

这条语句要mysql去根据order_id进行搜索,而后返回匹配记录中的product_id。因此组合索引应该按照如下的顺序建立:

 
1
create index orderid_productid on orders(order_id, product_id)
2
mysql> explain select product_id from orders where order_id in (123, 312, 223, 132, 224) \G
3
*************************** 1. row ***************************
4
           id: 1
5
  select_type: SIMPLE
6
        table: orders
7
         type: range
8
possible_keys: orderid_productid
9
          key: orderid_productid
10
      key_len: 5
11
          ref: NULL
12
         rows: 5
13
        Extra: Using where; Using index
14
1 row in set (0.00 sec)
能够看到,这个组合索引被用到了,扫描的范围也很小,只有5行。若是把组合索引的顺序换成product_id, order_id的话,mysql就会去索引中搜索 *123 *312 *223 *132 *224,必然会有些慢了。
此次索引搜索的性能显然不能和上次相比了。rows:31,个人表中一共就31条数据。索引被使用部分的长度:key_len:10,比上一次的key_len:5多了一倍。不知道是这样在索引里面查找速度快,仍是直接去全表扫描更快呢?
 
1
mysql> alter table orders add modify_a char(255) default 'aaa';
2
Query OK, 31 rows affected (0.01 sec)
3
Records: 31  Duplicates: 0  Warnings: 0
4
 
5
mysql>
6
mysql>
7
mysql> explain select modify_a from orders where order_id in (123, 312, 223, 132, 224) \G         
8
*************************** 1. row ***************************
9
           id: 1
10
  select_type: SIMPLE
11
        table: orders
12
         type: ALL
13
possible_keys: NULL
14
          key: NULL
15
      key_len: NULL
16
          ref: NULL
17
         rows: 31
18
        Extra: Using where
19
1 row in set (0.00 sec)
这样就不会用到索引了。 刚才是由于select的product_id与where中的order_id都在索引里面的。


为何要建立组合索引呢?这么简单的状况直接建立一个order_id的索引不就好了吗?果只有一个order_id索引,没什么问题,会用到这个索引,而后mysql要去磁盘上的表里面取到product_id。若是有组合索引的话,mysql能够彻底从索引中取到product_id,速度天然会快。再多说几句组合索引的最左优先原则:
组合索引的第一个字段必须出如今查询组句中,这个索引才会被用到。果有一个组合索引(col_a,col_b,col_c),下面的状况都会用到这个索引:

对于最后一条语句,mysql会自动优化成第三条的样子~~。下面的状况就不会用到索引:
 
1
col_b = "aaaaaa";
2
col_b = "aaaa" and col_c = "cccccc";

经过实例理解单列索引、多列索引以及最左前缀原则。实例:如今咱们想查出知足如下条件的用户id:


由于咱们不想扫描整表,故考虑用索引。


1.单列索引:

 
1
ALTER TABLE people ADD INDEX lname (lname);


将lname列建索引,这样就把范围限制在lname='Liu'的结果集1上,以后扫描结果集1,产生知足fname='Zhiqun'的结果集2,再扫描结果集2,找到 age=26的结果集3,即最终结果。

由 于创建了lname列的索引,与执行表的彻底扫描相比,效率提升了不少,但咱们要求扫描的记录数量仍旧远远超过了实际所需 要的。虽然咱们能够删除lname列上的索引,再建立fname或者age 列的索引,可是,不论在哪一个列上建立索引搜索效率仍旧类似。

2.多列索引:


为了提升搜索效率,咱们须要考虑运用多列索引,因为索引文件以B-Tree格式保存,因此咱们不用扫描任何记录,便可获得最终结果。

注:在mysql中执行查询时,只能使用一个索引,若是咱们在lname,fname,age上分别建索引,执行查询时,只能使用一个索引,mysql会选择一个最严格(得到结果集记录数最少)的索引

3.最左前缀:顾名思义,就是最左优先,上例中咱们建立了lname_fname_age多列索引,至关于建立了(lname)单列索引,(lname,fname)组合索引以及(lname,fname,age)组合索引。

注:在建立多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边

创建索引的时机

到这里咱们已经学会了创建索引,那么咱们须要在什么状况下创建索引呢?通常来讲,在WHERE和JOIN中出现的列须要创建索引,但也不彻底如此,由于MySQL只对<,<=,=,>,>=,BETWEEN,IN,以及某些时候的LIKE才会使用索引。例如:

 
1
SELECT t.Name FROM mytable t LEFT JOIN mytable m ON t.Name=m.username WHERE m.age=20 AND m.city='郑州'

此时就须要对city和age创建索引,因为mytable表的userame也出如今了JOIN子句中,也有对它创建索引的必要。

刚才提到只有某些时候的LIKE才需创建索引。由于在以通配符%和_开头做查询时,MySQL不会使用索引。例以下句会使用索引:

下句就不会使用:

 
1
SELECT * FROM mytable WHEREt Name like'%admin'

所以,在使用LIKE时应注意以上的区别。



1.5 索引实战(摘抄)

人们在使用SQL时每每会陷入一个误区,即太关注于所得的结果是否正确,而忽略了不一样的实现方法之间可能存在的性能差别,

这种性能差别在大型的或是复杂的数据库环境中(如联机事务处理OLTP或决策支持系统DSS)中表现得尤其明显。

笔者在工做实践中发现,不良的SQL每每来自于不恰当的索引设计不充份的链接条件不可优化的where子句

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

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

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

 

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

例:表record620000行,试看在不一样的索引下,下面几个 SQL的运行状况:

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

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

 
1
select count(*) from record where date >'19991201' and date < '19991214' and amount >2000 (14秒)
2
3
select date,sum(amount) from record group by date(28秒)
4
5
select count(*) from record where date >'19990901' and place in ('BJ','SH')(14秒)
6
7
---- 分析:---- 在群集索引下,数据在物理上按顺序在数据页上,重复值也排列在一块儿,于是在范围查找时,能够先找到这个范围的起末点,且只在这个范围内扫描数据页,避免了大范围扫描,提升了查询速度。

---- 3.placedateamount上的组合索引

---- 4.dateplaceamount上的组合索引

 
1
select count(*) from record where date >'19991201' and date < '19991214' and amount >2000(< 1秒)
2
3
select date,sum(amount) from record group by date(11秒)
4
5
select count(*) from record where date >'19990901' and place in ('BJ','SH')(< 1秒)
6
7
---- 分析:---- 这是一个合理的组合索引。它将date做为前导列,使每一个SQL均可以利用索引,而且在第一和第三个SQL中造成了索引覆盖,于是性能达到了最优。

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

缺省状况下创建的索引是非群集索引,但有时它并非最佳的;合理的索引设计要创建在对各类查询的分析和预测上。

通常来讲:

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

.常常同时存取多列,且每列都含有重复值可考虑创建组合索引;

.组合索引要尽可能使关键查询造成索引覆盖,其前导列必定是使用最频繁的列。

 

2、不充份的链接条件:

例:表card7896行,在card_no上有一个非汇集索引,表account191122行,在account_no上有一个非汇集索引,试看在不一样的表链接条件下,两个SQL的执行状况:

---- 分析:---- 

  • 在第一个链接条件下,最佳查询方案是将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)

 

3、不可优化的where子句

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

 
1
select * from record where substring(card_no,1,4)='5378'(13秒)
2
3
select * from record where amount/30< 1000(11秒)
4
5
select * from record where convert(char(10),date,112)='19991201'(10秒)
6
7
分析:
8
9
where子句中对列的任何操做结果都是在SQL运行时逐列计算获得的,所以它不得不进行表搜索,而没有使用该列上面的索引;
10
11
若是这些结果在查询编译时就能获得,那么就能够被SQL优化器优化,使用索引,避免表搜索,所以将SQL重写成下面这样:
12
13
select * from record where card_no like'5378%'(< 1秒)
14
15
select * from record where amount< 1000*30(< 1秒)
16
17
select * from record where date= '1999/12/01'(< 1秒)

你会发现SQL明显快起来!

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

 
1
select count(*) from stuff where id_no in('0','1')(23秒)
2
3
分析:----
4
5
where条件中的'in'在逻辑上至关于'or',因此语法分析器会将in ('0','1')转化为id_no ='0' or id_no='1'来执行。
6
7
咱们指望它会根据每一个or子句分别查找,再将结果相加,这样能够利用id_no上的索引;
8
9
但实际上(根据showplan),它却采用了"OR策略",即先取出知足每一个or子句的行,存入临时数据库的工做表中,再创建惟一索引以去掉重复行,最后从这个临时表中计算结果。所以,实际过程没有利用id_no上索引,而且完成时间还要受tempdb数据库性能的影响。
10
11
实践证实,表的行数越多,工做表的性能就越差,当stuff有620000行时,执行时间竟达到220秒!还不如将or子句分开:
12
13
select count(*) from stuff where id_no='0' select count(*) from stuff where id_no='1'
14
15
获得两个结果,再做一次加法合算。由于每句都使用了索引,执行时间只有3秒,在620000行下,时间也只有4秒。
16
17
或者,用更好的方法,写一个简单的存储过程:
18
19
create proc count_stuff asdeclare @a intdeclare @b intdeclare @c intdeclare @d char(10)beginselect @a=count(*) from stuff where id_no='0'select @b=count(*) from stuff where id_no='1'endselect @c=@a+@bselect @d=convert(char(10),@c)print @d
20
21
直接算出结果,执行时间同上面同样快!

 

---- 总结:---- 

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

1.任何对列的操做都将致使表扫描,它包括数据库函数、计算表达式等等,查询时要尽量将操做移至等号右边

2.inor子句常会使用工做表,使索引失效;若是不产生大量重复值,能够考虑把子句拆开;拆开的子句中应该包含索引。

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

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


部分引用地址:http://blog.csdn.net/gprime/article/details/1687930