MySQL性能优化一 底层数据结构与算法

一 索引

索引
MySQL官网解释:索引是帮助MySQL高效获取数据的排好序数据结构

索引数据结构:

  • 二叉树
  • 红黑树
  • Hash表
  • B-Tree

案例:有一张两列七行的一个表t
在这里插入图片描述
假如我们的查找sql语句是:

select * from t where t.col2=89;

正常情况下,需要一行一行取出来col2的值,然后与89进行比对,直到找到为止;

MySQL一张表里的数据,多行数据不一定是在磁盘里挨着存储的(因为假如存了一行数据后,几天后才存第二行数据,中间的时间段里可能会有其他数据存入磁盘),是随机存放的;执行一条查询sql语句(假如表里数据很多),每次从磁盘里取一条数据,都需要与磁盘做一次I/O读取交互,取出数据后做比对看是不是自己需要的数据,这样性能非常低下;我们的目的是,减少查找我们需要的数据时与磁盘的交互次数(减少查找次数),只要把这个次数控制在一定范围内,效率就会提升很多;此时,索引就诞生了;

如下,给col2做索引,前边已经提到了,索引就是一个数据结构,比如二叉树

二叉树
那我们把col2这列数据放在二叉树(左子节点小于父节点、右子节点大于父节点)里即可,如下:
在这里插入图片描述
查找89的话,只查找两次即可查到;第一次拿到34,发现不是我们要找的数据,且我们要找的数据大于34,应该在34右子节点查找,第二次即可找到89;

上图树中,每个节点存放key/value,其中key存放的是col2字段对应的值(34、77…89、23),value存放的是索引所在行的磁盘文件地址;

其实MySQL索引底层用的不是二叉树,原因如下

如果我们的查询col1的语句是这样的:

select * from t where t.col1=6;

如果是二叉树的话,那么col1对应的二叉树就是这样的:

扫描二维码关注公众号,回复: 14908771 查看本文章

在这里插入图片描述

此时的二叉树相当于变成了链表,查找col1=6的次数还是6次,没有提升查询效率;
即如果索引使用二叉树的话,遇到这种列的数据是递增的数据,二叉树就不会起到作用,所以索引底层没有用二叉树做;

红黑树
红黑树也叫二叉平衡树,有把树自动平衡的功能,col1对应的红黑树如下:
在这里插入图片描述
此时,查找col1=6的次数为3次;

MySQL索引底层用的也不是红黑树,原因如下:
树的高度的限制:当表的数据量太大,比如500w,那树的高度就非常高了,比如树高度达到了20,并且要查的数据位于最下边的叶子节点,至少需要20次查找,要做20次磁盘IO;所以我们要做的是把树的高度降下来,比如高度<=4,或者高度<=3等,我们是可以接受的——B树;

B树

  • 叶节点具有相同的深度,叶节点的指针为空
  • 所有索引元素不重复
  • 节点中的数据索引从左到右递增排列

之前的红黑树只有一个根节点,B树有多个跟节点(水平扩展)
在这里插入图片描述
MySQL索引底层也没有用纯粹的B树,而是对B树做了优化,即B+树

B+Tree(BTree变种)

  • 非叶子节点不存储data,只存储索引(冗余),可以放更多的索引(即叶子结点里包含了表里所有的索引元素)
  • 非叶子节点称为冗余索引,非叶子节点从叶子节点得到一些数据后,构建起来B+树(即非叶子节点就是为了构建B+树的)
  • 叶子节点包含所有索引字段
  • 叶子节点之间用指针连接(B树没有指针),存储着当前节点在磁盘上的位置,提高区间访问的性能

下边每一行我们称之为一页

在这里插入图片描述
MySQL索引底层用的就是B+树;
假如我们查找的col1=30,会先把根节点这一页(15、56、77)全部load到内存(RAM)里去(相对比较耗时),然后把30和内存里的这几个数据做比对(相对不耗时),假如使用二分查找快速定位到30是位于15~56之间;然后就把15这页数据(15、20、49)也加载到内存里,与30进行比对…;

那为什么不去掉其他节点,只留叶子结点,把所有的数据都放在叶子结点里,然后把叶子节点一次性load到内存里,直接把30和内存里的数据做折半查找呢?这样数据量大的话,内存容易撑爆;

每一页的大小大概16K

#查看mysql页大小:16384字节——16KB
SHOW GLOBAL STATUS  LIKE 'Innodb_page_size'

B+树放满后可以存放的数据量大概是多少?
为什么是16KB?
假如使用bigInt类型(8bit),则每一个索引占8bit,而上图中15和16中间存的是下一行(页)的地址(15、20、49的地址),这个地址占6bit;则一页数据16KB放满后,可以放的索引元素个数:16kb/(8+6)b=1170; 叶子结点特殊一些,就拿15这个叶子节点来说,data里存的可能是15这个索引所在磁盘空间地址,也可能存的是所在行的所有的其他列,data数据可能比较大,假如放的是一行数据,撑死了也就1kb(一行记录一般不会超过1kb),则这个叶子节点大概可以放的数据量为:1kb/(8+6)b=16(由于一般达不到1kb,所以得出的16这个值是假设的值,并不是在这里真正计算出来的);

综上,当B+树被放满后,可以放的索引数据量为:
1170X1170X16=21,902,400,即两千多万条;而树的高度仅仅是3,即3次IO就找到数据了;

MySQL的根节点其实是直接在内存里的(根节点常驻内存,即上图的15、56、77在最开始的时候就已经在内存里了),也就是说其实不是3次IO,而是2次;MySQL高版本后,把所有非叶子节点都放到了内存里了,这样就更快了;

为什么MySQL索引底层使用了B+树,而不是B树?
上边已经说了,B+树存储2000万条数的话,树高度只有3;如果是B树呢?
B树如下:
在这里插入图片描述

每一个data最大1kb,而每一页是16kb,所以每页(行)数据只能放16个索引元素,即16的n次方要达到2000万,这个n就是树的高度;很明显,n这个高度远远大于B+树的高度3;

表和索引是存储在磁盘里的,如果没有改配置的话,默认存的地方是:
在这里插入图片描述
在这里插入图片描述

二 MySQL表的存储引擎

2.1 存储引擎介绍

存储引擎是使用数据库的还是使用数据库表的?是数据库表的。

我们使用MySQL的Navcat建表的时候可以选择存储引擎,如下:
在这里插入图片描述
一般选择的存储引擎是InnorDB,早期版本使用MyISAM存储引擎
在这里插入图片描述

2.2 MyISAM存储引擎(已经不用了)

新建一张表,使用MyISAM做存储引擎,如下
在这里插入图片描述

  • .frm: 存放数据表结构的信息(frame框架简称)
  • .MYD: 存放数据(MY即MyISAM首字母,D是DATA)
  • .MYI: 存放索引(MY即MyISAM首字母,I是index索引)

MyISAM索引文件和数据文件是分离的(非聚集)

在这里插入图片描述
假如查的条件如下

select * from t where t.col1=30;

MySQL会先去MYI文件索引树里定位到索引元素为0xF3,然后根据0xF3在磁盘文件地址,在MYD文件里找到数据在磁盘里的一行数据;

2.3 InnorDB存储引擎

新建一张表,使用InnorDB做存储引擎,如下

  • .frm: 存放数据表结构的信息(frame框架简称)
  • .ibd: 存放数据和索引()

InnoDB索引实现(聚集)

  • 表数据文件本身就是按B+Tree组织的一个索引结构文件
  • 聚集索引-叶节点包含了完整的数据记录

在这里插入图片描述
上图可知,叶子结点存放了当前所在行的其他列的数据,如15这个节点存放了15所在行的所有其他列的数据34、Bob等(聚集索引);

即InnoDB的数据和索引在同一棵树里(同一个文件、聚集索引),而MyISAM是不在同一棵树里的(非聚集索引);

聚集索引与非聚集索引查找起来哪个快?
聚集快,因为聚集索引不需要跨文件查找;

为什么建议InnoDB表建主键,并且推荐使用整型的自增主键?

ibd文件必须要用一棵B+树来组织,那这个B+树从哪里来呢?如果表里自带主键的话,那就直接使用这个主键这列数据来构建B+树的整个表的数据。没有主键呢?没有主键的话,会从第一列开始,去选择一列数据都不重复的列作为主键,用这列数据来组织成一棵B+树;如果没有选到符合条件的列呢(没有一列数据是都不相等的)?那MySQL会新建立一个隐藏列,这个隐藏列会维护一个唯一id,来组织整张表的数据;

综上:我们建了主键后,就不需要那么麻烦了,不需要MySQL做那么多额外工作了;

那为什么推荐主键要使用整形且自增的呢?

整形原因

  • 在找索引的时候,在B+树这棵树里要进行比大小的操作,而uuid是字符串,比大小要通过ASSIC码先后顺序来比较,且要逐个字符来比,所以整形效率高;
  • 而且整形占用空间相对小很多;

自增原因
先来了解一下Hash结构

建索引的时候默认是B+Tree,也可以选择Hash结构
在这里插入图片描述
Hash结构

  • 对索引的key进行一次hash计算就可以定位出数据存储的位置
  • 很多时候Hash索引要比B+ 树索引更高效
  • 仅能满足 “=”,“IN”,不支持范围查询
  • hash冲突问题

表如下
在这里插入图片描述

把col3作为hash索引的话,当插入一条数据的时候,会对这个数据做一个hash算法(md5等很多种算法),得到的hash值放进hash桶里(hash数组),得到的hash值一样的话就就是hash冲突了,则生成一个链表来存hash值一样的数据;例如我们要查找name=Alice的一行数据,先对Alice做hash运算,得到hash值再去对应的链表里进行遍历;链表里的每个节点除了存放索引元素外,还存放了索引所在行的磁盘文件地址;
在这里插入图片描述
貌似这种hash查找更快一点;那为什么不用Hash结构,而要用B+树呢?主要是因为hash不支持=、in、范围查询;B+树在叶子结点有一个双向指针,且B+树是排好序的,所以支持范围查询;

非自增:新增数据的时候,会导致节点分裂,然后对树做一个平衡;
自增:新增数据的时候,不会分裂节点,会再新起一个节点;

为什么非主键索引结构叶子节点存储的是主键值?(一致性和节省存储空间)
如下,对col3做索引后,叶子节点Alice存储了主键值18
在这里插入图片描述
二级索引先找到主键索引,然后通过主键索引再找到具体的数据(二级索引有回表操作);

三 联合索引(复合索引)

一张表不推荐建立多个单值索引;一般通过建立2~3个联合索引,把80%以上的查询sql语句都覆盖到;

建立三个字段的联合主键索引:name、age、position
在这里插入图片描述

会按照索引建立的先后顺序,来排序,先比较name,再比age,再比position来决定先后顺序,排序后放在索引树里;
如name是字符串类型,那就按照Assic比较每一个字符,当通过name能排好序的话,就不看age和position了;name都一样(都叫Bill),那就比较age,age一样的话,就比较position,由于是联合主键,所以这里这三个字段不可能同时相等;

索引最左原理

在上边建立联合索引的前提下,下边哪条语句会走索引?

# 走索引
1 SELECT * FROM employees WHERE name = 'Bill' and age = 31;
# 不走索引
2 SELECT * FROM employees WHERE age = 30 AND position = 'dev';
# 不走索引
3 SELECT * FROM employees WHERE position = 'manager';

对于联合索引,一定要按照建立索引的顺序去使用;那为什么要有索引最左原理,为什么要按照name、age、position的查询顺序才会走索引?

数据插入索引树里是排好序的,而排序规则就是按照建索引时name、age、position的顺序来的;

假如我们不符合最左原则,直接查age=30,在整张表里,age不是排好序的了,所以索引没有起到作用,需要整张表全表扫描;
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_33417321/article/details/121130741