单键索引仍是组合索引

摘自http://book.51cto.com/art/200906/132406.htmphp

 

8.4.5  索引的利弊与如何断定,是否须要索引html

 

相信读者都知道索引可以极大地提升数据检索的效率,让Query 执行得更快,可是可能并非每一位朋友都清楚索引在极大提升检索效率的同时,也给数据库带来了一些负面的影响。下面就分别对 MySQL 中索引的利与弊作一个简单的分析。mysql

 

索引的好处sql

 

索引带来的益处可能不少读者会认为只是"可以提升数据检索的效率,下降数据库的IO成本"。数据库

 

确实,在数据库中表的某个字段建立索引,所带来的最大益处就是将该字段做为检索条件时能够极大地提升检索效率,加快检索时间,下降检索过程当中需要读 取的数据量。可是索引带来的收益只是提升表数据的检索效率吗?固然不是,索引还有一个很是重要的用途,那就是下降数据的排序成本。

咱们知 道,每一个索引中的数据都是按照索引键键值进行排序后存放的,因此,当Query 语句中包含排序分组操做时,若是排序字段和索引键字段恰好一致,MySQL Query Optimizer 就会告诉 mysqld 在取得数据后不用排序了,由于根据索引取得的数据已经知足客户的排序要求。缓存

 

那若是是分组操做呢?分组操做没办法直接利用索引完成。可是分组操做是需要先进行排序而后分组的,因此当Query 语句中包含分组操做,并且分组字段也恰好和索引键字段一致,那么mysqld 一样能够利用索引已经排好序的这个特性,省略掉分组中的排序操做。并发

 

排序分组操做主要消耗的是内存和 CPU 资源,若是可以在进行排序分组操做中利用好索引,将会极大地下降CPU资源的消耗。oracle

 

索引的弊端memcached

 

索引的益处已经清楚了,可是咱们不能只看到这些益处,并认为索引是解决 Query 优化的圣经,只要发现 Query 运行不够快就将 WHERE 子句中的条件所有放在索引中。函数

 

确实,索引可以极大地提升数据检索效率,也可以改善排序分组操做的性能,但有不能忽略的一个问题就是索引是彻底独立于基础数据以外的一部分数据。假 设在Table ta 中的Column ca 建立了索引 idx_ta_ca,那么任何更新 Column ca 的操做,MySQL在更新表中 Column ca的同时,都需要更新Column ca 的索引数据,调整由于更新带来键值变化的索引信息。而若是没有对 Column ca 进行索引,MySQL要作的仅仅是更新表中 Column ca 的信息。这样,最明显的资源消耗就是增长了更新所带来的 IO 量和调整索引所致的计算量。此外,Column ca 的索引idx_ta_ca需要占用存储空间,并且随着 Table ta 数据量的增长,idx_ta_ca 所占用的空间也会不断增长,因此索引还会带来存储空间资源消耗的增长。

 

如何断定是否需要建立索引

 

在了解了索引的利与弊以后,那咱们到底该如何来判断某个索引是否应该建立呢?

 

实际上,并无一个很是明确的定律能够清晰地定义什么字段应该建立索引,什么字段不应建立索引。由于应用场景实在是太复杂,存在太多的差别。固然,仍是仍然可以找到几点基本的断定策略来帮助分析的。

 

1. 较频繁的做为查询条件的字段应该建立索引

 

提升数据查询检索的效率最有效的办法就是减小需要访问的数据量,从上面索引的益处中咱们知道,索引正是减小经过索引键字段做为查询条件的 Query 的IO量之最有效手段。因此通常来讲应该为较为频繁的查询条件字段建立索引。

 

2. 惟一性太差的字段不适合单首创建索引,即便频繁做为查询条件

 

惟一性太差的字段主要是指哪些呢?如状态字段、类型字段等这些字段中存放的数据可能总共就是那么几个或几十个值重复使用,每一个值都会存在于成千上万 或更多的记录中。对于这类字段,彻底没有必要建立单独的索引。由于即便建立了索引,MySQL Query Optimizer 大多数时候也不会去选择使用,若是何时 MySQL Query Optimizer选择了这种索引,那么很是遗憾地告诉你,这可能会带来极大的性能问题。因为索引字段中每一个值都含有大量的记录,那么存储引擎在根据索引 访问数据的时候会带来大量的随机IO,甚至有些时候还会出现大量的重复IO。

 

这主要是因为数据基于索引扫描的特色引发的。当咱们经过索引访问表中数据时,MySQL 会按照索引键的键值顺序来依序访问。通常来讲,每一个数据页中大都会存放多条记录,可是这些记录可能大多数都不会和你所使用的索引键的键值顺序一致。

 

假若有如下场景,咱们经过索引查找键值为A和B的某些数据。在经过A键值找到第一条知足要求的记录后,会读取这条记录所在的 X 数据页,而后继续往下查找索引,发现 A 键值所对应的另一条记录也知足要求,可是这条记录不在 X 数据页上,而在Y数据页上,这时候存储引擎就会丢弃X数据页,而读取Y数据页。如此继续一直到查找完A键值所对应的全部记录。而后轮到B键值了,这时发现 正在查找的记录又在X数据页上,可以前读取的 X 数据页已经被丢弃了,只能再次读取 X 数据页。这时候,实际上已经重复读取 X 数据页两次了。在继续日后的查找中,可能还会出现一次又一次的重复读取,这无疑给存储引擎极大地增长了IO访问量。

 

不只如此,若是一个键值对应了太多的数据记录,也就是说经过该键值会返回占整个表比例很大的记录时,因为根据索引扫描产生的都是随机 IO,其效率比进行全表扫描的顺序IO效率低不少,即便不会出现重复 IO 的读取,一样会形成总体 IO 性能的降低。

 

不少比较有经验的 Query 调优专家常常说,当一条Query返回的数据超过了全表的 15%时,就不该该再使用索引扫描来完成这个 Query 了。对于"15%"这个数字咱们并不能断定是否很准确,可是至少侧面证实了惟一性太差的字段并不适合建立索引。

 

3. 更新很是频繁的字段不适合建立索引

 

上面在索引的弊端中已经分析过了,索引中的字段被更新的时候,不只要更新表中的数据,还要更新索引数据,以确保索引信息是准确的。这个问题导致IO 访问量较大增长,不只仅影响了更新 Query 的响应时间,还影响了整个存储系统的资源消耗,加大了整个存储系统的负载。

 

固然,并非存在更新的字段就适合建立索引,从断定策略的用语上也能够看出,是"很是频繁"的字段。到底什么样的更新频率应该算是"很是频繁"呢? 每秒?每分钟?仍是每小时呢?说实话,还真难定义。不少时候是经过比较同一时间段内被更新的次数和利用该字段做为条件的查询次数来判断的,若是经过该字段 的查询并非不少,可能几个小时或是更长才会执行一次,更新反而比查询更频繁,那这样的字段确定不适合建立索引。反之,若是咱们经过该字段的查询比较频 繁,但更新并非特别多,好比查询几十次或更多才可能会产生一次更新,那我我的以为更新所带来的附加成本也是能够接受的。

 

4. 不会出如今 WHERE 子句中的字段不应建立索引

不会还有人会问为何吧?本身也以为这是废话了,哈哈!

8.4.6  单键索引仍是组合索引

 

在大概了解了MySQL 各类类型的索引,以及索引自己的利弊与判断一个字段是否需要建立索引以后,就要着手建立索引来优化Query 了。在不少时候,WHERE 子句中的过滤条件并不仅是针对于单一的某个字段,常常会有多个字段一块儿做为查询过滤条件存在于 WHERE 子句中。在这种时候,就必需要判断是该仅仅为过滤性最好的字段创建索引,仍是该在全部字段(过滤条件中的)上创建一个组合索引。

 

对于这种问题,很难有一个绝对的定论,需要从多方面来分析考虑,平衡两种方案各自的优劣,而后选择一种最佳的方案。由于从上一节中已了解到索引在提 高某些查询的性能同时,也会让某些更新的效率降低。而组合索引中由于有多个字段存在,理论上被更新的可能性确定比单键索引要大不少,这样带来的附加成本也 就比单键索引要高。可是,当WHERE 子句中的查询条件含有多个字段时,经过这多个字段共同组成的组合索引的查询效率确定比只用过滤条件中的某一个字段建立的索引要高。由于经过单键索引过滤的 数据并不完整,和组合索引相比,存储引擎需要访问更多的记录数,天然就会访问更多的数据量,也就是说须要更高的 IO 成本。

 

可能有朋友会说,那能够建立多个单键索引啊。确实能够将 WHERE 子句中的每个字段都建立一个单键索引。可是这样真的有效吗?在这样的状况下,MySQL Query Optimizer 大多数时候都只会选择其中的一个索引,而后放弃其余的索引。即便他选择了同时利用两个或更多的索引经过 INDEX_MERGE 来优化查询,所收到的效果可能并不会比选择其中某一个单键索引更高效。由于若是选择经过 INDEX_MERGE 来优化查询,就需要访问多个索引,同时还要将几个索引进行 merge 操做,这带来的成本可能反而会比选择其中一个最有效的索引更高。

 

在通常的应用场景中,只要不是其中某个过滤字段在大多数场景下能过滤90%以上的数据,而其余的过滤字段会频繁的更新,通常更倾向于建立组合索引, 尤为是在并发量较高的场景下。由于当并发量较高的时候,即便只为每一个Query节省了不多的 IO 消耗,但由于执行量很是大,所节省的资源总量仍然是很是可观的。

 

固然,建立组合索引并非说就需要将查询条件中的全部字段都放在一个索引中,还应该尽可能让一个索引被多个 Query 语句利用,尽可能减小同一个表上的索引数量,减小由于数据更新带来的索引更新成本,同时还能够减小由于索引所消耗的存储空间。

此外,MySQL 还提供了另一个优化索引的功能,那就是前缀索引。在 MySQL 中,能够仅仅使用某个字段的前面部份内容作为索引键索引该字段,以达到减少索引占用的存储空间和提升索引访问效率的目的。固然,前缀索引的功能仅仅适用于 字段前缀随机重复性很小的字段。若是需要索引的字段前缀内容有较多的重复,索引的过滤性天然也会随之下降,经过索引所访问的数据量就会增长,这时候前缀索 引虽然可以减小存储空间消耗,可是可能会形成 Query 访问效率的极大下降,得不偿失。

 

 

摘自http://www.canphp.com/article/show-130.html

 

复合索引优化

两个或更多个列上的索引被称做复合索引。
利用索引中的附加列,您能够缩小搜索的范围,但使用一个具备两列的索引不一样于使用两个单独的索引。复合索引的结构与电话簿相似,人名由姓和名构成,电话簿 首先按姓氏对进行排序,而后按名字对有相同姓氏的人进行排序。若是您知道姓,电话簿将很是有用;若是您知道姓和名,电话簿则更为有用,但若是您只知道名不 姓,电话簿将没有用处。
因此说建立复合索引时,应该仔细考虑列的顺序。对索引中的全部列执行搜索或仅对前几列执行搜索时,复合索引很是有用;仅对后面的任意列执行搜索时,复合索引则没有用处。
如:创建 姓名、年龄、性别的复合索引。



复合索引的创建原则:

若是您极可能仅对一个列屡次执行搜索,则该列应该是复合索引中的第一列。若是您极可能对一个两列索引中的两个列执行单独的搜索,则应该建立另外一个仅包含第二列的索引。
如上图所示,若是查询中须要对年龄和性别作查询,则应当再新建一个包含年龄和性别的复合索引。
包含多个列的主键始终会自动以复合索引的形式建立索引,其列的顺序是它们在表定义中出现的顺序,而不是在主键定义中指定的顺序。在考虑未来经过主键执行的搜索,肯定哪一列应该排在最前面。
请注意,建立复合索引应当包含少数几个列,而且这些列常常在select查询里使用。在复合索引里包含太多的列不只不会给带来太多好处。并且因为使用至关多的内存来存储复合索引的列的值,其后果是内存溢出和性能下降。

        
复合索引对排序的优化:

复合索引只对和索引中排序相同或相反的order by 语句优化。
在建立复合索引时,每一列都定义了升序或者是降序。如定义一个复合索引:

Sql代码  
  1. CREATE INDEX idx_example    
  2. ON table1 (col1 ASC, col2 DESC, col3 ASC)  
 
其中 有三列分别是:col1 升序,col2 降序, col3 升序。如今若是咱们执行两个查询
1: Select col1, col2, col3 from table1 order by col1 ASC, col2 DESC, col3 ASC
  和索引顺序相同
2: Select col1, col2, col3 from table1 order by col1 DESC, col2 ASC, col3 DESC
 和索引顺序相反
查询1,2 均可以别复合索引优化。
若是查询为:
Select col1, col2, col3 from table1 order by col1 ASC, col2 ASC, col3 ASC
  排序结果和索引彻底不一样时,此时的查询不会被复合索引优化。


查询优化器在在where查询中的做用:

若是一个多列索引存在于 列 Col1 和 Col2 上,则如下语句:Select   * from table where   col1=val1 AND col2=val2 查询优化器会试图经过决定哪一个索引将找到更少的行。以后用获得的索引去取值。
1. 若是存在一个多列索引,任何最左面的索引前缀能被优化器使用。因此联合索引的顺序不一样,影响索引的选择,尽可能将值少的放在前面。
如:一个多列索引为 (col1 ,col2, col3)
    那么在索引在列 (col1) 、(col1 col2) 、(col1 col2 col3) 的搜索会有做用。

 

Sql代码 
  1. SELECT * FROM tb WHERE  col1 = val1   
  2. SELECT * FROM tb WHERE  col1 = val1 and col2 = val2   
  3. SELECT * FROM tb WHERE  col1 = val1 and col2 = val2  AND col3 = val3  
 

2. 若是列不构成索引的最左面前缀,则创建的索引将不起做用。
如:
Sql代码 
  1. SELECT * FROM  tb WHERE  col3 = val3   
  2. SELECT * FROM  tb  WHERE  col2 = val2   
  3. SELECT * FROM  tb  WHERE  col2 = val2  and  col3=val3  
 
3. 若是一个 Like 语句的查询条件不以通配符起始则使用索引。
如:%车 或 %车%   不使用索引。
    车%              使用索引。
索引的缺点:
1.       占用磁盘空间。
2.       增长了插入和删除的操做时间。一个表拥有的索引越多,插入和删除的速度越慢。如 要求快速录入的系统不宜建过多索引。

下面是一些常见的索引限制问题

一、使用不等于操做符(<>, !=)
下面这种状况,即便在列dept_id有一个索引,查询语句仍然执行一次全表扫描
select * from dept where staff_num <> 1000;
可是开发中的确须要这样的查询,难道没有解决问题的办法了吗?
有!
经过把用 or 语法替代不等号进行查询,就可使用索引,以免全表扫描:上面的语句改为下面这样的,就可使用索引了。
Sql代码 
  1. select * from dept shere staff_num < 1000 or dept_id > 1000;  
 

二、使用 is null 或 is not null
使用 is null 或is nuo null也会限制索引的使用,由于数据库并无定义null值。若是被索引的列中有不少null,就不会使用这个索引(除非索引是一个位图索引,关于位图 索引,会在之后的blog文章里作详细解释)。在sql语句中使用null会形成不少麻烦。
解决这个问题的办法就是:建表时把须要索引的列定义为非空(not null)

三、使用函数
若是没有使用基于函数的索引,那么where子句中对存在索引的列使用函数时,会使优化器忽略掉这些索引。下面的查询就不会使用索引:
Sql代码 
  1. select * from staff where trunc(birthdate) = '01-MAY-82';  
 
可是把函数应用在条件上,索引是能够生效的,把上面的语句改为下面的语句,就能够经过索引进行查找。
Sql代码 
  1. select * from staff where birthdate < (to_date('01-MAY-82') + 0.9999);  
 

四、比较不匹配的数据类型
比较不匹配的数据类型也是难于发现的性能问题之一。
下面的例子中,dept_id是一个varchar2型的字段,在这个字段上有索引,可是下面的语句会执行全表扫描。
Sql代码 
  1. select * from dept where dept_id = 900198;  
 
这是由于oracle会自动把where子句转换成to_number(dept_id)=900198,就是3所说的状况,这样就限制了索引的使用。
把SQL语句改成以下形式就可使用索引
Sql代码 
  1. select * from dept where dept_id = '900198';  
 

恩,这里还有要注意的:

来自老王的博客(http://hi.baidu.com/thinkinginlamp/blog/item/9940728be3986015c8fc7a85.html)

比方说有一个文章表,咱们要实现某个类别下按时间倒序列表显示功能:

SELECT * FROM articles WHERE category_id = ... ORDER BY created DESC LIMIT ...

这样的查询很常见,基本上无论什么应用里都能找出一大把相似的SQL来,学院派的读者看到上面的SQL,可能会说SELECT *很差,应该仅仅查询须要的字段,那咱们就索性完全点,把SQL改为以下的形式:

SELECT id FROM articles WHERE category_id = ... ORDER BY created DESC LIMIT ...
 

咱们假设这里的id是主键,至于文章的具体内容,能够都保存到memcached之类的键值类型的缓存里,如此一来,学院派的读者们应该挑不出什么毛病来了,下面咱们就按这条SQL来考虑如何创建索引:

不考虑数据分布之类的特殊状况,任何一个合格的WEB开发人员都知道相似这样的SQL,应该创建一个”category_id, created“复合索引,但这是最佳答案不?不见得,如今是回头看看标题的时候了:MySQL里创建索引应该考虑数据库引擎的类型!

若是咱们的数据库引擎是InnoDB,那么创建”category_id, created“复合索引是最佳答案。让咱们看看InnoDB的索引结构,在InnoDB里,索引结构有一个特殊的地方:非主键索引在其BTree的叶节 点上会额外保存对应主键的值,这样作一个最直接的好处就是Covering Index,不用再到数据文件里去取id的值,能够直接在索引里获得它。

若是咱们的数据库引擎是MyISAM,那么创建"category_id, created"复合索引就不是最佳答案。由于MyISAM的索引结构里,非主键索引并无额外保存对应主键的值,此时若是想利用上Covering Index,应该创建"category_id, created, id"复合索引。

唠完了,应该明白个人意思了吧。但愿之后你们在考虑索引的时候能思考的更全面一点,实际应用中还有不少相似的问题,好比说多数人在创建索引的时候不从 Cardinality(SHOW INDEX FROM ...能看到此参数)的角度看是否合适的问题,Cardinality表示惟一值的个数,通常来讲,若是惟一值个数在总行数中所占比例小于20%的话,则 能够认为Cardinality过小,此时索引除了拖慢insert/update/delete的速度以外,不会对select产生太大做用;还有一个 细节是创建索引的时候未考虑字符集的影响,好比说username字段,若是仅仅容许英文,下划线之类的符号,那么就不要用gbk,utf-8之类的字符 集,而应该使用latin1或者ascii这种简单的字符集,索引文件会小不少,速度天然就会快不少。这些细节问题须要读者本身多注意,我就很少说了。
 摘自http://blog.chinaunix.net/uid-7692530-id-2567605.html
  1. 对于具备2个用and链接条件的语句,且2个列之间的关联度较低的状况下,multi col index有必定优点。
  2. 对于具备2个用and链接条件的语句,且2个列之间的关联度较高的状况下,multi col index有很大优点。
  3. 对于具备2个用or链接条件的语句,isolate col index有必定优点,由于这种状况下multi col index 将会致使全表扫描,而前者能够用到index merge的优化。