MySQL 的索引理解

索引

@(数据库)[MySQL]html

索引是什么?

索引的解释

数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询,更新数据库表中数据。
  • 值得注意的是:SQL 标准并无为数据库用户提供任何在数据库系统中控制建立和维护索引的防范,因为索引是冗余的数据结构,所以索引对保证正确性来讲并非必须的;可是索引对事务的高效处理十分重要,对完整性约束的有效实施也很重要;若是没有索引的话,MySQL进行全表扫描会对资源进行极大的浪费。
  • 若是不恰当的使用索引的话,只会增长无效的数据维护,耗费资源,同时查询效率仍然得不到提升。
  • 那么索引到底是什么呢?索引是一种数据结构,一个索引是存储的表中一个特定列或者某几个特定列的值数据结构,索引是在列上建立的。

索引的分类

有两种基本的索引类型
顺序索引:基于值的顺序排序
散列索引:基于将值平均分布在若干散列桶中,一个值所属的散列通是有一个函数决定的,该函数被称为 散列函数
顺序索引

顺序索引中,根据包含记录是否按照搜索码制定的顺序排序能够分为汇集索引和非汇集索引mysql

  • 被索引文件中的记录自身也能够按照某种排序顺序存储,一个文件能够有多个索引,分别预计不一样的搜索码,若是包含记录的文件按照某个搜索码指定的顺序排序,那么改搜索码对应的索引称为汇集索引,也称为主索引(Primary Index)。
  • 搜索码指定的顺序于文件中记录的物理顺序不一样的索引被称为非汇集索引或者辅助索引
  • 非汇集索引和汇集索引的区别在于,经过汇集索引能够查到须要查找的数据,而经过非汇集索引能够查到记录对应的主键值, 再使用主键的值经过汇集索引查找到须要的数据
  • 关于数据库中 Key,Primary Key,Unique Key 与 index 的不一样能够参见这篇文章
  • 关于顺序索引,通常若是没有明确表示的话指的就是 B+Tree 索引
哈希索引
  • 哈希索引(hash index)基于哈希表实现,只有精确匹配索引全部咧的查询才有效;对于每一行数据,存储引擎都会对全部的索引计算一个哈希码,哈希吗是一个较小的值,而且不一样键值计算出来的哈希码也不同。哈希索引将全部的哈希码存储在索引中,同时在哈希表中保存指向每一个数据行的指针。
聚簇索引和非聚簇索引并非单独的索引类型,而是一种数据存储方式 [高性能 MySQL]

索引的优势和缺点

优势
  • 索引大大减小减小了服务器须要扫描的数据量
  • 索引能够帮助服务器避免排序和临时表
  • 索引能够将随机 I/O 变为顺序 I/O
缺点
索引的缺点主要是针对不合理索引而言的,对于开发者而言,索引维护所耗费的资源和索引所提供的快速查询能力节省的时间资源二者进行取舍。
  • 建立和维护索引都须要耗费时间,并且随着数据量的增长而增长。
  • 每一个索引还须要耗费额外的物理空间资源。
  • 当对表中的数据进行 CURD 操做的时候,索引也须要对应的动态维护,增长数据维护成本。

索引的数据结构

MySQL 存储索引的时候通常咱们没有明确之处其他结构就是指的是 B-Tree 数据结构存储索引。 参考资料

卫星数据:指的是索引元素所指向的数据记录,好比数据库的某一行。算法

磁盘 I/O

  • 动态查询树主要有:二叉查找树,平衡二叉查找树,红黑树,B-Tree/B+Tree/B*Tree
  • 前三者是典型的二叉查找树结构,其查找的时间复杂度O(log2N)与树的深度相关,那么下降树的深度天然对查找效率是有所提升的;还有一个实际问题:就是大规模数据存储中,实现索引查询这样一个实际背景下,树节点存储的元素数量是有限的(若是元素数量很是多的话,查找就退化成节点内部的线性查找了),这样致使二叉查找树结构因为树的深度过大而形成磁盘I/O读写过于频繁,进而致使查询效率低下,那么如何减小树的深度,一个基本的想法就是:采用多叉树结构(因为树节点元素数量是有限的,天然该节点的子树数量也就是有限的)。
  • 关于硬盘(外存储器)的解读,磁盘获取数据由三部分时间组成:查找时间(代价最高),等待时间,传输时间.
  • 磁盘读取数据是以盘块(block)为基本单位的。位于同一盘块中的全部数据都能被一次性所有读取出来。而磁盘IO代价主要花费在查找时间Ts上。所以咱们应该尽可能将相关信息存放在同一盘块,同一磁道中。或者至少放在同一柱面或相邻柱面上,以求在读/写信息时尽可能减小磁头来回移动的次数,避免过多的查找时间Ts。
  • 当咱们利用索引查询的时候,显然不可能把整个索引所有加载到内存中,只能逐一加载每个磁盘页,这里的磁盘页对应这索引树的节点.
  • 在使用树结构查询的时候若是使用二叉树,那么磁盘的 IO 次数最坏状况下等于索引树的高度.此时索引树的高度就对查询资源相当重要.

B-Tree Wiki

什么是 B-Tree?一个 m 阶的 B 树有如下几个特征。sql

  1. 根节点至少有两个子节点
  2. 每一个中间节点都包含 K-1个元素和 K 个子节点,其中 m/2<=k<=m
  3. 每个叶子节点都包含 K-1个元素,其中 m/2<=k<=m
  4. 全部的叶子节点都位于同一层
  5. 每一个非终端节点中包含有 n 个关键词信息(n,P0,K1,P1,K2....Kn,Pn)
    a. 其中 n 的取值范围是ceil(m/2)-1<=n<=m-1
    b. Ki为关键字,且关键字按照顺序排序(K(i-1)<Ki)
    c. Pi为指向子树根的接点,且指针P(i-1)指向子树种全部结点的关键字均小于Ki,但都大于K(i-1)。[每一个节点中的元素从小到大排列,节点当中 K-1个元素正好是 K 个孩子包含元素的值域划分]

clipboard.png

模拟查找文件29的过程:

(1) 根据根结点指针找到文件目录的根磁盘块1,将其中的信息导入内存。【磁盘IO操做1次】数据库

(2) 此时内存中有两个文件名17,35和三个存储其余磁盘页面地址的数据。根据算法咱们发现17<29<35,所以咱们找到指针p2。服务器

(3) 根据p2指针,咱们定位到磁盘块3,并将其中的信息导入内存。【磁盘IO操做2次】数据结构

(4) 此时内存中有两个文件名26,30和三个存储其余磁盘页面地址的数据。根据算法咱们发现26<29<30,所以咱们找到指针p2。函数

(5) 根据p2指针,咱们定位到磁盘块8,并将其中的信息导入内存。【磁盘IO操做3次】性能

(6) 此时内存中有两个文件名28,29。根据算法咱们查找到文件29,并定位了该文件内存的磁盘地址。大数据

分析上面的过程,发现须要3次磁盘IO操做和3次内存查找操做。关于内存中的文件名查找,因为是一个有序表结构,能够利用折半查找提升效率。至于3次磁盘IO操做时影响整个B-tree查找效率的决定因素。

固然,若是咱们使用平衡二叉树的磁盘存储结构来进行查找,磁盘IO操做最少4次,最多5次。并且文件越多,B-tree比平衡二叉树所用的磁盘IO操做次数将越少,效率也越高。

clipboard.png
B 树中每一个节点都具备卫星数据

节点的插入
插入(insert)操做:插入一个元素时,首先在B-tree中是否存在,若是不存在,即在叶子结点处结束,而后在叶子结点中插入该新的元素,注意:若是叶子结点空间足够,这里须要向右移动该叶子结点中大于新插入关键字的元素,若是空间满了以至没有足够的空间去添加新的元素,则将该结点进行“分裂”,将一半数量的关键字元素分裂到新的其相邻右结点中,中间关键字元素上移到父结点中(固然,若是父结点空间满了,也一样须要“分裂”操做),并且当结点中关键元素向右移动了,相关的指针也须要向右移。若是在根结点插入新元素,空间满了,则进行分裂操做,这样原来的根结点中的中间关键字元素向上移动到新的根结点中,所以致使树的高度增长一层。

例:自顶向下查找4的节点位置,发现4应当插入到节点元素3,5之间

clipboard.png

节点3,5已是两元素节点,没法再增长。父亲节点 2, 6 也是两元素节点,也没法再增长。根节点9是单元素节点,能够升级为两元素节点。因而拆分节点3,5与节点2,6,让根节点9升级为两元素节点4,9。节点6独立为根节点的第二个孩子

clipboard.png

节点的删除操做
删除(delete)操做:首先查找B-tree中需删除的元素,若是该元素在B-tree中存在,则将该元素在其结点中进行删除,若是删除该元素后,首先判断该元素是否有左右孩子结点,若是有,则上移孩子结点中的某相近元素到父节点中,而后是移动以后的状况;若是没有,直接删除后,移动以后的状况.。

删除元素,移动相应元素以后,若是某结点中元素数目小于ceil(m/2)-1,则须要看其某相邻兄弟结点是否丰满(结点中元素个数大于ceil(m/2)-1),若是丰满,则向父节点借一个元素来知足条件;若是其相邻兄弟都刚脱贫,即借了以后其结点数目小于ceil(m/2)-1,则该结点与其相邻的某一兄弟结点进行“合并”成一个结点,以此来知足条件。那我们经过下面实例来详细了解吧。

例:删除11节点

clipboard.png

删除11后,节点12只有一个孩子,不符合B树规范。所以找出12,13,15三个节点的中位数13,取代节点12,而节点12自身下移成为第一个孩子。(这个过程称为左旋)

clipboard.png

B+Tree

B+Tree 是对于 B-Tree 的一种变体,有着比 B-Tree 更高的查询效率。

一个 m 阶的 B 树有着以下特色

  1. 有 K 个子树的中间节点包含有k个元素(B树中是k-1个元素),每一个元素不保存数据,只用来索引,全部数据都保存在叶子节点。
  2. 全部的叶子结点中包含了所有元素的信息,及指向含这些元素记录的指针,且叶子结点自己依关键字的大小自小而大顺序连接。
  3. 全部的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。

clipboard.png

根节点的最大元素等荣誉整个树的最大元素,叶子节点包含了全量元素信息,而且每个叶子节点都带有指向下一个节点的指针,造成了一个有序链表.

clipboard.png

在 B+Tree 树中,只有叶子节点保存卫星数据.

clipboard.png

须要补充的是,在数据库的汇集索引(Clustered Index)中,叶子节点直接包含卫星数据。在非汇集索引(NonClustered Index)中,叶子节点带有指向卫星数据的指针。

因为 B+Tree 树的中间节点没有卫星数据,因此一样大小的磁盘也(1~4K)能够容纳更多的元素,意味着查询的 IO 次数越少。

另外,B+Tree 的查询必须最终查询到叶子节点,和 B-Tree 不一样,查询性能最稳定,并且在范围查询中,比起只能以来繁琐的中序遍历的 B 树,更有效率。

综合来看,B+Tree 对比 B-Tree 有三大优点:

  1. 单一节点存储更多的元素,使得查询的IO次数更少。
  2. 全部查询都要查找到叶子节点,查询性能稳定。
  3. 全部叶子节点造成有序链表,便于范围查询。

哈希索引

关于哈希索引,目前 MySQL 中只有 Memory 和 NDB 两种引擎支持,详细了解能够参考这篇文章 MySQL索引之哈希索引,本文不在赘述。

索引的使用

理解了索引的数据结构以后咱们就理解了索引在建立和使用上的一些方法(如下所描述的索引均指的是 B-Tree 索引)。
create table People (
    last_name   varchar(50)     not null,
    first_name  varchar(50)     not null,
    dob         date            not null,
    gendar      enum('m','f')   not null,
    key(last_name, first_name, dob)
);
  • 使用 B-Tree 索引的查询类型适用于下列集中状况
  1. 全值匹配

    select * from People where last_name = 'Allen' and first_name = 'Cuba' and dob = '1999-01-01';

  2. 匹配最左前缀

    select * from People where last_name = 'Allen';

  3. 匹配列前缀

    select * from People where last_name like 'A%';

  4. 匹配范围值

    select * from People where last_name > 'Allen' and last_name < 'Bob';

  5. 精确匹配某一列并范围匹配另一列

    select * from People where last_name = 'Allen' and last_name like 'B%';

  6. 只访问索引的查询

    select last_name,

    first_name, 
        dob

    from People
    where last_name = 'Allen'

    and first_name = 'Cuba' 
    and dob = '1999-01-01';
  • 关于使用索引的一些限制
  1. 若是不是按照索引的最左列开始查找,则没法使用索引

    select * from People where first_name = 'Allen';

  2. 不能跳过索引中的列

    select * from People where last_name = 'Allen' and dob = '1999-01-01';

  3. 若是查询中有某个列的范围查询,则其右边的全部咧都没法使用索引优化查询

    select * from People where last_name = 'Allen' and first_name like 'J%' and dob = '1999-01-01';

高性能的索引策略

参考资料

  1. 最左前缀匹配原则,很是重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就中止匹配,好比a = 1 and b = 2 and c > 3 and d = 4 若是创建(a,b,c,d)顺序的索引,d是用不到索引的,若是创建(a,b,d,c)的索引则均可以用到,a,b,d的顺序能够任意调整。
  2. 使用独立的列
  3. 多列索引:MySQL 从5.0以后的更新版本引入了一种叫索引合并策略,关于这项策略能够参考MySQL 优化之 index merge
  4. =和in能够乱序,好比a = 1 and b = 2 and c = 3 创建(a,b,c)索引能够任意顺序,mysql的查询优化器会帮你优化成索引能够识别的形式
  5. 尽可能选择区分度高的列做为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大咱们扫描的记录数越少,惟一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不一样,这个值也很难肯定,通常须要join的字段咱们都要求是0.1以上,即平均1条扫描10条记录
  6. 索引列不能参与计算,保持列“干净”,好比from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,缘由很简单,b+树中存的都是数据表中的字段值,但进行检索时,须要把全部元素都应用函数才能比较,显然成本太大。因此语句应该写成create_time = unix_timestamp(’2014-05-29’);
  7. 尽可能的扩展索引,不要新建索引。好比表中已经有a的索引,如今要加(a,b)的索引,那么只须要修改原来的索引便可

一些有价值的参考资料

相关文章
相关标签/搜索