b+树和b树的区别

优点:

(1)B+树的磁盘读写代价更低非叶子节点包含的信息更少,如果把同一节点的所有信息放在一个磁盘块中,则可以比B树放入更多的关键码。一次读入内存当中(读一个块)就能读入更多的关键码,所以降低了磁盘I/O总数。

(2)查询效率更加稳定对任何关键字的查找都必须从根节点走到叶子节点,路径长度相同,所以对每条数据的查询效率相当。

(3)B树在提高磁盘I/O性能的同时并没有解决元素遍历效率低下的问题。而B+树因为叶子节点有链指针存在,所以遍历叶子节点即可以实现对整棵树的遍历。而在数据库中基于范围的查询是非常频繁的,B+树就能更好的支持。

b+树的特性:

B+树是B树的一种变形,比B树具有更广泛的应用,m阶 B+树有如下特征: 

  (1) 除根结点以外,每个内部结点有m/2到m-1个孩子,如果等于m个孩子就要分裂

(2)所有的父节点都存在子节点中,都是子节点最大,或者最小的节点,所以叶子节点才会保存所有的数据

比如有个3阶的b+树构建

b树的构造:

针对m阶高度h的B树,插入一个元素时,首先在B树中是否存在,如果不存在,即在叶子结点处结束,然后在叶子结点中插入该新的元素。

  • 若该节点元素个数小于m-1,直接插入;
  • 若该节点元素个数等于m-1,引起节点分裂;以该节点中间元素为分界,取中间元素(偶数个数,中间两个随机选取)插入到父节点中;
  • 重复上面动作,直到所有节点符合B树的规则;最坏的情况一直分裂到根节点,生成新的根节点,高度增加1;

不同点:1、b树中的节点都是数据节点,b+树的非叶子节点是索引节点(加指针),叶子节点是数据节点

               2、b+数叶子节点 和 叶子节点上层有冗余节点,b树则没有

               3、b+树叶子节点有双向指针,b树则没有

为什么b+是能减少io读写的次数呢?

show GLOBAL STATUS like  'Innodb_page_size' ;  能够查询出数据页的大小有多少,数据页是每一次io读取的最小单元

可见而知:页大小为:16384/1024 = 16kb;b+树的索引节点和指针需要的数据很小,所以一个页中存放的索引节点很多,然后可以创建比较大n阶b+树,减少树的高度,从而减少io的读取次数

注意:b+树叶子节点存放的是 整个行的数据

innodb索引及优化

创建索引的几个规则:

1、列的离散型:离散型的计算公式:count(distinct column_name):count(*),就是用去重后的列值个数比个数。值在 (0,1] 范围内。离散型越高,选择型越好。

2、最少空间原则:前面已经说过,当关键字占用的空间越小,则每个节点保存的关键字个数就越多,每次加载进内存的关键字个数就越多,检索效率就越高。创建索引的关键字要尽可能占用空间小。

 覆盖索引及回表查找:

如:id为主键索引 ,  (name,scholl,banji)为辅助索引

id主键索引树: 

 

  (name,scholl,banji)辅助索引树:

关于explain知识点:一张图彻底搞懂MySQL的 explain - SegmentFault 思否

侧重关注以下几个字段:

1. id //select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序,如果相同则id如果相同,可以认为是一组,从上往下顺序执行
在所有组中,id值越大,优先级越高,越先执行
2. possible_keys //显示可能应用在这张表中的索引,一个或多个,但不一定实际使用到
3. key //实际使用到的索引,如果为NULL,则没有使用索引
4. ref //显示索引的哪一列被使用了,如果可能的话,是一个常数,哪些列或常量被用于查找索引列上的值
5. Extra //包含不适合在其它列中显示但十分重要的额外信息

6. type  type=all为全表扫描

覆盖索引:当使用索引的时候,如果查询出的数据在索引中就能够获取的到

比如:

 其中id就在(name,scholl,banji)辅助索引树的叶子节点,则不需要到主键索引树回表查询,extra = Using index 代表是覆盖索引

extra 为null则进行了回表查询

联合索引最左匹配原则:

如:(name,scholl,banji)辅助联合索引,会创建出name   ,  name scholl ,  name  scholl  banji 三个索引

EXPLAIN SELECT * FROM `selfinfo` where name = "yc";


EXPLAIN SELECT * FROM `selfinfo` where name = "yc"  and scholl = "丰中";


EXPLAIN SELECT * FROM `selfinfo` where name = "yc"  and scholl = "丰中" and banji = "1班";

条件有索引但是mysql不会走索引的情况:

1、in  (not in 用不到索引)

 EXPLAIN SELECT * FROM `selfinfo` where  name  in ("yc","yb");


 EXPLAIN SELECT * FROM `selfinfo` where  name  in ("yc","yb","yr","yd","yl","yu");

2、like

Where 条件中,like 9%, like %9%, like%9,三种方式都用不到索引。后两种方式对于索引是无效的。第一种9%是不确定的,决定于列的离散型,结论上讲可以用到,如果发现离散情况特别差的情况下,查询优化器觉得走索引查询性能更差,还不如全表扫描。

EXPLAIN SELECT * FROM `selfinfo` where  name  like "y%";

3、> 或者 <

EXPLAIN SELECT * FROM `selfinfo` where  name  > "y";

 

innodb中的hash索引  

 

最大的缺点不支持范围查询 

MyISAM执行引擎

MyISAM与InnoDB 数据结构的区别_fyygree的博客-CSDN博客_innodb和myisam的数据结构

ACID特性及底层实现     

 脏读:A事务执行过程中,B事务读取了A事务的修改。但是由于某些原因,A事务可能没有完成提交,发生RollBack了操作,则B事务所读取的数据就会是不正确的。这个未提交数据就是脏读(Dirty Read)

 两次读取,后来读的数据由于回滚了,不正确 

 幻读:B事务读取了两次数据,在这两次的读取过程中A事务添加了数据,B事务的这两次读取出来的集合不一样。

两次读取,后来读的数据增多了

不可重复读:B事务读取了两次数据,在这两次的读取过程中A事务修改了数据,B事务的这两次读取出来的数据不一样。B事务这种读取的结果,即为不可重复读(Nonrepeatable Read)

两次读取,前后读的数据不一致

mysql事务的4各级别:Read Uncommitted(读取未提交内容) ,Read Committed(读取提交内容),Repeatable Read(可重读),Serializable(可串行化)

mysql默认是的Repeatable Read(可重读)

Read Uncommitted(读取未提交内容) 会产生脏读,幻读,可重复读

Read Committed(读取提交内容)会产生幻读,可重复读

Repeatable Read(可重读)会产生幻读

深入学习MySQL事务:ACID特性的实现原理 - 编程迷思 - 博客园

分库分表技术实现

     1、数据库:

                垂直拆分:

                水平拆分:

      2、表:

               垂直拆分:

               水平拆分:

技术实现:

1、proxy:

2、jdbc拦截:

学习mycat:

查询优化器的优化过程

单表操作跟多表联合操作

     联合查找join:

            1、可能会造成索引失效

             2、如果大表跟大表之间相连,计算的压力很大

             3、不易维护,比如修改逻辑之类的

     单表查询的优点:

             1、比如用mybatis之类的可以加缓存

             2、易维护

redo.log,bin.log,undo.log等日志分析

不同点:

       1、bin log是属于msql ,redo log和undo log是属于innodb

       2、bin log主要用于主从同步,redo log 是记录innodb事务commit之后用于持久化数据到数据库,  undo.log 是用于innodb事务rollback,实现事务的原子性 和多版本并发控制(MVCC)

下面分别来说每个日志的作用:

undo.log(保证原子性,实现MVCC):

       

redo.log(保证持久性) ,固定大小的两个文件,循环写,两阶段提交

解决隔离性相关锁:

    共享锁:

      lock in share mode ,读写互斥

     事务A:

          BEGIN;

          select * from selfinfo LOCK IN SHARE MODE ;

     事务B:

          BEGIN;
            update selfinfo set name = "cccc"  where age = 12; //执行不了

    排他锁/独占锁(读写不互斥,写写互斥):

       update ,insert,delete自带排他锁,如果需要给select 添加排他锁,则 select for update 

       select * from selfinfo for update ; 添加的是表锁

       如果添加where条件,如果条件里面是索引,则添加的是行锁(可以多行),如果非索引,则为表锁

    间隙锁(配合MVCC解决幻读):

      对范围进行加锁,比如select * from tmp where  id > 3 for update,如果id为索引,则对于id>3的行加锁,防止id>3的行插入,如果id不为索引,则加表锁,解决幻读

    自增锁:

       针对自增字段来言

自增锁很明显是用于自增类型的操作,自增锁是表级锁,自增锁的作用是为了保证数据库的主键是自动递增的。其实这个锁主要就是用于拥有自增主键的数据表的插入操作,两个事务先后执行插入操作,第二个事务的插入操作则会被阻塞,因为需要保证主键是递增操作。

    意向锁:

    记录锁

         存在于包括主键索引在内的唯一索引中,锁定单条索引记录。

    间隙锁

          存在于非唯一索引中,锁定开区间范围内的一段间隔,它是基于临键锁实现的。其实针对一

         个范围来讲的

    临键锁

           存在于非唯一索引中,该类型的每条记录的索引上都存在这种锁,它是一种特殊的间隙

           锁,锁定一段左开右闭的索引区间。其实针对每一条记录来讲的,一条记录带有next-key锁

MVCC

    mvcc实现了 RC(Read Committed),RR(Repeatable Read)

    RC(Read Committed): 每一次读取都会获取最新的readView,所以可能会造成不可重复读

    RR(Repeatable Read):每次读取都会 用当前会话第一次读取的readView

    readView (一致性视图),是针对库而言的,不具体某张表,由所有未提交的事务id数据(数组里面最小的id为min_id)和已创建(包括已提交未提交)的最大事务id(max_id)

    如 [trx_1,trx_2,trx_3,trx_4,trx_5] trx_6

    undo log里面会为每张表维护一个历史操作的版本链表

    

 对于删除的情况可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将trx_id修改成删除操作的trx_id,同时在该条记录的头信息里面的标记位写上true,来表示当前记录已经被删除,在查询 时按照上面的规则查到对应的记录,如果delete_flag标记位为true,意味着记录已经被删除,则不反回数据

假如:

selfinfo表中有一条记录

当不断修改name的时候,undo日志就会维护这样一个版本链表

幻读及间隙锁

幻读:当前读,快照读

当当前读和快照读 混合使用的时候,会产生幻读

在可重读事务级别下,select * from ... where是快照读

                                    select * from .... where ... for update select * from .... where ... lock in share mode update .... set .. where ... delete from. . where ..是当前读

事务A 事务B
BEGIN; BEGIN;
select * from author where age = 20;
INSERT into author VALUES (28,'g25',20);
select * from author where age = 20;
COMMIT;

select * from author where age = 20;

select * from author where age = 20 for update;

此处产生幻读,因为事务A还没提交 

如果都为当前读,都为快照读就不会产生幻读

如果都为快照读,就是不加锁的情况

如果都为快照读,则需要加锁来实现

1、利用间隙锁

图一中,根据id列,我们可以分为几个区间:(无穷小,1),(1,5),(15,20),(20,25),(28,无穷大)。

当select * from author where id = 5 for update;的时候,INSERT into author VALUES (4,'g25',20);INSERT into author VALUES (6,'g25',20);都不会加锁

只要这些区间对应的两个临界记录中间可以插入记录,就认为区间对应的记录之间有间隙。

BEGIN; BEGIN;
select * from author where id = 2 for update;

INSERT into author VALUES (3,'g25',20);

间隙锁加锁原理

假设现在有一张表,有id(索引)和其它列若干,下面有一条sql语句,下面的B+树图是该表的索引,这条sql最终会扫描包含60,62的那个节点,最后发现找不到这条记录。虽然没找到,但是扫描了60~65这之间的索引,所以InnoDB就必须保证下次再执行这条sql时,表中绝对不能插入id为61,63,64的数据,否则就会出现幻读。所以InnoDB就会给[60,65)加间隙锁。

select * from user where id = 63 for update

如果是按索引范围查询那就简单了,直接给大于63的区间加间隙锁就好了。

select * from user where id > 63 for update

以非索引字段为条件进行扫描

在按非索引字段进行检索时只能通过扫描全表来查找,自然而然要给整张表加锁。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐