MySQL——B+Tree、索引及使用时优化

索引结构

MySQL目前提供了以下四种索引

  • BTREE索引:最常见的索引类型,大部分索引都支持B树索引
  • HASH索引:只有MEMORY引擎支持,使用场景简单
  • R-tree索引(空间索引):空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
  • Full-text(全文索引):全文索引也是MyISAM的一个特殊索引类型,主要用于全文索引,InnoDB从MySQL5.6版本开始支持全文索引
    InnoDB引擎MyISAM引擎Memory引擎
    BTREE索引支持支持
    HASH索引不支持不支持
    R-tree索引不支持支持
    Full-text5.6版本之后支持支持

通常所说的索引,若为特别指出,基本值B+树(多路搜索树,并不一定是二叉树)结构组织的索引。其中聚集索引、复合索引、前缀索引、唯一索引默认都是使用B+tree树索引,统称为索引

BTREE结构

Btree又叫多路平衡树,一颗m叉的btree特性如下:

  • 树中每个节点最多包含m个孩子
  • 除树根节点外,每个节点至少有[ceil(m/2)]个孩子
  • 若根节点不是叶子节点,则至少含有两个孩子
  • 所有的叶子节点都在同一层
  • 每个非叶子节点由n个key与n+1个指针组成,其中[ceil(m/2)-1]<=n<=m-1

以5叉BTREE为例

插入步骤

key的数量:[ceil(m/2) - 1]<=n<=m-1。所以2<=n<=4。当n>4时,中间节点分裂到父节点,两边节点分裂
以插入C N G A H E K Q M F W L T Z D P R X Y S数据为例
演变过程:

  1. 首先,结点空间足够,4个字母插入相同的结点中
    在这里插入图片描述

  2. 再插入H,n>4,空间不够,中间元素G字母向上分裂到新的节点(当H插入的时候ACGHN,其中中间节点为G,所以是G向上分裂)
    在这里插入图片描述

  3. 插入E,K,Q不需要分裂,各节点空间足够
    在这里插入图片描述

  4. 插入M,在HKNQ块插入M(HKMNQ中间元素是M),元素M向上分裂到父节点G,HK与NQ分裂
    在这里插入图片描述

  5. 插入F、W、L、T不需要分裂
    在这里插入图片描述

  6. 插入Z,Z大于M,走最右边指针,指向NQTW的节点,在这个节点中NQTW插入Z,Z排序后是最大,中间元素为T,但是此时n最大为4,T需要向上分裂,同时NQ和WZ分裂
    在这里插入图片描述

  7. 插入D,与上述插入Z同样的步骤,D是中间元素,向上分裂,AC和EF分裂,而D插入到父节点中还能保持平衡;然后继续插入P、R、X、Y都满足Btree特性,不需要分裂
    在这里插入图片描述

  8. 最后插入S,S大于M小于T走第四个指针,向NPQR中插入,插入后事NPQRS,key个数超过4了,所以中间元素Q向上分裂,同时NP、RS分裂;Q插入到父节点中DGMT,变成DGMQT,超过4,中间元素M向上分裂,同时DG和QT分裂
    在这里插入图片描述

删除步骤
  • 首先查找B树中需删除的元素,如果该元素在B树中存在,则将该元素在其结点中进行删除,如果删除该元素后,首先判断该元素是否有左右孩子结点,如果有,则上移孩子结点中的某相近元素到父节点中,然后是移动之后的情况;如果没有,直接删除后,移动之后的情况
  • 删除元素,移动相应元素之后,如果某结点中元素数目(即关键字数)小于ceil(m/2)-1,则需要看其某相邻兄弟结点是否丰满(结点中元素个数大于ceil(m/2)-1),如果丰满,则向父节点借一个元素来满足条件;如果其相邻兄弟都刚脱贫,即借了之后其结点数目小于ceil(m/2)-1,则该结点与其相邻的某一兄弟结点进行“合并”成一个结点,以此来满足条件。

例如依次删除上述BTREE中的H、T、R、E
在这里插入图片描述

  1. 首先删除元素H,当然首先查找H,H在一个叶子结点中,且该叶子结点元素数目3大于最小元素数目ceil(m/2)-1=2,则操作很简单,咱们只需要移动K至原来H的位置,移动L至K的位置(也就是结点中删除元素后面的元素向前移动)
    在这里插入图片描述
  2. 删除T,因为T没有在叶子结点中,而是在中间结点中找到,发现它的继承者W(字母升序的下个元素),将W上移到T的位置,然后将原包含W的孩子结点中的W进行删除,删除W后,该孩子结点中元素个数大于2,满足平衡条件,无需进行合并操作。
    在这里插入图片描述
  3. 删除R元素,R在叶子结点中,但是该结点中元素数目为2,删除导致只有1个元素,已经小于最小元素数目ceil(5/2)-1=2,而由前面我们已经知道:如果其某个相邻兄弟结点中比较丰满(元素个数大于ceil(5/2)-1=2),则可以向父结点借一个元素,然后将最丰满的相邻兄弟结点中上移最后或最前一个元素到父节点中(有没有看到红黑树中左旋操作的影子?),在这个实例中,右相邻兄弟结点中比较丰满(3个元素大于2),所以先向父节点借一个元素W下移到该叶子结点中,代替原来S的位置,S前移;然后X在相邻右兄弟结点中上移到父结点中,最后在相邻右兄弟结点中删除X,后面元素前移。
    在这里插入图片描述
  4. 删除元素E,因为E所在的结点数目刚好满足最小平衡所需条件元素个数,而相邻的兄弟结点也是同样的情况,删除一个元素都不能满足条件,所以需要该节点与某相邻兄弟结点进行合并操作;首先移动父结点中的元素(该元素在两个需要合并的两个结点元素之间)下移到其子结点中,然后将这两个结点进行合并成一个结点。所以在该实例中,咱们首先将父节点中的元素D下移到已经删除E而只有F的结点中,然后将含有D和F的结点和含有A,C的相邻兄弟结点进行合并成一个结点。
    在这里插入图片描述
    但是虽然D元素满足ACDF的平衡条件,而G节点不满足平衡条件,元素个数小于ceil(5/2)-1,而所有相邻节点中元素个数都刚刚满足平衡,不能像第二步骤中那样借,只能像第三步骤那样合并,与兄弟结点进行合并成一个结点,而根结点中的唯一元素M下移到子结点,这样,树的高度减少一层
    在这里插入图片描述

B+TREE结构

B+Tree为Btree的变种,区别为:

  • m叉B+Tree最多含有m个key,而BTREE最多含有m-1个key
  • B+Tree的叶子节点保存所有key信息,依key大小顺序排列
  • 所有非叶子节点都可以看做key的索引一部分
    在这里插入图片描述

由于B+Tree只有叶子节点保存key信息,查询任何key都要从root走到叶子。所以B+Tree的查询效率更加稳定

MySQL中的B+TREE

MySQL索引数据结构对经典的B+TREE进行了优化。在原有的B+Tree基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能
在这里插入图片描述

索引分类

  • 单值索引(单列索引):一个索引只包含单个列,一个表可以有多个单列索引
  • 唯一索引:索引列的值必须唯一,但允许有空值
  • 复合索引:即一个索引包含多个列

索引语法

创建索引

-- 创建索引的时候不指定索引的类型的时候默认使用的是B+TREE索引

-- 方式一:使用create的方式创建索引
create [UNIQUE|FULL TEXT|SPATIAL] INDEX index_name [USING index_type] on table_name(index_col_name,...);
-- 方式二:使用alter方式创建索引
-- 普通的索引
alter table table_name add index index_name(columnName_list);
-- 唯一索引(索引的列里面的值除了null可以多次出现,其余的值必须唯一)
alter table table_name add unique index index_name(columnName_list);
-- 全文索引
alter table table_name add fulltext index_name(columnName_list);

查看索引

show index from table_name;

删除索引

drop index index_name on table_name;

索引失效情形

定义了索引而索引失效的分析

  • 情形一:对使用索引的字段在条件查询的时候使用了函数
  • 情形二:
    例如一张表中字段name,status,address构成了复合索引,顺序是create index name_status_address_index on table_name(name,status,address),其中name、status、address都是varchar类型的
    • 等值条件查询时where name = 'aa' and status ='1' and address = '苏州市'都走索引
    • 条件查询时where name = 'aa' and status >'1' and address = '苏州市'由于status使用了范围查询,右边的列不走索引,所以此条件中只有name和status走索引,address不走索引
    • 等值条件查询时where name = 'aa' and address = '苏州市'根据最左前缀法则,只有name走了索引
    • 等值条件查询时where address = '苏州市'根据最左前缀法则,没有索引可走
    • 等值条件查询时where status = '1' and address = '苏州市' and name = 'aa'都走索引
    • 等值条件查询时where name = 'aa'走name索引
  • 情形三:
    上述案例中索引情形
    等值条件查询时where name = 'aa' and status = 1只有name走索引,status涉及了隐式转换,不走索引
  • 情形四:
    • 使用or关联条件的时候,如果or前面的字段使用了索引,而后面的字段没有索引,那么是不走索引的
      例如
select name,status,address where name = 'aa' or remark = 'bb';
-- 使用explain分析的时候name也没有走索引,索引失效了

select name,status,address where name = 'aa' and remark = 'bb';
-- 使用explain分析的时候name是走索引的,与or相反
  • 模糊匹配的时候,like的字段如果使用了%开头,也是不走索引的,但是单独以%结尾走索引,这种情况可以使用覆盖索引的方式解决,即要查询的字段有索引用到
-- 还是上述的索引案例,这里id字段是表table_name的主键,所以是有id字段索引的;
-- 此处即使后面模糊匹配使用的是%开头了,但是explain执行下面的情形的时候我们发现还是走name_status_address_index名字索引
select id,name,status,address where name like '%aa%';
  • 使用in和not in的时候,使用in走索引,not in不走索引(8版本之前是这样,8版本之后都走索引)
    有表信息,里面sellerid为主键,name,status,address为上述描述的联合索引
    在这里插入图片描述
    5.7版本分别执行in和not in的案例
    在这里插入图片描述
    8版本执行案例,in和not in都走索引
    在这里插入图片描述
  • 情形五:

如果MySQL评估使用索引比全表更慢,则不使用索引,如上面情形四种in和not in案例的数据,里面sellerid为主键,name,status,address为上述描述的联合索引,现在再设置一个address的单独的索引create index idx_seller_address on seller(address);
当执行select * from seller where name = '北京市';我们发现即使使用了定义了索引,但是使用expalin的时候却没有像我们预期的那样,而是走的全表索引,'北京市’的太多,估算不走索引,走全表扫描;
在这里插入图片描述
但是执行select * from seller where name = '西安市';这个时候,我们发现依旧走idx_seller_address索引。
在这里插入图片描述
也就是说索引中的某个字段的值占据的比例特别大,基本占据了全表的记录,那么就不走索引,直接全表扫描

  • 情形六
    is null和is not null有时走索引,有时不走索引

就像上面所说的那样,字段占据全表的记录多为null的话执行is null就不走索引,走is not null就会走索引;相反索引字段中值为null少的话,执行字段is null的时候走索引,走is not null就不走索引

例如数据在这里插入图片描述
name字段有索引
在这里插入图片描述
当执行elplain select * from t_user where name is null;的时候
在这里插入图片描述
当执行elplain select * from t_user where name is not null;的时候
在这里插入图片描述

SQL优化的细节

  • 尽量使用索引,索引使用情形配合explain分析
  • 查询时使用到索引的字段,查询的结果尽量在索引的字段内(也就是尽量使用覆盖索引),避免回表(因为索引只记录了索引的数据,而不记录一整行的记录数据)

例如上述的复合情形

select * from table_name where name= 'aa';
-- 此sql语句使用explain分析的时候可以看到Extra信息为Using index Condition,就是说查询的结果还需回表查询所有信息

select name,status,address from table_name where name= 'aa';
-- 此sql语句使用explain分析的时候可以看到Extra信息为Using where;Using index,就是说,查询直接用到了索引中的数据,不用再回表查询

select name,status,remark from table_name where name = 'aa';
-- 此sql语句使用explain分析的时候可以看到Extra信息为Using index Condition,就是说查询的结果中remark字段不再符合索引中,还需回表查询信息,所以extra为Using index Condition

大批量导入数据

大批量导入数据使用load指令导入大批量数据的时候

  • 数据的最好已排好主键顺序
-- load指令,其中filepath,table_name为具体文件路径,和表名,','表示字段值之间以逗号分隔,\n表示换行符
load data local infile 'filepath' into table `table_name` fields terminated ',' line terminated '\n';
  • 第二点当表结构有唯一性校验的时候,导入之前关闭唯一性校验,导入后开启
-- 会话级关闭
set UNIQUE_CHECKS=0;
-- 导入完成后,再开启
set UNIQUE_CHECKS=1;
  • 第三点,手动提交事务,在导入前开启手动提交事务,导入后再关闭手动提交,因为默认是关闭的

insert时候优化的细节

多数据插入的时候尽量将数据拼接在一起,开启手动提交事务,被插入的数据记录对应的主键有序

order by排序的时候

有两种情形在explain的extra中的信息,一种执行时使用到索引using index,一种执行时没有使用到索引Using filesort
例如有emp表,表中的age和salary是复合索引
执行下面的情形始终在explain的extra是filesort,即使order by字段建立了索引,但是没有使用到
在这里插入图片描述
但是在查询的使用覆盖索引的时候,即查询的字段只有索引内的字段,但是没有索引外的字段,下面是只有索引内的字段展示;一旦select后面的字段有除索引定义的字段外的字段,如name的时候就不走索引了;当然也会出现一种情形,即使使用了覆盖索引,而在排序的条件,一个字段升序,一个字段降序,又会出现Using filesort的情形了;两个字段同时升序或同时降序也会用到索引,但是字段必须按照索引定义的时候的字段先后顺序,不然即便是同样的排序方式,字段先后顺序变了,也会出现Using filesort的情形;一个字段增降序同样用到索引
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对出现filesort的优化

通过创建合适的索引,能够减少Filesort 的出现,但是在某些情况下,条件限制不能让Filesort消失,那就需要加快Filesort的排序操作。对于Filesort,MySQL 有两种排序算法:
1)两次扫描算法: MySQL4.1之前,使用该方式排序。首先根据条件取出排序字段和行指针信息,然后在排序区sort buffer中排序,如果sort buffer不够,则在临时表temporary table中存储排序结果。完成排序之后,再根据行指针回表读取记录,该操作可能会导致大量随机I/O操作。
2.)一次扫描算法: 一次性取出满足条件的所有字段,然后在排序区sort buffer中排序后直接输出结果集。排序时内存开销较大,但是排序效率比两次扫描算法要高。
MySQL通过比较系统变量max_length_for_sort_data的大小和Query语句取出的字段总大小,来判定是否那种排序算法,如果max_length_for_sort_data更大,那么使用第二种优化之后的算法;否则使用第一种。
可以适当提高sort_buffer_sizemax_length_for_sort_data系统变量,来增大排序区的大小,提高排序的效率。
在这里插入图片描述

group by语句的优化

由于**GROUP BY实际上也同样会进行排序操作**,且与ORDER BY相比,GROUP BY主要只是多了排序之后的分组操作。当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。所以,在GROUP BY的实现过程中,与ORDER BY一样也可以利用到索引。
如果查询包含group by但是用户想要避免排序结果的消耗,则可以执行order by null禁止排序。如下:
在这里插入图片描述

而上面中的Using temporary是使用到了临时表的含义,因为age没有索引
在这里插入图片描述

子查询优化

MySQL4.1版本之后,开始支持SQL的子查询。这个技术可以使用SELECT语句来创建一个单列的查询结果 ,然后把这个结果作为过滤条件用在另一个查询中。使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,查询是可以被更高效的连接(JOIN )替代。
在这里插入图片描述

or优化

上面说到or关键字的时候,若or前面的字段有索引,后面的字段没有索引,查询时整个是不走索引的;而若有复合索引的时候,例如有一个复合索引create index idx_age_salary on table_name (age,salary);在使用查询的时候select * from table_name where age = 20 or salary = 3000;这条语句即便使用到了age和salary,而age和salary是复合索引,但实际查询的时候不会走索引
建议使用union替换or

  • 情形一:
    在这里插入图片描述
  • 情形二:
    此处使用id和age分别走不通索引名的索引,但是type是index_merge
    在这里插入图片描述
    再看看使用union的情形,此处的type分别是常量const和ref,效率还是高于上面的index_merge
    在这里插入图片描述

limit分页查询优化

分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头疼的问题就是limit 2000000,10 , 此时需要MySQL排序前2000010记录,仅仅返回2000000-2000010的记录,其他记录丢弃,查询排序的代价非常大

  • 优化思路一:在主键上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容
    在这里插入图片描述
  • 优化思路二:此方法有一定局限性,只适用于id自增的表,且主键不能断层,(若中间出现某个记录被删除,id出现断层,结果就不准确),可以把limit查询转化成某个位置的查询
    在这里插入图片描述

索引提示

use index建议MySQL去使用哪个

使用use index人为干预数据库走具体的索引,使用use index仅仅是提供参考,MySQL不一定使用
在这里插入图片描述
在执行select * from tb_seller where name = ‘aa’;的时候,可能走两个索引,虽然最终只走一个索引,看下explain具体情形,在使用use index(index_name)的时候就会指定了
在这里插入图片描述

ignore index忽略索引

有点类似于上面的use index,不过是反着来的感觉
在这里插入图片描述

froce index强制使用某个索引

例如某个字段有索引,但是执行的时候遇到了索引值的记录过多的情形,MySQL会认为不走索引更高效的情形,就不走索引,而是全表扫描了
在这里插入图片描述
在这里插入图片描述
使用force index后的情况
在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值