MySQL 数据库存储数据最终是以文件的形式存储到硬盘的,在程序中使用要把磁盘文件中的数据读到内存中
执行一次IO的时间可以执行40万条指令(如果以 CPU 的指令执行效率来比较的话) 数据库量百万千万级数据,如每次io 9毫秒的时间显然是个灾难
所以查找数据时把磁盘IO次数控制在一个很小的数量级,一个高度可控的多路搜索树就是 B + 树
树的高度决定着io的次数,如果没有索引 没有索引则检索过程变成了顺序查找,O(n)的时间复杂度几乎是不能忍受 ,每个磁盘块都要发生一次IO(单个磁盘块中不可能存储太多数据) 效率会特别差
一、底层实现原理和优化
在数据结构中,最常见的搜索结构就是二叉搜索树和AVL树(高度平衡的二叉搜索树,,当数据量比较大时,都会由于树的深度过大而造成I/O读写过于频繁,进而导致查询效率低下
mysql的索引数据结构采取的是B+树
B+树索引并不能根据键值找到具体的行数据,B+树索引只能找到行数据锁在的页,然后通过把页读到内存,再在内存中查找到行数据
B+树,它的树高是恒定(可以控制在3到5层),它的渐进复杂度是恒定的
索引若采用哈希:
可以通过hash值定位具体的位置,效率高,但是不能进行模糊查询 范围查询
一个索引的是否优秀判断的依据是 IO渐进复杂度
索引若采用二叉树:
若是一个斜二叉树,查询速度和全表扫描没有区别,树高
索引若采用平衡二叉树:
太深了:深度决定者io的次数,而io耗时大
太小了:结点存储的信息太小了,从而导致频繁的io操作
索引若采用红黑树:
比二叉树相对来说会好一些。它会有一个因子会控制树高,无论二叉搜索树还是AVL树,当数据量比较大时,都会由于树的深度过大而造成I/O读写过于频繁
索引若采用多路平衡二叉树:(B-Tree B树):
(1)每个节点中有key,也有data,而每一个页的存储空间是有限的,如果data数据较大时就会导致每个节点(即一个页)能存储的key的数量很小
(2)当存储的数据量很大时,同样1会导致B-Tree的深度较大,增加查询时的磁盘I/O次数,进而影响查询效率
B+Tree叶子结点是顺序的,相邻结点存在顺序引用
1:B+Tree拥有B-Tree的优点,深度浅,数据块大
2:因为只在叶子结点存储数据,从而导致扫全表的能力强,因为叶子结点是顺序的,从而导致排序功能更强
二、B+树更适合B树
磁盘读写代价更低:
B+tree的内部结点相对B 树更小,相对来说IO读写次数也就降低了
查询效率更加稳定:
任何关键字的查找必须走一条从根结点到叶子结点的路,所有关键字查询的路径长度相同,导致每一个数据的查询效率相当
主要原因:
B+树只要遍历叶子节点就可以实现整棵树的遍历 方便扫库,而且在数据库中基于范围的查询是非常频繁的,而B树只能中序遍历所有节点,效率太低
三、文件索引和数据库索引使用B+树的原因
文件与数据库都是需要较大的存储,也就是说,它们都不可能全部存储在内存中,故需要存储到磁盘上
数据库系统巧妙利用了局部性原理与磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入
而红黑树这种结构,高度明显要深的多,并且由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性。
B+树最大的好处:只要遍历叶子节点就可以实现整棵树的遍历 方便扫库,而且在数据库中基于范围的查询是非常频繁的。
而B树只能中序遍历所有节点,效率太低
四、索引的优点
加快数据的检索速度
加速表和表之间的连接;
通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性
使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间;(有序)
五、索引的缺点
时间方面:创建索引和维护索引要耗费时间 ,R U D时,索引也要动态的维护,这样就降低了数据的维护速度;
空间方面:索引需要占物理空间
六、索引类型
普通索引、唯一索引、组合索引 、全文索引 。不一一介绍了
HASH 哈希索引 (MEMORY支持)
七、什么数据应该添加索引 (索引干两件事:检索和排序)
1.频繁作为查询条件的字段
2.查询中排序的字段 (有序)
3.主外键关系的字段 (可以加快连接的速度)
4.经常需要根据范围进行搜索的列上 (索引已经排序,其指定的范围是连续的)
5.经常需要排序的列上
注意:对于大的文本字段甚至超长字段 可以用前缀索引
八、索引注意事项
1.注意组合索引键列顺序
2.正确使用索引 遵循SARG规则
SARG 用于限制搜索的一个操作,是一个值的范围内的匹配或者两个以上条件的AND连接
SARG规则 如果使用not 或者 <>,最好转换成别的方法
不满足SARG形式的语句最典型的情况就是包括非操作符的语句,如:NOT、!=、<>;、!<;、!>;、NOT EⅪSTS、NOT IN、NOT LIKE等,
另外还有索引字段不能用函数.
如:Name=’张三’ and 价格>5000 符号SARG,
而:Name=’张三’ or 价格>5000 则不符合SARG。
使用or会引起全表扫描。尽量不要对建立了索引的字段,作任何的直接处理
2.1.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,表字段时不能允许为null的列 可设置默认值
select id from t where num is null 可以在num上设置默认值0
应改为:
select id from t where num=0
2.2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
2.3.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描 (少用or)
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
2.4.in 和 not in 也要慎用,因为IN会使系统无法使用索引,而只能直接搜索表中的数据
select id from t where num in(1,2,3) 对于连续的数值,能用 between 就不要用 in 了
应改为:
select id from t where num between 1 and 3
2.5.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描
select id from t where substring(name,1,3)='abc'--name以abc开头的id
select id from t where datediff(day,createdate,'2005-11-30')=0--‘2005-11-30’生成的id
应改为:
select id from t where name like 'abc%' (通配符 前缀索引)
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'
2.5.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引
2.6.用not exists都比not in要快
not in 那么内外表都进行全表扫描,没有用到索引;
not extsts 的子查询依然能用到表上的索引
2.7.能用UNION ALL就不要用UNION
UNION ALL不执行SELECT DISTINCT函数(去重),这样就会减少很多不必要的资源
2.8.前缀索引
char/varchar太长全部做索引的话,效率太差,存在浪费
无法使用其前缀索引做ORDER BY和GROUP BY,也无法使用前缀索引做覆盖扫描