简单的聊聊索引的那些事儿

工做经历中,随着业务数据长时间积累,Mysql的数据也稍微有必定的量,因而乎当时咱们进行一次服务端慢查询大排查,肯定慢查询属于哪一个工程而且将其优化掉。我工程内也有一个,大致是MQ订阅到的taskid,taskid关联task的log表去找最近的一条记录的时间,而后根据时间校验是否放行作相应业务处理。我explain下,发现当时写的时候,log表的taskid也没有建索引,当log表的记录积累起来后,这个查询会显得很慢,建索引后有立竿见影的效果,固然这只是一个很是简单的场景。其实这里还衍生出一个问题:当log表数据量过大时候修改表结构,会形成一段时间的锁表。虽然有些方式能够避免锁表,可是“合理时机”建立索引仍是很重要的。知其然,要知其因此然,来看看索引的那些事儿。html

目录:

  • 相关知识准备
  • 聊聊BTree和B+Tree
  • MyISAM和InnoDB的索引实现
  • InnoDB的主键选择和优化
  • 经过原理理解最左前缀

相关知识准备

磁盘存储:

Mysql数据通常以文件的形式存储在磁盘上,读取数据时须要在磁盘上进行IO操做。当须要从磁盘读取数据时,系统会将数据逻辑地址传给磁盘,磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址,即肯定要读的数据在哪一个磁道,哪一个扇区。为了读取这个扇区的数据,须要将磁头放到这个扇区上方,为了实现这一点,磁头须要移动对准相应磁道,这个过程叫作寻道,所耗费时间叫作寻道时间,而后磁盘旋转将目标扇区旋转到磁头下,这个过程耗费的时间叫作旋转时间。mysql

计算机局部性原理:

当一个数据被用到时,其附近的数据也一般会立刻被使用。sql

磁盘预读:

因为磁盘顺序读取的效率很高(不须要寻道时间,只需不多的旋转时间),所以对于具备局部性的程序来讲,预读能够提升I/O效率。预读的长度通常为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操做系统每每将主存和磁盘存储区分割为连续的大小相等的块,每一个存储块称为一页(在许多操做系统中,页得大小一般为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,而后异常返回,程序继续运行。数据库

索引的目的:

上边内容能够看出磁盘IO很是耗时,因此索引归根结的目的:减小耗时磁盘IO次数,提升获取数据的性能。缓存

聊聊BTree和B+Tree

BTree有以下特色:
  1. 全部键值分布在整颗树中;
  2. 任何一个关键字出现且只出如今一个结点中;
  3. 搜索有可能在非叶子结点结束;
  4. 在关键字全集内作一次查找,性能接近二分查找;

B+Tree是BTree的变体,也是一种多路搜索树, 它与BTree的不一样之处在于:

  1. 全部关键字存储在叶子节点,非叶子节点并不存储真正的data;
  2. 为全部叶子结点增长了一个链指针;

MyISAM和InnoDB的索引实现

MyISAM主键索引策略:

MyISAM表的主键索引(Primary key)如图所示。能够看出MyISAM的索引文件仅仅保存数据记录的地址。MyISAM的辅助索引和主键索引在策略上没任何区别,只是主键索引多了惟一约束。(辅助索引为惟一索引也有此特性)

InnoDB主键索引策略:

能够看到叶节点包含了完整的数据记录。这种索引叫作汇集索引。由于InnoDB的数据文件自己要按主键汇集,因此InnoDB要求表必须有主键(MyISAM能够没有),若是没有显式指定,则MySQL系统会自动选择一个能够惟一标识数据记录的列做为主键,若是不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段做为主键,这个字段长度为6个字节,类型为长整形。

InnoDB辅助索引策略:

InnoDB辅助索引策略与MyISAM的不一样是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的全部辅助索引都引用主键做为data域。查询数据时,首先检索辅助索引得到主键,而后用主键到主索引中检索得到记录。辅助索引强依赖于主键索引,因此合理的建立主键是很是重要的。

InnoDB的主键选择和优化

在使用InnoDB存储引擎时,若是没有特别的须要,请永远使用一个与业务无关的自增字段做为主键。从数据库索引优化角度看,使用InnoDB引擎而不使用自增主键绝对是一个糟糕的主意。上文讨论过InnoDB的索引实现,InnoDB使用汇集索引,数据记录自己被存于主索引(一颗B+Tree)的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,所以每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,若是页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)。若是表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。以下图所示:bash

这样就会造成一个紧凑的索引结构,近似顺序填满。由于每次插入时也不须要移动已有数据,因此效率很高,也不会增长不少开销在维护索引上。若是使用非自增主键(若是身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置, 此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增长了不少开销。只要能够,请尽可能在InnoDB上采用自增字段作主键。

经过原理理解最左前缀

假设当前联合索引为:KEY a_id_state_index (a_id,name) 查询验证:性能

EXPLAIN SELECT * FROM `user` WHERE `a_id` = 5 AND `name` = 'cj_25'
EXPLAIN SELECT * FROM `user` WHERE `name` = 'cj_25' AND `a_id` = 5
复制代码

注:上面两句结果都以下图,由于mysql会对where里面的条件顺序在查询以前会被mysql自动优化优化

EXPLAIN SELECT * FROM `user` WHERE `a_id` = 5
复制代码

EXPLAIN SELECT * FROM `user` WHERE `name` = 'cj_25'
复制代码

为了分析仍是根据数据,按照上面规则去画这颗B+树:

对于辅助索引,树的构建是按照“最左”字段的顺序构建的,当查询name时,只能顺序查找,没法使用二分。

参考文章地址

blog.codinglabs.org/articles/th…spa

相关文章
相关标签/搜索