数据库的索引详解

索引的重要性:当你的数据库的性能出现问题了,那么就重新优化你的索引吧,这能够解决80%的性能问题,由此可见索引的重要性,尤其在数据量越来越大的时候,影响更加的明显,一个最优的索引能够轻易的将查询性能提高好几个数量级

索引的作用和优点:
1. 能够大大的提高数据的查询检索速度
2. 通过创建唯一性索引可以保证数据库中每一行的唯一性
3. 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
4. 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
5. 通过使用索引,可以在查询的过程中,使用查询优化器,提高系统的性能。
索引的弊端和缺点:
1. 索引会占用一部分存储空间,尤其在数据量很大的时候占用的存储空间可是很客观的;
2. 一旦对数据进行了插入、删除、修改等操作,要对索引进行动态的维护。

但是总的来说利还是大于弊的,所以我们设计数据库的时候需要善于利用索引,善于优化索引。

索引是最好的提高查询解决方案吗?
答案: 不一定,只有当索引帮助存储引擎快速查找带来的好处大于其带来的额外开销时,索引才是有效的。
对于非常小的表,大部分情况下全表扫描更加有效
对于大中型的表,索引非常有效
对于特大型的表,建立和维护索引的代价特别大,索引就不是那么有效了,可以使用分区技术,来进行一组数据的查询(而不是一条一条的匹配),对于更大的TB级别的数据,经常会使用块级别的元数据技术来代替索引,例如Infobright

什么地方该用索引,什么地方应该避免使用索引?
使用索引:
1. 在数据量超过几百行之后就应该考虑建立索引,在主键上建立索引,保证数据的唯一性和组织表中的数据排列结构
2. 在经常使用到的查询列上建立索引,比如 where name = “wang” ,经常做这样的查询,那么name上就应该建立索引
3. 在经常进行范围查询的列上建立索引(可以建立聚簇索引,让索引的顺序和数据的物理存放顺序一致,这样大大的加快的查找的速度,变随机查找为顺序查找)
4. 在经常用在连接的列上,在连接字段列上建立索引,这些列主要是一些外键,可以加快连接的速度;
5. 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;
不该使用索引
1. 数据量太小不要建立索引,因为维护的代价要高于建立索引之后优化的代价
2. 经常频繁更新的列不要建立索引,因为一旦更新,就要对索引也要随之更新,如果更新的代价比查询的代价高,那就不要建立索引
3. 不经常被引用、查询的列不要建立索引,因为没有必要
4. 对于那些列的取值很少(比如性别),或者text等类型的大文本字段不要建立索引,大文本字段的索引也会很长,影响查询

使用索引注意事项:
1. 对于复合索引,要选择合适的顺序建立索引,因为对于mysql来说,建立了字段 (A,B,C)的复合索引,其实实质上是建立了 索引 A,B,C 和索引A, 所以当你单独用 B 和C去查询的时候,索引并没有用到.
在这里特别需要注意的是:索引的顺序非常重要!!!{
不过不是按照索引的最左列开始查找,则无法使用索引, B 和C 不行
不能跳过索引中的列, 利用A C查询无法使用索引
如果查询中有某个列的范围,则右边的列无法使用索引查询优化,
}
2. order by中的列是不会使用索引的, 对于索引列排序, MySQL查询只使用一个索引,因此如果where子句中的列已经使用了索引的话,在数据库默认排序可以符合要求的情况下不要使用排序操作,所以如果可以的话,在经常order by的列上建立索引
3. 一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引,而like “aaa%”可以使用索引。
4. 不要在列上进行计算,因为一旦出现了计算操作,将会导致该列不使用索引,而是直接全表扫描。
5. 一些sql语句会导致不使用索引,而进行全表扫描,应该避免,比如:
where 子句中不要对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描:select id from t where num is null (全表扫描) 修改成 select id from t where num is 0;
where 字句中不要使用!=或<>操作符,否则将导致引擎放弃使用索引而进行全表扫描
where 子句中不要使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,应该修改为使用 union 或者 union all (这两者的区别是 union 返回的是不重复的值,而union all 返回所有的每一列的值,包含重复元素 )
select id from t where num=10 or num=20 全表扫描
可以替换成:
select id from t where num=10
union all
select id from t where num=20
in 和 not in 也要慎用,否则会导致全表扫描 建议能用between替换,建议使用between, 另外用exists 替换也是很好的
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
注意的是 exists 实际上并不返回值,它只是检查exists 里面是否至少返回了一行数据,你可以理解为它返回一个true 或者false, 如果是那么where 语句就成立, 所以select 1你可以写成select 2,select id 都是没有任何问题的, 而in 确实会返回结果集的,所以in() 里面的字句必须返回的是num 字段集
在列上对字段进行表达式操作,计算操作,函数操作,和使用参数都会导致全表扫描

索引根据其构造结构可以分为 : B-tree索引, B+tree索引, Hash索引全文索引,空间数据索引(R-Tree),分行树索引(TokuDB中使用)
先介绍几种树的概念:
B树是二叉的排序树,也叫二叉查找树
红黑树是B树的变种,是平衡的二叉排序树
B-Tree 也是B树的改进,而不过二叉变成了多叉,而且非叶子节点中也存储了数据,搜索有可能到非叶结点就结束。
B+Tree 是B-tree的变种,所有的关键字都出现在叶子节点上,一次查找搜索必然是到叶子节点上才结束的。
B-tree 索引:
B-tree 是一种平衡的多叉排序树,,,B-Tree它的特点如下:(M代表着阶数,代表着一个节点最多有多少个孩子节点,例如M阶B树代表着该B树的节点的孩子节点最多有M个
1.定义任意非叶子结点最多只有M个儿子;且M>2;
2.根结点的儿子数为[2, M];
3.除根结点以外的非叶子结点的儿子数为[M/2, M];
4.每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
5 非叶子节点的关键字个数 = 指向儿子的指针个数 - 1
6. 非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树
7. 所有的叶子节点位于同一层

B-Tree的特性:
1. 关键字集合分布在整棵树中
2. 任何关键字出现且只出现在一个节点中
3. 搜索有可能在非叶子节点中结束
4. 搜索性能等价于做二分查找,做一次查找最大的次数为 h 次,h为B-tree的深度

在数据库中B-Tree索引的实现:
1. 根节点常驻内存
2. 根节点和非叶子节点的槽中存放了指向下一个子节点的指针,每个页中存放着一些关键字,与指针相对应,定义了子节点中值的上限与下限,存储引擎根据这些指针向下层查找, 但是叶子节点中只存放数据的物理地址,不再存放指针。
3. 将每一个节点设定为一个页的大小,这样只需要一次I/O就可以读取一个节点的内容,(这是因为页是计算机管理存储器的逻辑块,硬件和操作系统在进行内存和磁盘上的数据交换时往往以一个页作为基本单位)
4. 在叶节点和非叶子节点中,都存储了关键字(该关键字里包含了指向该索引数据本身的物理地址),在一次查找中,给定了某个关键字,如果在任何节点找到了该关键字(包括非叶结点)则就可以根据找到的关键字读取到该关键字所指向的实际数据。

B+tree 索引: 是B-Tree的变种
大部分的定义和B-Tree相同,但是它有独特于B-tree的地方,
1. 非叶结点的子树指针和关键字个数相同
2. 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间)
3. 所有的叶子节点增加了一个指针,指针指向相邻的叶子节点。
4. 所有的关键字都在叶子节点中出现

B+Tree的特点
1. 所有的关键字都出现在叶子节点中(稠密索引),而且叶子节点中的关键字恰好都是有序的
2. 不可能在非叶子节点查找成功,这是因为非叶子节点中存储的仍旧是索引,并没有存储实际的数据或者指向实际数据的物理地址,在叶子节点才存放的是实际的数据或者实际数据的物理地址
3. 查找方式有两种: 一种是从根节点进行查找,另一种是可以从叶子节点的开头开始查找,因为叶子节点中存储了指向下一个叶子节点的指针,而且在数据库的实现中,叶子节点在实际的物理存储中是顺序存放的,也就是叶子节点都是集中在一块存储区域内存放的(这样的好处是大大提高了区间查询效率)
4. 更适合文件系统

B+Tree 在 MySQL中的实现
MySQL 中 的 InnoDB和MyISAM存储引擎使用的就是B+Tree的实现方案
在InnoDB中,数据的物理存放顺序是按照设定聚簇索引的列的顺序进行组织的(实质上是按照主键的顺序组织数据),聚簇索引(一定包含主键,但也可能是主键和其他列的复合索引),对于InnoDB中来说,它的索引文件和数据文件是同一个文件,所以读取进来的页中(也就是一个节点)包含了key和data(key 为索引,data为实际的数据)
{
同时,对于聚簇索引来说, key = 索引键值;data = 实际数据本身
对于非聚簇索引,key = 索引键值, data = 聚簇索引的值
}
所以了解了InnoDB的索引实现就可以很容易的理解为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大另外使用自增字段作为主键则是一个很好的选择,因为非递增的索引会在插入删除数据时候,为了维持B+Tree的特性而频繁的进行调整,十分低效

对于MyISAM 存储引擎,按主键顺序储存数据,索引文件和数据文件是两个文件,会将索引文件读进内存,文件中的所有索引, key = 索引键值, data = 数据的物理地址 。

MyISAM 和InnoDB中的索引比较
1. MyisAM支持全文索引(FULLTEXT)、压缩索引,InnoDB不支持
2. MyisAM 按主键顺序储存数据,主键索引叶子节点保存对应数据行物理地址,辅助索引跟主键索引相差无几;InnoDB 按聚簇索引顺序存储数据,聚簇索引同时保存数据行实际数据,其他辅助索引保存的是聚簇索引的值
3. MyisAM键值分离,索引载入内存(key_buffer_size),数据缓存依赖操作系统; InnoDB键值一起保存,索引与数据一起载入InnoDB缓冲池; MyisAM主键(唯一)索引按升序来存储存储,InnoDB则不一定
4. MyisAM处理字符串索引时用增量保存的方式,如第一个索引是‘preform’,第二个是‘preformence’,则第二个保存是‘7,ance’,这个明显的好处是缩短索引,但是缺陷就是不支持倒序提取索引,必须顺序遍历获取索

B+Tree 和B-Tree的性能比较?
1. 相对来说 ,B-Tree键值只在索引中出现一次,比B+Tree能节省存储空间
2. B-Tree树的键值位置不定,使得在插入删除操作中复杂度明显增加,B+Tree的插入删除操作性能比B-Tree更好
3. B+Tree因为叶子节点的物理存放是集中在一块存储区域的而且按顺序存放,所以 B+Tree进行区间查询的速度会更加的快

先介绍一下局部性原理和磁盘预读:
局部性原理: 当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中
磁盘预读(目的就是要减少磁盘I/O,理论根据就是局部性原理): 由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。
由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。
预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行

为什么选用B+/-Tree,而不是用红黑树(平衡二叉树)?
上面介绍了在索引的实现中,是将每一个节点设置为一个页的大小来提高效率的,可知检索一次最多需要访问h个节点,需要h-1次磁盘I/O,O(h)=O(logdN)。一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小(通常不超过3)。
如果使用红黑树构件索引,那么树的深度H将会非常的大,O(h)=O(log2N),由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以红黑树的I/O需要h-1次,效率明显比B-Tree差很多。

B-Tree的插入与删除请观看博客
http://blog.csdn.net/hguisu/article/details/7786014

插入:首先在最底层的节点中添加一个关键字,如果添加该关键字之后,满足M阶B树的条件(该节点关键字个数不超过M-1),则完成添加,否则就要对B树进行调整,具体的调整策略请看上面链接的博客。
删除的时候,首先在最底层的节点删除那个关键字,删除之后,看是否满足M阶B树的条件(关键字数目不低于ceil(M/2) 向上取整),满足删除成功,不满足对树进行调整,调整策略请看上述博客。

Hash索引:
基于Hash表实现,只有精确匹配索引所有列的查询才会生效。
Hash索引的优势:
对于每一行,存储索引都会对所有的索引计算一个hash值,该值比较小,hash索引将每一行对应的hash值存储在hash索引中,也就是说hash表中存放了每一行的hash值,和该行的数据地址, 因此索引的结构非常紧凑,节省了存储空间,同时hash索引的查询速度非常的快
Hash索引的局限:
1. hash索引只包含哈希值和行指针,而不存储字段值,所以必须要实现读取行数据本身的操作
2. hash索引不能用来进行范围查询(like,< >等),只支持等值查询
3. hash索引也无法用来进行排序,因为它不是按照索引值的大小进行存储的
4. 对于复合索引,不支持部分索引列匹配查找,比如在A,B,C上建立索引(hash值为ABC的联合值),所以单独查询A无法使用Hash索引
5. 如何hash冲突很多的话,代价也比较大

全文索引
是一种特殊类型的索引,它查找的是文本中的关键词,而不是直接比较索引中的值,类似于搜索引擎做的事情。
MyISAM中的全文索引是一类特殊的B-Tree索引,共有两层,第一层是所有的关键字,对于每一个关键字的第二层包含的是一组相关的文档指针,全文索引不会索引文档中的所有词语,会根据相关规则过滤掉一些词语,而且也不会存储关键字具体匹配在哪一行。
因为MyISAM存储引擎的限制,多数情况下我们会选择InnoDB引擎 和 Sphinx插件来实现全文索引的功能。

按照功能来划分: 索引可以分为两大类:
主键索引,在主键上建立的索引,不允许空值null,每一行都是唯一的,一个表只有一个主键,主键可以作为外键
定义了主键,会自动的为主键创建索引;
唯一索引,索引的字段的值只能出现一次,可以为null,主键索引就是一种特殊的唯一索引,可以创建多个唯一索引
CREATE UNIQUE INDEX index_name
ON table_name (column_name)
普通索引(简单索引)”允许使用重复的值,允许创建多个简单索引
CREATE INDEX index_name
ON table_name (column_name);
复合索引,在多个列上建立的索引
CREATE INDEX index_name
ON table_name (column_name, column_name,….)
前缀索引: 单列的前10个字符创建前缀索引,前缀索引用于解决要索引一个很长的字符列的情况,诀窍是选择合适长的前缀来保证较高的选择性(利用前缀就可以精确定位到一行要查询的数据),又不会太长造成创建或者维护索引的巨大开销。MySQL中对于Blob,Text,很长的varchar的列必须使用前缀索引
CREATE INDEX index_name
ON table_name (column_name(10));
在创建表的时候创建主键索引,唯一索引,普通索引:
CREATE TABLE IF NOT EXISTS ‘User’ (
PRIMARY KEY (id),
UNIQUE KEY forumid (forumid),
KEY forumname (forumname),
KEY realname (realname)

聚簇索引(聚集索引)
CREATE CLUSTERED INDEX index_name
ON table_name (column_name);
非聚簇索引:除聚簇索引外的其他索引都是非聚簇索引。

下面详细的介绍一下聚簇索引。
聚簇索引实质上是一种数据的存储方式,它将数据行和相邻的索引键值紧凑的存储在一起,所以一个表只能有一个聚簇索引,因为不可能将数据存放在两个位置。数据的物理存放顺序和索引的存放顺序是一致的。
MySQL中InnoDB的聚簇索引的实现,对于InnoDB来说,该存储引擎默认以主键作为聚簇索引来组织存储数据,所以InnoDB无法显式的创建聚簇索引了。因此InnoDB如果没有定义主键,会选择一个唯一的非空索引代替,如果这也没有,会隐式的定义一个主键来作为聚簇索引。
聚簇索引的优点:
1. 把相关的数据保存在一起,减少了磁盘的I/O操纵
2. 数据的访问更加快捷,它将索引和数据保存在一起,因此只要找到了索引值就找到了数据,不需要再次去读取。
3. 使用覆盖索引扫描的查询可以直接使用节点中的主键值。
聚簇索引的缺点:
1. 聚簇索引的最大优势是减少了磁盘I/O和查询数据的时间,如果数据全部在内存,则优势很微弱了
2. 插入速度严重依赖于插入顺序,按照主键的插入顺序是加载数据到InnoDb表中速度最快的方式(而且主键最好设置为一个自增的字段,这样主键的值是顺序的,每次在插入的时候都能够插入到表的最后面,如果主键不是自增的,那么在插入的时候新的行不一定比最后的一行的键值大,所以总需要为新的行寻找合适的位置,需要额外工作,这也是为什么建议使用自增主键的原因),但是也会产生问题,对于高并发的工作环境中,在InnoDB中按照主键顺序插入到表中会造成明显的锁的争用,因为有可能所有的插入操作都集中在主键的上界点,导致插入所需的间隙锁的竞争,或者增加一行数据,所有的插入操作集中在主键的下界点。
3. 更新聚簇锁的代价很大,因为也要移动数据
4. 会导致全表扫描变慢,尤其是行很稀疏或者由于页分裂导致数据存放不连续的时候
5. 在InnoDB中,二级索引可能比想象中的更大,而且二级索引需要两次查询才能命中数据这是因为二级索引中存放的是主键的值,而不是指向行的物理地址。(二级索引之所以如此设计的原因是在InnoDB表中的数据进行了移动行或者数据也分裂时无需更新二级索引的这个指针)

覆盖索引:如果一个索引中已经包含(或者说覆盖了)所有要查询的字段的值,我们就称之为覆盖索引,这样就可以直接使用索引来直接获取列的数据,而不再需要读取数据行的回表查询了。善用覆盖索引能提高查询速度。

explain 解析器: 分析sql语句的查询性能情况,分析select的查询行为
关于这个解释器参数的详细说明,可以参考博客
http://blog.csdn.net/leileixiaoshan/article/details/26108427

我在这里只做一个简要的概括说明:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+————-+————–+——+—————+——+———+——+———+————-+
| 1 | SIMPLE | TradeBalance | ALL | NULL | NULL | NULL | NULL | 3418948 | Using where

id:表示查询语句中的查询顺序(select 语句中可能有子查询),id越大,说明越早执行

select_type: 每个select字句的查询类型, 简单或者复杂,
SIMPLE: 代表不包含子查询或者Union
PRIMARY,SUBQUERY 包含子查询,最外层的标记为PRIMARY ,子查询标记为SUBQUERY
UNION:第二个select语句出现在Union之后,标记为Union
DERIVED:在From列表中包含的子查询
UNION RESULT:从UNION表获取结果的SELECT

table: 查询的表的名字

type(这个属性很重要): 访问类型,表示MySQL在表中找到所需行的方式, 从上到下,性能由最差到最好
ALL: 全表查询,遍历全表找到匹配行‘
index:利用索引进行索引的遍历查询
range 索引范围扫描, 对索引的扫描开始于某一点,返回匹配的行,常见于between,<, >
ref: 非唯一性索引扫描, 返回匹配某个单独值的所有行, 常见于 =
eq_ref: 唯一性索引扫描, 返回匹配的唯一一行,常见于主键或者唯一索引扫描
const、system,查询对某部分进行优化,并且转换为一个常量时,system表示查询的表只有一行的特殊情况
null: MySqL在优化过程中分解语句,执行甚至不用访问表或者索引
possible_keys, 是指查询可能使用到的索引,key,表示查询实际使用到的索引, key_len表示索引字段可能的最大长度

ref: 上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
rows: 找到所需的记录所需要读取的行数,也就是符合匹配条件的行数
Extra,额外的信息
Using index: 表示在select操作中用到了覆盖索引(Covering index)
Using where: 表示MySqL使用where字句来过滤结果集,而且是先返回行结果集以后再使用where语句过滤行
Using temporary : 表示需要使用到临时表来存储结果集,常用于排序和分组
Using filesort 表示无法利用索引进行排序的操作称为文件排序
MySQL执行计划的局限

•EXPLAIN**不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况**
EXPLAIN不考虑各种Cache
EXPLAIN不能显示MySQL在执行查询时所作的优化工作
•部分统计信息是估算的,并非精确值
EXPALIN只能解释SELECT操作,其他操作要重写为SELECT后查看执行计划

高性能的索引策略要考虑如下方面:
1. 独立的列,即查询的的时候列中不存在计算,表达式,函数参数等
2. 对于索引长字符列(blob,Text,),善于使用前缀索引
3. 要选择合适的索引顺序,按照经验来说,选择性较高的字段放在前面,也就是说经过第一个条件之后得到的行数会比较的少
4. 善于使用聚簇索引,在innoDb中按照主键顺序插入行
5. 善于利用覆盖索引
6. 利用索引扫描来做排序
7. 可以使用压缩索引
8. 移除或者优化重复和冗余索引,对于未使用的索引要删除掉
9. 索引和锁,索引可以减少innoDB访问行的行数,从而减少对行的加锁,使用了索引之后,索引会过滤掉一些无效的行,从而使得只对有效的行进行加锁,需要注意的是,有时即使使用了索引也会锁住一些无效的行:
select id from user where id <5 and id >1; 有时对于这种范围查询的时候也会锁住一些无效的行,首先结果会返回 id<5 的所有行,然后加锁, 然后再从结果中找 >1的结果返回,然后再解锁。
由此可见,如果不使用索引,就会对全表扫描而锁住所有的行,开销特别大。

此博文参考了博客
http://blog.csdn.net/superhosts/article/details/25611119
http://blog.csdn.net/leileixiaoshan/article/details/26108427

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章