为什么MySQL性能优化必问索引
正确的创建合适的索引是数据库优化的基础。
索引的本质和工作机制
索引是为了加速对表中数据行的检索而创建的一种分散存储的数据结构。
在RDBMS系统(关系数据库管理系统)中数据的索引都是硬盘级索引。
工作机制
以id为例,通过将id与id所代表的数据所在磁盘地址进行绑定,以达到加快查询性能的目的。
MySQL选择B+Tree的推演过程
传统的二叉搜索树
相比较二叉树,二叉搜索树需要满足一个性质,即当y是x的左子树的一个节点(注:不是直接子节点,是左子树的所有节点),则y.key <= x.key;如果y是x右子树的一个节点,则y.key >= x.key。
极端的二叉搜索树
这同样是一颗二叉搜索树,因为它满足二叉搜索树所需要的性质,同时这也是我们经常会遇到的二叉搜索树。
假如我们的id字段做了个自增操作,所以我们第一个插入的id就会作为根节点,后面插入的也会基于根节点来进行比对,但因为id是自增,则不会存在左子树。最终形成了一个线性链表。假如我们的运气很差,我们需要查询的是最后一个节点,那最终就变成了全表扫描。
平衡二叉搜索树(AVL树)
平衡二叉搜索树相较于二叉搜索树多了平衡二字,那么它平衡在哪呢?
- 左子树与右子树高度之差的绝对值不超过1
- 树的每个左子树和右子树都是AVL树
- 每一个节点都有一个平衡因子(balance factor),任一节点的平衡因子是-1、0、1(每一个节点的平衡因子 = 右子树高度 - 左子树高度)
通过一系列的操作(如左旋、右旋)保证树的任何一个节点的左右子树高度一致。
相信你应该经常听到别人说,不要在经常变动的字段上加索引。
因为数据结构平衡的保障,它会经常发生树的结构变化。
为什么MySQL不选择AVL树?
我们先来分析一波这个二叉树的诟病,在我们的数据结构中,二叉树有两大诟病:
1、树的高度太高,导致IO操作次数太高。树的高度决定了每次IO操作的次数,因为索引是硬盘级的,所以我们做一次IO操作,是会非常耗费系统性能的,我们每次插入可能只插入一条数据,但因为树的高度太高,导致要操作不知道多少次的IO操作才能完成这条数据的插入成功,严重影响系统性能。
2、IO的利用率太低。进行一次IO操作,我们一次拿到的只有一个关键字,一个数据区和两个子节点引用,并不能带回所有我们想要的数据。
多路平衡搜索树(B-树)
相比AVL树,拥有更多的子树/子节点,而这里的平衡是指,所有叶子节点都在一个水平线上。
公式:关键字的个数 = 子叉路 - 1
如何解决利用率问题
假设我们定义一个磁盘块是16K,比如我们要存储的关键字是int类型的,一个的大小应该是4byte,假如P1和P2保存指向地址算它2byte,还有一个数据区也不保存内容,只存磁盘的指向也算2byte,那么我们要加一个关键字只需要8byte。而我们现在一个磁盘块的大小是16K,按照理论值来算,一个磁盘块就可以存2000个关键字。如果说我们要一个磁盘2000个关键字来存的话,那么我们的子节点岔路就有2001路,做一次IO操作,我们能拿到满满的16K的关键字,那么IO利用率就上来了。
那么树的高度呢
树的高度也顺带的解决了,如果我们的根节点存2000个关键字,那么我们在第二层的时候就有2001路,而每一路又能存2000个关键字,那么两层树我们能存多少关键字?
加强版多路平衡搜索树(B+树)
与B树不同,B+树的数据是不保存在分支节点上的,而是全部保存在叶子节点上,同时叶子节点上的数据会形成一个双向链表,并且其中的关键字恰好有序。
优点(相比B-树)
1、根节点和支节点不存数据区,以便提高我们的IO利用率;
2、排序能力强;
3、基于索引的数据结构,扫表的能力强;
4、查询效率更加的稳定(树的高度 = IO次数)。
MySQL落地B+Tree索引方案
Myisam引擎
- .myi文件:保存B树的索引结构
- .myd文件:保存数据
Myisam引擎多索引存储机制
在我们的Myisam引擎中通过查询执行计划,会发现一种类型叫index merge(索引合并)。
我们的 where 中可能有多个条件(或者join)涉及到多个字段,它们之间进行 AND 或者 OR,那么此时就有可能会使用到 index merge 技术。index merge 技术如果简单的说,其实就是:对多个索引分别进行条件扫描,然后将它们各自的结果进行合并(intersect/union)。
MySQL5.0之前,一个表一次只能使用一个索引,无法同时使用多个索引分别进行条件扫描。但是从5.1开始,引入了 index merge 优化技术,对同一个表可以使用多个索引分别进行条件扫描。
Innodb引擎
与Myisam引擎不同,Innodb是没有index_merge的。
Innodb是基于主键将索引组织到一起的。
聚集索引:
聚集索引是指数据库表行中数据的物理顺序与键值的逻辑(索引)顺序相同;
通俗点讲,就是索引的叶子节点存的是整个单条记录的所有字段值。
注:
在Innodb引擎中,只有主键才是聚集索引,其它索引都是非聚集索引
Innodb引擎多索引
假设我们要查询name=zhang3的数据,它会基于辅助索引找到zhang3所对应该的主键id 101,再基于101去主键索引进行二次的扫描,找到数据,这一个过程叫回表。
为什么要回表
Myisam引擎是结构与数据分开存储的,结构树里只保存数据所在的地址,因此树的结构发生变化并不会影响数据的所在地址。
但Innodb是将数据直接保存到叶子节点上的,B+树有个特性,就是它为了要保持绝对平衡的关系,每当插入数据的时候,都有可能进行分页合并的操作,这会导致如果辅助索引保存的是地址,如果主键索引修改后不维护辅助索引,那么你用这个地址是找不到原来数据的。但对Innodb来说,主键索引才是它的核心,不可能主键索引被修改后还要通知辅助索引去进行维护,所以辅助索引保存的永远都是数据对应的主键id,这样无论主键索引的B+树怎么改变树结构,都能通过id去进行查找。
关于索引的各种原则
列的离散性
区分度低的走索引反而影响性能,以性别为例,那么当我们需要找一个性别为1的数据的时候,我们应该找谁?
最左匹配原则
这里的最左匹配和我们平时的不一样,它是从左往右一个字符一个字符的对比
假设abc和adc进行比较,根据ASCII码自然排序
abc 97 98 99
adc 97 100 99
所以adc是比abc要大的
联合索引
-
单列索引(是一种特殊的联合索引,只采用表当中一列作为索引)
-
节点中关键字[name]
-
create index idx_name on users(name)
-
-
联合索引(采用表当中某几列作为索引,同时会基于关键字的顺序来进行排列)
-
节点中关键字[name, phoneNum, age]
-
create index idx_name_phoneNum_age on users(name, phoneNum, age)
-
-
思考题
-
针对联合索引users(name, phoneNum, age)
-
select * from users where name = "张三丰" and phoneNum > 13666666666 and age > 20
-
查询语句会在联合索引中匹配哪些列
- 只会用到name和phoneNum,首先name是个等值查询,所以它会用到name;然后phoneNum是范围查询,因为这里用了范围查找,那么对后面的查询来说,离散性就会大打折扣,所以在MySQL就规定了,如果用了范围之后匹配就不匹配了,它就直接停止了。
-
覆盖索引
通过索引项的信息可直接返回所需要的查询列,则该索引称之为查询SQL的覆盖索引
例:
表:User Innodb引擎
索引:PK(id) key(name, phoneNum) unique(userNum)
下面哪些SQL使用了覆盖索引
select userNum from teacher where userNum = ?; -- 是
select * from teacher where name = ?; -- 不是
select id, userNum from teacher where userNum = ?; -- 是
select name, phoneNum from teacher where userNum = ?; -- 不是
select phoneNum from teacher where name = ?; -- 是
覆盖索引用于减少回表操作,因为需要查询的数据能够直接从辅助索引获取,进而不需要进行回表查询,以提高性能。
最后
附上一首打油诗:
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
Like百分写最右,覆盖索引不写星;
不等空值还有or,索引失效要少用。
来源:CSDN
作者:qq813997065
链接:https://blog.csdn.net/qq813997065/article/details/103600943