索引
索引是帮助MySQL高效获取数据的数据结构,类似于数据的目录
优点:提高数据的检索效率,降低数据库的IO成本;通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗
缺点:索引列也会占用空间;索引大大提高了查询效率,同时也降低了更新表的速度
tips:如果表中有主键,默认会使用主键作为聚簇索引的索引键
如果没有主键,就选择第一个不包含NULL值的唯一列作为聚簇索引的索引键
如果上述两个都没有,InnoDB将会自动生成一个隐藏的row_id作为聚簇索引的索引键
二叉树
选定一个根节点,将所有大于根节点的节点放在右子树下,小于根节点的节点放在左子树下 但是如果顺序插入的情况下,会形成一个链表,导致查询性能大大降低,大数据量的情况下,层级较深,检索速度较慢
B树(多路平衡查找树)
以一颗最大度数为5的b树为例(每个节点最多存储4个key,5个指针)
这是一个大概的图,B树具体如何构建,可以在
www.cs.usfca.edu/~galles/vis… 中查看
B+树
以一棵最大度数为4的b+树为例
相对于B树,B+树的所有数据都出现在叶子节点,叶子节点之间形成一个单向链表,非叶子节点的数据仅用于索引
MySQL索引数据结构对B+树进行了优化,在原有B+树的基础上,增加一个指向相邻叶子节点的指针(形成双向链表),提高了区间访问的性能
tips:为什么会提高访问性能?
假设要执行一个范围查询,我们查到第一个满足条件的节点后,为了找到下一个数据,必须要回溯到父节点,甚至更上一层重新查找下一个键值,但是有了双向链表之后,定位到第一个满足条件的节点,不需要在回溯到上一级,可以直接顺序扫描后续节点
为什么使用B+树索引
- B+树 vs B树 B+树只在叶子节点存储数据,B树在非叶子节点也要存储数据,索引B+树的单个节点的数据量更小,同样的磁盘IO次数下,就能查询更多的节点,查询效率更高,同时B+树的叶子节点使用双向链表链接,在查询时可以二分查找,提高效率
- B+树 vs 二叉树 对于n个节点的B+树,其查询复杂度为O(logdN),d为节点允许的最大直接点个数 实际场景下,d通常大于100,因此即便数据量达到千万级别,B+树的高度也只有3-4层左右,即最多需要3-4次IO就可以查询到数据 而二叉树的子节点最多只有两个,查询复杂度为O(logN),查询复杂度更大,磁盘IO次数更多
- B+树 vs Hash索引 Hash索引在做等值查询时,效率很快,查询复杂度为O(1) 但是Hash索引不适用于范围查询
B+树是如何进行查询的
InnoDB中,B+树的每个节点都是一个数据页
通过上图,来查找主键为6的记录
- 从根节点开始,由于主键是6,二分法查找范围[1,7),可以知道要到页30中去查找数据
- 在页30中再次进行二分查找,由于6>5可以知道要到页16去查询数据
- 再到叶子节点中,通过槽查找记录时,使用二分法快速定位要查询哪个槽,定位到槽后,再遍历槽内的所有记录,找到主键为6的记录
Hash
Hash索引就是采用一定的Hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储到hash表中,如果两个或两个以上的键映射到同一个值上,就产生了hash冲突,可以通过链表来解决冲突 特点:
- Hash索引只能用于对等比较,不支持范围查询
- 无法利用索引完成排序操作
- 查询效率高,通常只需要一次检索(没有发生Hash冲突)即可,效率通常要高于B+树索引
索引分类
主键索引:针对于表中主键创建的索引,只能有一个
唯一索引:表中某一列的数据不能重复,可以有多个
常规索引:快速定位到特定的数据
全文索引:全文索引查找的是文本中的关键词,而不是比较索引中的值
InnoDB存储引擎中,又可以分为以下两种
聚簇索引:将数据存储与索引放到一块,索引结构的叶子节点保存了行数据,必须存在,且只能有一个
- 如果存在主键,主键索引就是聚簇索引
- 如果不存在主键,将使用第一个唯一索引作为聚簇索引
- 如果表中没有主键,也没有适合的唯一索引,则会自动生成一个rowid作为隐藏的聚簇索引 二级索引:将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键
select * from user where id = '1';
select * from user where name = 'tom';
二级索引查询需要回表查询,即在二级索引查询到对应的主键之后,还需要到聚簇索引中再次查询得到行数据,因此第二条sql语句的查询速度要比第一条更慢
前缀索引:当字段类型为varchar text时,有时候会索引很长的字符串,这会让索引变得很大,查询时浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀建立索引,大大减少索引空间,提高索引效率
覆盖索引:覆盖索引即只需要在二级索引的B+树中查询一次就能查询到结果的过程
create index idx___ on table_name(column(n));
只需要在建立索引时,在列名后面指定需要多长的前缀n即可
索引下推
对于联合索引(a,b),在执行select * from table where a > 1 and b = 2;时,只有a字段会走索引,那么联合索引的B+树在找到第一个满足条件的主键值(ID为2)后,还需要判断其他条件是否满足
- 在MySQL 5.6 之前,只能从ID为2的记录开始一个个回表,到主键索引上找出数据行,再对比b字段值
- MySQL 5.6之后,引入了索引下推优化,可以在联合索引遍历的过程中,对联合索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数 例如,对于下面这条SQL语句
select * from t where age = 25 and city = '北京';
传统索引方法,会先找到所有age = 25的记录,通过回表获取完整的四条记录,然后在Server层检查city='北京'的条件,最终保留一条记录,这样子就会有三次额外的回表操作
而对于索引下推,先找到所有age=25的记录之后,在索引层面直接判断city='北京',找到一条记录后,只对这一条记录进行回表,其他记录全部跳过
索引区分度
建立联合索引时的顺序,对索引的效率也有很大的影响,越靠前的字段被用于索引过滤的概率越高,因此在开发过程中,通常要把区分度大的字段排在前面,这样区分度大的字段就越有可能被更多的SQL使用到
区分度 = distinct(column) / count(*)
例如,对于性别字段,区分度就很小,不适合建立索引,或不适合排在联合索引列靠前的的位置,而对于UUID这类的字段就比较适合做索引,或排在联合索引列靠前的位置
对于区分度小的字段,假设字段的值分布均匀,无论搜索哪个值都可能得到一般的数据,在一些情况下,还不如不要索引,因为当MySQL中的查询优化器发现某个值出现在表的数据中的百分比较高时,一般会忽略索引,进行全表扫描
索引使用场景
索引能够提高查询速度,但是需要占用物理空间,创建索引和维护索引要耗费时间,且时间随着数据量的增加而增大,降低表的增删改的效率,每次增删改索引,B+树为了维护索引的有序性,都要进行动态维护
适合使用索引的场景
- 字段具有唯一性,例如订单号,商品编码等
- 经常用于WHERE查询条件的字段,建立索引就能够提高整个表的查询速度
- 经常用于GROUP BY和ORDER BY的字段,这样在查询的时候就不需要再次排序
不适合使用索引的场景
- WHERE,GROUP BY,ORDER BY里用不到的字段,因为建立索引会占用物理空间,这些条件用不到的字段,就不需要建立索引
- 字段的区分度低,字段中存在大量的重复数据,例如性别
- 表中数据较少
- 经常更新的字段,不用建立索引,因为经常更新的字段,需要维护B+树的有序性,就需要频繁的重新建立索引,影响数据库性能