mysql

mysql索引底层数据结构

2020-08-04  本文已影响0人  今年五年级

一 什么是索引

索引是帮助mysql高效获取数据的排好序数据结构,索引是储存在文件中的

1.1 图例

最简单的索引可以理解为目录(一行一行寻找),如下图所示


image.png

复杂一点的索引:树形结构索引


image.png
树形结构的索引效率要高于简单索引的效率

二 常见索引结构

由上面可知:存储索引的树的高度越低,查找次数越少,查找速度越快。因此为了存储上千万行数据,并且还要让树的高度可控,只需要让一个节点横向存储更多的索引元素即B树

这样就不是查询某个索引,而是将整个装有15,56,77索引元素的节点从磁盘加载到内存中,这样进行一次IO操作,就可以查询出很多索引。这样次数会少很多

非叶子节点会将叶子节点每个头元素,如15,20,49作为冗余的索引来构建B+树。实际的索引和data都在叶子节点层

每个包含很多索引的节点在mysql中叫做磁盘页,mysql给这个磁盘页分配的存储空间是16KB,可以使用下面的sql语句查询这个参数SHOW GLOBAL STATUS LIKE 'iNNODB_PAGE_SIZE'。即这个节点可以放16KB索引元素

假设要在B+树上查询30这个元素,首先在磁盘上定位根节点,将根节点整个load到内存,然后在内存中做比对,因为节点中的索引元素从左到右递增,因此可在内存中使用折半查找。发现在15到56之间,下图中的15和56之间的白色部分存储的是左分叉15,20,49的节点的磁盘文件地址,发现在做分叉,那就把15,20,49这个索引元素的节点继续加载到内存中去查找,然后去查找叶子节点,将叶子节点加载到内存,再折半,找到30

在内存中的比对操作相比于磁盘读取节点可以忽略不计

因为整张表的索引元素在叶子节点有一份完整的数据,假设B+树的高度是3,树被放满的情况下,求叶子节点可以放多少个索引元素?
每个节点可以放16KB的数据,如果存放主键索引,主键索引为bigint类型,而bigint一个占8个字节byte,而指向分叉的白色部分mysql分配占据了6个字节。而索引跟磁盘分叉地址白色部分是1:1的关系,当该节点占满后,可以放(16KB)/(8+6)byte=1170左右个索引元素。对于叶子节点来说,有点区别,因为叶子节点存放的data元素可能是索引所在行的磁盘文件地址,也可能是索引所在行的其他列,因此data可能有时候比较大,假设data是把一行的记录都放进来,单个索引+data最大按1KB算,那么叶子节点的一个节点可以放16个(索引+data)

那么H=3的高度的话,叶子节点能放1170x1170x16=21904200个索引元素

非叶子节点在mysql启动的时候,会一次性被load到内存中,那么比如查找30,上面的冗余节点不需要去从磁盘load,之前已经初始化在内存中了,最终只要在内存中定位到指定的磁盘地址指针,将叶子节点load一次到内存中查询即可

问:高度越低越好,那么直接将所有数据放在根节点处呢,直接一次load到内存?
不行,一个节点只能存16K数据,如果数据表真有千万行数据,也存不下,这个节点需要几M甚至几十M。况且每次查找都需要将这么多数据load到内存,或者load一次,如果有成百上千张表,放在内存也不现实,几千万的数据load到内存查找也不会很快

三 索引分类

3.1 查看磁盘上innoDB引擎和myisam引擎的表实际存储形式

存储引擎是表级别的,意思是一个库中不同的表可以有不同的存储引擎,我们创建两张表用于测试,info表和info1表

info表采用innoDB引擎,info1表采用myisam引擎
innoDB引擎:
show global variables like "%datadir%"
磁盘上该表有两个文件
info.rrm 表的结构文件,任何一个存储引擎都有
info.ibd InnoDB独有的,(数据和索引)文件,即数据和索引在同一个位置
myisam引擎:
info1.frm
info1.MYD 以d结尾,代表data数据
info1.MYI 以i结尾,代表 index索引

3.2 myisam的非聚簇索引

如下图所示,col1第一列设置为主键,用B+树存储的时候,将所有的索引存储在叶子节点,索引15存储3个地方,做了冗余,叶子节点存储的是这一行数据库的文件指针(有了这个指针,再去文件中找一次即可)
先通过索引找到文件指针,再根据文件指针去MYD文件中定位那一行数据


col1创建的索引为主键索引,col2创建的索引为非主键索引,在myisam引擎中,存储的方式都是一样的

3.3 innoDB的聚簇索引

和myisam的非聚簇索引的差别主要在叶子节点,myisam在索引15的同一个节点中,存储的是行记录的文件指针,而innoDB在索引15的同一个节点中,存储的是索引所在行的其他列的数据,并且查到的索引15这一行数据才会加载到内存,没查询的还在磁盘上,因此不存在耗内存的问题

innoDB的主键索引存储形式不同于非主键索引,比如我们采用第三列学生的名字列作为普通索引,非主键索引叶子节点存储的data是索引所在行的主键

字符串的话,右边还是大于左边,采用ASCII码值的大小来比较左右
当进行匹配的时候,会把字符串转换成ascll码,如abc变成97 98 99,然后从左往右一个字符一个字符进行对比。所以在sql查询中使用like %a 时候索引会失效,因为%表示全匹配,如果已经全匹配就不需要索引,还不如直接全表扫描

\color{ red } {innoDB的非主键索引不存储整行数据,存储的是主键字段的主键索引的值 }

回表)innoDB有主键索引和非主键索引(辅助索引),先在辅助索引树上找到主键索引值,然后再用这个值去找主键索引树上对应的行记录

问题1:innoDB的非主键索引不存储整行数据,存储的是主键字段的主键索引的值,为什么要这么存储呢?
答:一致性和节省存储空间,主键索引已经要存储一次整行数据, 如果非主键索引也存储整行数据,那么意味着插数据要在主键索引和非主键索引都插入,插入两次,可能会有数据一致性的问题

问题2:innoDB表为什么必须要有主键,并推荐使用整型自增主键?
因为非主键索引存储的叶子节点会访问主键索引的地址,如果不使用整型,比如使用UUID的话,
(1)则浪费空间
(2)自增整型比较大小非常快,字符串作为索引,则比较采用ASCII码值逐位比较,速度极慢
(3)则每次新建节点申请一个页空间,即使如下图我只插入50,计算机也会申请一页的空间,而递增的主键id可以按照顺序进行插入,查询范围,比如大于50的数据,这一页会很快。而如果使用uuid的话,当前面满了他不会按照顺序在右面一直插入,而是会出现随机插入(比如插入在前面度满了的节点)这样导致这个节点会发生分裂,这是一个非常慢的过程,还可能该节点数据移动到其他节点上去,造成其他节点分裂

如果没有主键的话,innoDB默认会从你的记录中的能作为主键(数据不重复)的一列作为主键,如果一行的所有列都不能作为主键,那么innoDB会在后台默认生成整型主键

3.4 联合索引

不建议搞很多单值索引(浪费很多存储空间),优先联合索引
比如两个varchar字段key1,key2构成联合索引,存储索引的时候,是key1+key2字符串组合在一起进行比较大小的

三个字段,字符串类型名称,int类型年龄,字符串类型角色组成的联合索引

存储的时候,把这3个字段全部放到索引的这个里面,但是真正比较的时候是分开比较的,先去比较第一个字段,如果第一个字段相同就走到第走到第二行左边,判断第二个字段,如果还相同,就比较第三个字段

如果创建的是主键联合索引(联合主键),那么索引树的值中就存储行其他字段
如果创建的是非主键辅助联合索引,那么索引树的值中就存储主键字段

上一篇下一篇

猜你喜欢

热点阅读