Mysql-索引

by shihang.mai

1. 索引存放位置

选择不同的存储引擎,数据和索引以不同的文件格式,存放在不同的位置。

存储引擎 位置 文件格式 索引类型
Innodb 磁盘 .frm:表结构
.idb:数据文件和索引文件
聚簇索引
Myisam 磁盘 .frm:表结构
.myi:索引文件
.myd:数据文件
非聚簇索引
Memory 内存

2. Mysql索引数据结构

mysql存储索引数据结构用的B+树

2.1 如果用哈希表存储

mysql-hash

Data:(key,value)=(列值,一行数据或者是某个列的值)。每次在添加索引时,需要计算索引列的hash值,取模运算后计算下标,然后放入data即可。哈希表存储索引只适合等值查询场景。

Mysql不用哈希表原因:

  • 数据是无序的,而企业中大多查询是范围查询,那么就要做遍历操作,比较浪费时间
  • hash表做操作时,要全部加载到内存,比较浪费内存空间

2.2 树

树特点:左子树必须小于根节点,右子树必须大于根节点。多叉树的话,从左到右是有序的

mysql-tree

得出结论:二叉树及其变种,都不能支撑索引的存储。因为树深度无法控制或者是插入性能差。

2.3 B树

  • 所有的键值分布在整个树中

  • 搜索可能在非叶子结点结束,性能逼近2分查找

    mysql-B树

    图中,黄色-索引列值,红色-索引对应的数据,绿色-指针,例如查找28

    1. 第一次IO,读入4k,16<22<34,寻找p2
    2. 第二次IO,读入4k,25<28<31,寻找p2
    3. 第三次IO,读入4k,找到28返回数据

    假如:data-1k,那么每个磁盘块存4条数据 ,一共存储4*4*4=64条数据,3次IO

    缺点:

    1. data数据的大小并不能控制,一个磁盘块内,随着data数据下降,索引量下降
    2. 当存储数据量大时,树深度过深,增大查询IO次数,进而影响性能

2.4 B+树

  • 非叶子结点只存储key,叶子节点存储key-value

  • 非叶子结点能存放更多key,降低树的高度。将数据范围变为更多的区间,区间越多检索越快

  • 叶子结点间还是双向链表,顺序查询性能更高

    mysql-B+树

    图中,黄色-索引列值,红色-索引对应的数据,绿色-指针,例如查找28

    1. 第一次IO,读入4k,16<22<34,寻找p2
    2. 第二次IO,读入4k,25<28<31,寻找p2
    3. 第三次IO,读入4k,找到28返回数据

    假如:data-1k,p+16=10byte,4k=4096byte约等于4000byte,一共存储400*400*4=640000条数据,同样3次IO

3. 总结

3.1 innodb索引

mysql innodb索引是B+树

每个节点都是一个磁盘块4k大小

非叶子节点会存放索引及指向下个磁盘块的指针,并且索引是有序排列的

叶子节点存放索引及其值,并且索引是有序排列的,每个叶子节点都有双向指针

3.2 组合索引匹配

例如建立(a,b,c)组合索引

索引由上以下分别是a b c ,并且从左往右a的索引有序,当a索引相同,那么根据b索引排序,b索引相同根据c排序

先匹配a索引,再匹配b索引,最后匹配c索引,所以有最左匹配原则,条件a ab abc均会走索引

3.3 B+树和B树对比

B树匹配有可能在非叶子节点结束,即每个磁盘块均包含索引及其数据,这样的话索引的数量会随着数据的增大而减少,导致树深过高

而使用B+树的话,叶子节点存放数据,那么非叶子节点就可以存更多的索引,降低树的深度

对于查询同一个条件,B+树读取I/O的数量比B树少

3.4 树高度计算

TINYINT(1字节)/SMALLINT(2字节)/MEDIUMINT(3字节)/INT(4字节)/BIGINT(8字节)

问:3000万数据,树高度多少?

反问:每条记录多大?用什么存储主键?有建其他索引吗?有的话是哪个,用什么存储?

如:每条记录1k,主键用bigint存储,没建其他索引

那么这里只有id索引树,树深度计算前,先计算叶子节点和非叶子节点分别能存多少条数据.指针大小默认=6k,innodb每页16k

叶子节点可存储的数据量=(16 * 1024)/(1 * 1024 + 8)约等于16

非叶子节点可存储的数据量=(16 * 1024)/(6 + 8)约等于1170

那么2层树深度可表示的数据条数=1170 * 16 = 18720

那么3层树深度可表示的数据条数=1170 * 1170 * 16 约等于2000W

那么4层树深度可表示的数据条数=1170 * 1170 * 1170 * 16>2000W

所以3000万条数据高度为4

4. Innodb回表

当索引列是普通索引时触发

表数据

Id Name
1 ma
2 zhou
3 lian
4 gan
5 ming
6 lu
mysql-回表

当name建索引,data存放的是id的主键。例如找lian,那么先找下面的B+树,找到了主键3,再找上面的B+树,找到返回。叫回表

5. Myisam树

仍然是B+树,但是因为数据和索引是分别存放在不同文件中。那么主键索引的B+树的红色存放的是文件位置,有文件位置,再去找数据文件即可

6. 索引分类

  1. 主键索引

    唯一非null。mysql自动以主键创建索引,没主键的话,找唯一键,没唯一键的话,自行生成rowId创建索引。

  2. 唯一索引

    唯一可为null。唯一索引不用做回表操作。

  3. 普通索引

    不唯一可为null。要做回表操作。

    -- 需要先找name b+树,再找id B+树
    select * from stud where name ='msh';
    -- 覆盖索引 如果需要查询的列 在索引树中可以找到对应数据,不用再查另外一个b+树,就叫覆盖索引
    -- 只需要找name b+树
    select id from stud where name = 'msh'; 
    
  4. 全文索引

    可在char varchar text字段类型上建。

  5. 组合索引

    组合索引在B+数的查找

    https://blog.csdn.net/ibigboy/article/details/104571930/
    

    多列值组成一个索引,专门用于组合

    告警运维查询慢,页面需求:数据中心和设备名称
    
    那么可以建2种情况索引:
    
    A. dcId&eqName(组合索引) + eqName(普通索引)
    
    B. eqName&dcId(组合索引)+ dcId(普通索引)
    
    name长度一定比dcId大,那么选择B方案
    

7. 索引下推

前提:组合索引.

建立name,age联合索引,查询下面sql

SELECT * from stud where  name ='msh' and age ='28';

在mysql5.6前,通过name去(name,age)B+树找到id,然后通过id去(id)B+树找到数据返回到server层,在server层再通过age过滤数据

在5.6后加入后,通过name,age去(name,age)B+树找到id,然后通过id去(id)B+树找到数据返回

这里的下推是指,下推到存储引擎做过滤操作

8. 索引合并和组合索引

索引合并,分别对name,age建索引,然后查找时,分别去(name)B+和(age)B+树查找数据,然后结果进行合并

组合索引,将(name,age)编为一个索引,查找数据时,直接(name,age)B+树查找数据

9. 前缀索引

对于一些比较长的字段,截取前面固定长度的字符做索引。

alter table student add key(name(5));

10. 索引使用

  1. 全值匹配
    建立字段(name,age,sex)联合索引
select * from student where name='msh' and age = 18 and sex=0
  1. 匹配最左前缀
    建立字段(name,age,sex)联合索引
-- 只有(name,age,sex),(name,age),(name)走索引
select * from student where name='msh' and age = 18 and sex=0
select * from student where name='msh' and age = 18 
select * from student where name='msh'
  1. 匹配列前缀
    建立字段(name,age,sex)联合索引
-- 走索引
select * from student where name like 'm%'
-- 不走索引
select * from student where name like '%m%'
  1. 匹配范围值
    建立字段(name,age,sex)联合索引
select * from student where name > 'msh'
  1. 精确匹配某一列,范围匹配另外一列
    建立字段(name,age,sex)联合索引
-- 2个都走索引
select * from student where name='msh' and age > 18 
-- sex不走索引
select * from student where name='msh' and sex > 18 
-- 这个age也不走索引
select * from student where name='msh' and age = '18' 
  1. 组合索引匹配
    建立字段(a,b,c)联合索引
-- 用了a
where a=3
-- 用了a,b
where a=3 and b=4
-- 用了a,b,c 顺序mysql会自动调整的
where a=3 and c=5 and b=4
-- 用了a
where a=3 and c=5
-- 用了a,b
where a=3 and b>4 and c=5
-- 用了a
where a=3 and b like '%xx%'  and c=5
  1. union all,in,or都能够使用索引,但是推荐使用in
  2. 范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列
  3. 需要join的字段,数据类型必须一致,不然不走索引

函数、隐式类型转换、字符集不走索引原因

11. 索引不适用条件

有索引,但是不走:

  1. 索引列上有函数。

  2. 不满足最左前缀。

  3. 使用了不等号

  4. 需要join的字段,数据类型不一致

不适合建立索引:

  1. 唯一性太差的字段,即使频繁作为查询条件
  2. 更新非常频繁的字段
  3. 不会出现在 WHERE 子句中的字段
  4. 表记录太少

12. 不能建很多索引原因

虽然索引可以加快查询,但是对插入,删除等等操作需要维护索引,索引也要存储空间的,而且随着表数据量的增加,索引所占用的空间也会不断增加,所以索引还会带来存储空间资源消耗的增加。会涉及页分裂和页合并

当插入数据时

向一页一页的插,满了就新开一页插入

页合并可发生在删除或更新时

当你删了一行记录时,实际上记录并没有被物理删除,记录被标记为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录达到MERGE_THRESHOLD(默认页体积的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用,这叫页合并

页分裂可发生在插入或更新

页可能填充至100%的。当前页A无法容纳需要插入的数据,然而下一页B也无法容纳,那么就会新建一页C,将A可分的数据移到C,并把需要插入的数据也插入到C,然后维护前后指针,类似链表插入数据一样。叫页分裂.

要记住在合并和分裂的过程,InnoDB会在索引树上加写锁(x-latch)。在操作频繁的系统中这可能会是个隐患。它可能会导致索引的锁争用(index latch contention)。如果表中没有合并和分裂(也就是写操作)的操作,称为“乐观”更新,只需要使用读锁(S)。带有合并也分裂操作则称为“悲观”更新,使用写锁(X)

13. 一条sql走了索引但是依然很慢

  1. explain看下执行计划, 虽然走了索引,但是走的索引是不是最好的?
  2. 有没有进行内存排序操作?
  3. 是不是查询的列不在索引中而导致了回表?
  4. 是不是服务器压力太大,而导致的处理速度变慢?
  5. 是不是有查询的数据没在mysql的buffer poll中,而从磁盘加载到内存中

你可能感兴趣的:(Mysql-索引)