优雅的MySQL-索引

索引(在MYSQL中也叫做键),是存储引擎用于快速找到记录的一种数据结构,也是数据库性能调优的起点。
举一个栗子!考虑如下情况,假设数据库中一个表有10^6 条记录,DBMS的页面大小为4K,并存储100条记录。如果没有索引,查询将对整个表进行扫描,最坏的情况下,如果所有数据页都不在内存,需要读取10^4 个页面,如果这10^4 个页面在磁盘上随机分布,需要进行10^4 次I/O,假设磁盘每次I/O时间为10ms(忽略数据传输时间),则总共需要100s(但实际上要好很多很多)。
如果对之建立B-Tree索引,则只需要进行log100(10^6)=3次页面读取,最坏情况下耗时30ms。这就是索引带来的效果,很多时候,当你的应用程序进行SQL查询速度很慢时,应该想想是否可以建索引。
注意查询问题,包括等值查询,范围查询,模糊查询(like),并集查询(or)等。

索引基础

在MySQL中,存储引擎用一本书的“索引”找到对应页码类似的方法使用索引,其先在索引中找到对应值,然后根据匹配的索引记录找到对应的数据行。
索引可以包含一个或多个列的值。如果索引包含多个列,那么列的顺序也十分重要,因为MySQL只能高效地使用索引的最左前缀列。

索引的类型

PRIMARY KEY 主键索引
INDEX 普通索引
UNIQUE 唯一索引
FULLTEXT 全文索引
组合索引(较特殊):一次检索三条索引,直到找出相应的数据返回。

聚簇

非聚簇索引

B+Tree的叶子节点上的data,并不是数据本身,而是数据存放

聚簇索引:
  • InnoDB的主键使用,
    ** 优点:**
  • 聚簇索引的数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的。范围查找时,聚簇索引要比非聚簇索引查询效率高很多。
  • 非聚簇索引定位到对应主键时需要多一次目标记录寻址,多一次IO
  • 聚集索引这种主+辅索引的好处是,当发生数据行移动或者页分裂时,辅助索引树不需要更新,因为辅助索引树存储的是主索引的主键关键字,而不是数据具体的物理地址。
    缺点:
    1.插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键。如果采用随机插入过程,会需要频繁的移动和分页
    2.更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。
    3.二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。二级索引的叶节点存储的是主键值,而不是行指针(非聚簇索引存储的是指针或者说是地址),这是为了减少当出现行移动或数据页分裂时二级索引的维护工作,但会让二级索引占用更多的空间
    4.采用聚簇索引插入新值比采用非聚簇索引插入新值的速度要慢很多,因为插入要保证主键不能重复,判断主键不能重复,采用的方式在不同的索引下面会有很大的性能差距,聚簇索引遍历所有的叶子节点,非聚簇索引也判断所有的叶子节点,但是聚簇索引的叶子节点除了带有主键还有记录值,记录的大小往往比主键要大的多。这样就会导致聚簇索引在判定新记录携带的主键是否重复时进行昂贵的I/O代价。

B-Tree索引

B树

B树简化图

按照顺序存储,每个叶子页到根的距离相同。
B树的特征:

  1. 根节点至少有两个孩子
  2. 每个非根节点有[ ,M]个孩;
  3. 每个非根节点有[ -1,M-1]个关键字,并且以升序排列
  4. key[i]和key[i+1]之间的孩子节点的值介于key[i]、key[i+1]之间
  5. 所有的叶子节点都在同一层

局部性原理:磁盘并不是每次严格按需读取,而是每次都会预读。磁盘读取完需要的数据后,会按顺序再多读一部分数据到内存中
B-Tree借助计算机磁盘预读的机制,并使用如下技巧:每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了一个结点只需一次I/O。

B+Tree索引

B+树

B+树简化
  • 所有关键字存储在叶子节点,非叶子节点不存储真正的data
  • 为所有叶子节点增加了一个链指针

浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。

查找过程

如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。

性质
  1. 索引字段: IO次数取决于b+数的高度h,每块磁盘块的数据项数量m越多,树的高度越低。因此在磁盘块的大小一定,也就是数据页的大小,每个索引字段尽量小,m越大。这也是B+树吧真实的数据放在叶子节点而非内层节点中。
  2. 最左匹配原则: 当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;
  3. 范围查找:mysql是关系型数据库,经常会按照区间来访问某个索引列,B+树的叶子节点间按顺序建立了链指针,加强了区间访问性,所以B+树对索引列上的区间范围查询很友好。而B树每个节点的key和data在一起,无法进行区间查找。
  4. 查询效率:

Hash索引

  1. 如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值;当然了,这个前提是,键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,直到找到相应的数据;
  2. 从示意图中也能看到,如果是范围查询检索,这时候哈希索引就毫无用武之地了,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索;
  3. 哈希索引也没办法利用索引完成排序,以及like ‘xxx%’ 这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询);
  4. 哈希索引也不支持多列联合索引的最左匹配规则;
  5. B+树索引的关键字检索效率比较平均,不像B树那样波动幅度大,在有大量重复键值情况下,哈希索引的效率也是极低的,因为存在所谓的哈希碰撞问题。

索引的使用场景

适用场景

主键自动建立唯一索引
频繁作为查询条件的

不适用的场景

频繁被修改的不适合

慢查询

建索引的原则

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.=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。
3.尽量选择区分度高的列作为索引,区分度的公式是
count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录。
4.索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’)
5.尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。

Explain

可以用来:
  1. 查看表的读取顺序
  2. 数据读取操作的操作类型
  3. 哪些索引可以用
  4. 哪些索引被用了
  5. 表之间的引用
  6. 没长膘有多少航被优化器查看
内容

id
select_type
simple
primary:复杂查询的外层
subquery:
union:union之后的select
union_result:union查询的结果
table
结果是哪个表
type
该查询是什么类型
system:只有一行数据
const:通过索引一次就找到了,用于比较primary key 或者unique索引。
eq_ref:唯一性索引扫描,每个索引键表中只有一条记录匹配,常见于主键或者唯一索引扫描
ref:非唯一索引扫描,返回匹配某个单独值的所有行。本质也是索引
range:只检索给定范围的行,使用一个索引来选择行,key列显示了用那个索引。即where,in,<,>查询
index:遍历索引树进行扫描。
all:全表扫描
possible_keys
可能用到的索引,但是并不一定
key
真正用到的索引
key_len
组合索引中,命中了几个字段(最左匹配)
非组合索引中,这个索引的长度
ref
索引中的哪一列备用上了
rows
查询结果有多少行

优化步骤

0.先运行看看是否真的很慢,注意设置SQL_NO_CACHE
1.where条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高
2.explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询)
3.order by limit 形式的sql语句让排序的表优先查
4.了解业务方使用场景
5.加索引时参照建索引的几大原则
6.观察结果,不符合预期继续从0分析

你可能感兴趣的:(优雅的MySQL-索引)