1、B tree 与B+ tree 的区别

B tree 一个节点可以存放多个数据,数据个数和Degree有关 并且内部数据根据大小排序

+tree看起来则比B tree要宽一些,这是因为B+tree的叶子节点数据都在非叶子节点上冗余了一份,并且是有序的。一个节点上也可以有多个元素,这点和B tree很类似。并且B+tree的叶子节点上是有指针的.
2、Innodb中的B+Tree是如何产生的呢?
select VERSION(); #查询版本号
show global status like 'Innodb_page_size'; # 查询系统变量
select 16384/1024; # 页大小


可以看到,在版本5.7.16的情况下,一个可以存放16384条的数据,除以1024就是16KB左右
在Innodb引擎下,读数据或者是写数据,单位就是页。
创建一张表
CREATE TABLE t1(
`a` int PRIMARY key,
`b` int,
`c` int,
`d` int,
`e` VARCHAR(20)
) ENGINE=InnoDB;
select * from t1;
show index from t1;执行 select * from t1 会发现这张表为空。
执行 show index from t1 会发现 mysql底层仍然会认为这张表底层 这个 B+Tree是 B Tree的一个升级版。

插入数据
insert into t1 values(4,3,1,1,'d');
insert into t1 values(1,1,1,1,'a');
insert into t1 values(8,8,8,8,'h');
insert into t1 values(2,2,2,2,'b');
insert into t1 values(5,2,3,5,'e');
insert into t1 values(3,3,2,2,'c');
insert into t1 values(7,4,5,5,'g');
insert into t1 values(6,6,4,4,'f');插入的时候,这些数据我并没有按照顺序排列,插入表中,发现仍然可以根据主键顺序进行排列

从磁盘去数据时,按照一页一页的取,以 t1表为例,a、b、c、d、四个字段都为int类型,e字段varchar(20)类型,就按UTF8 mb4
那么大小就是 4*4+4 = 20b ,换算后,这8条数据都达不到一页(16KB)的大小,那么在读取数据时,能一次性全部读取放入内存,进行一次磁盘io
新建一张表时,里面任何数据都没有,那么往里面插入数据时,则会新开一页,

插入数据时,按照顺序执行,首先插入 4,3,1,1,'d' ,后续插入 1,1,1,1,'a' 时,底层它会按照主键id这个字段,会把这条数据放在 4,3,1,1,'d'的前面

这样做有一个小小的好处: 比如
执行select * from t1 where a=3;
从 第一行数据一次往下找,发现 a字段为4的这一组数据后,就没必要继续往下找了,提高查询效率

假设我现在要查 a = 30000的这条数据,如果依次查找的效率很低,有没有什么办法能提高效率呢?
作者 想到了一个好办法,根据书的页目录,来确定具体的数据

查询 a = 3,只能在第一组页目录下的数据区域,不可能在第二组,因为页目录中的值存的是第二组数据的最小值。
在查询 a = 30000
根据页目录查找,只可能在第二组
假设我这一页只能存储四条数据,现在又insert into t1 values(5,2,3,5,'e')新增了一条数据。这条数据会放到哪一页呢?
其实在新增时,底层会断开链表中的指针,把8,8,8,8,'h'放到新开的一页,把5,2,3,5,'e'这一组数据拼接到4,3,1,1,'d'的下面

原来的一页中,断开链表插入新数据,在移动原来的一组数据时,有一定性能影响,mysql官方也推荐使用连续的自增id

假设我现在8条数据全部插入,并按照顺序存满两页,那么页与页之间的页头也有一个指针。
现在执行一个sql select * from t1 where a = 6。Innodb拿到这条sql会怎么执行呢?
我们处于 "上帝视角" 知道 a = 6在第二页,但是innodb并不知道。所以它会先取出第一页查找,发现没有后会顺着next指针,再去第二页查找。
假设 不断的往这张表里新增数据 不断的新增页,现在要查找 a = 60000。依然时先从第一页开始查找,再顺着next指针继续往下查找。就是在遍历这个链表。这样效率就会非常的慢。
优化思路:
可以为这图中的页 添加 一个 页目录 ,假设现在有4页,那么可以为这个页目录添加4组数据,值就是这4页中的最小值

假设我现在再执行 select * from t1 where a = 60000呢
mysql底层就会根据页目录来确定需要定位的数据属于哪一页
3、高度为3的B+ tree能存多少数据?

这页目录也能存16KB,这张图存了一个主键值和一个指针,在Innodb引擎下,大小就是 4b + 6b = 10b,这一对(主键值+指针)大小就是10b。
那一页能存放多少组这样的数据呢?
Innodb默认,一页大小为16KB,那就是16*1024/10

可以存放1638组这样的数据。也就说新开 一个 页目录,按照主键值+指针的组 ,可以管理1638页数据。
那数据页又能存放多少数据呢?
假设 某一张表 里的 一条数据大小为1KB.那我一页又能存储多杀数据呢?按照主键值时int类型来看。就是 1638*(16kb/1kb);

这是一个2层B+Tree可以存放的数据。
那三层的B+Tree呢?
结构类似这这种 ,按照 主键值时int类型,一条数据大小为1kb计算 1638*1638*(16/1)

大概就是可以存储 42928704条数据。
4、Innodb是如何支持范围查找能走索引的?
还是t1表
explain select * from t1 where a = 6; type = const
explain select * from t1 where b = 1; type = ALL
explain select * from t1 where a > 6; type = range主键索引如图:

执行 a > 6时,底层会先执行 a = 6,索引会从目录页(非叶子节点)定位到第三页数据页(叶子节点)。找到主键值等于6的这条数据,直接返回这条数据之后的数据。
那 a < 6 呢? 同样的思路

那为什么可以找到目标数据前面的数据呢。其实B+Tree的叶子节点是双向指针。


图来自Mysql官方
5、为什么要遵顼最左前缀原则才能走索引?
先来看看联合索引
执行 create index idx_t1_ bcd on t1(b,c,d) 为bcd字段创建一个联合索引底层会生成一颗 B+Tree。以bcd字段的值进行排序,如b字段的值相同,则会比较c字段的值
执行

explain select * from t1 where b = 1and c = 1 and d = 1;
传入的条件是 111 对比后定位第一页(叶子节点) 。定位后直接返回吗?这要根据传入的sql,这个sql是select *,所以需要拿到主键值去主键索引查找。所以下方还得存对应的主键。

根据主键值去主键索引查找后返回。这一过程,称之为 回表查询
利用 索引 和传入的where条件的顺序并没有关系
如: explain select * from t1 where c = 1and d = 1 and = 1
如图 照样可以利用索引

最左前缀原则,和这个 where条件的顺序,是没有关系的。
和 你给的 条件的值 和 这个走索引的顺序是不是一样,能不能匹配的到。
比如 这个 bcd索引,就看你where给的条件有没有b这个字段,有 就可以走 这个索引,没有就 利用不了 这个索引。
分别执行
explain select * from t1 where c = 1 and d = 1
explain select * from t1 where b = 1 and c = 1 and d = 1
发现第一条sql不能利用到索引,第二条sql可以。如图


6、范围查找导致索引失效原理分析
执行sql explain select * from t1 where b = 1可以走索引
那 explain select * from t1 where b > 1呢? 前面的 a > 1可以走索引,这个b > 1是不是也可以呢?

可以看到走的是全表扫描。这是因为,底层首先会根据传入的条件 定位 到 b = 1这条数据,然后定位到这条数据之后的数据。但是执行的是select *。底层会拿到后面的数据的主键值去主键索引 进行 回表。
全表扫描:根据传入的条件一个个遍历,找到后返回
走索引: 先定位到 b = 1这条数据,然后定位 b > 1的数据,并拿到主键值,再去主键索引里回表
那如果改下 sql explain select * from t1 where b > 6呢?

发现又可以走索引了。这又是为什么呢? 这是因为在我这张t1表里总共就8条数据
bcd联合索引里

定位到 b > 6后,发现这后面就一条数据,拿到主键值,去主键索引里查找,只用回表一次,速度比直接走全表扫描要快一些。
也就是 如果 判断 直接走 全表扫描的速度要比走索引快,就会走 全表扫描。
7、覆盖索引的底层原理
SQL : explain select * from t1 where b > 1 不能走索引
那如果不查* 就查b呢
explain select b from t1 where b > 1;

发现可以走索引,并且 Extra 时 Using where;Using index。意思是 索引覆盖 你要查询的这个字段正好在这个索引里有。
首先会定位得到 b = 1这条数据,得到 b 字段大于1的数据。虽然走bcd联合索引得到的是不完整的数据,但是sql查询只需要b字段,可以直接返回。
那带上a字段呢?比如 b,c,d,a
explain select b,c,d,a from t1 where b > 1;

发现可以利用到索引,这是因为bcd这个联合索引中,存储了a字段的值,不需要回表。
但是带上e字段,就需要进行回表,就会走全表扫描了
explain select a,b,c,d,e from t1 where b > 1; 如图

8、索引扫描底层原理
先执行一条sql: explain select b from t1;

where条件都没给,依然可以走 bcd 索引.type = index 会从 bcd 索引中扫描返回数据。会比全表扫描更快一点
换成c呢
explain select c from t1;

依然可以走索引,这是因为底层会在 bcd 联合索引中查找遍历返回,并不需要给最左的字段。
9、order by为什么会导致索引失效?
先执行一条sql : explain select * from t1 order by b,c,d; 这条sql能不能走索引?

执行后发现走的是全表扫描。
没有给where条件,底层执行就会从主键索引里的8条数据全部取出来,因为数据比较少,所以会直接在内存中排序。
那如果走 bcd 索引呢? 没有给where条件,就不能很好的利用索引页目录。会直接去叶子节点中获取,获取出来的数据就是按照 b,c,d的字段顺序排序,但是 查询的是 select * 获取出来的数据返回之前会回表查询8次。所以底层认为走全表扫描要比走 bcd 联合索引快。
Extra显示Using filesort的意思是,mysql查询以后在内存中进行排序。

explain select b from t1 order by b,c,d; 更换查询条件后,就可以走索引,因为只需要B字段,不需要排序,也不需要回表

10、Mysql中数据类型转换有哪些要注意的?
首先为e字段建议一个索引 create index idx_t1_e on t1(e); -- 为e字段建立索引
底层也会维护一颗B+ Tree

分别执行以下SQL
explain select * from t1 where a = 1; --- 可以走索引
explain select * from t1 where e = '1'; --- 可以走索引
explain select * from t1 where 'a' = 1; --- 可以走索引
explain select * from t1 where e = 1; --- 不可以走索引运行结果如下




后面两条sql为什么为这样呢?
可以先执行 select 'a' = 0,select 'b' = 1;这两条sql看看

当两边类型不匹配时,字符会转换成数字,那转换成哪个数字呢,ASCII码?都不是,会转换成数字 0
所以 select 'a' = 0时 输出 1,而当 select 'b' = 1时会输出0
当然,也有特殊的情况下,比如 储存的值时 字符 '123'这种,那么执行select '123' = 123 会输出1 表示等式两边相等

另一种特殊情况就是,比如我手动修改a的某一行的值为0

那么我执行一条sql: select * from t1 where a = 'c'; 一样可以查询出表中第一条数据。这是因为执行中会把字符’c‘转换成0
如图

11、对字段进行操作导致索引失效原理
如上:
explain select * from t1 where 'a' = 1; --- 可以走索引
explain select * from t1 where e = 1; --- 不可以走索引这两条sql,为什么上面一条可以走索引,下面一条不可以呢?
这是因为 ,执行时,需要将字段转换成数字类型,但是e是个字段,varchar类型。
所以在执行 sql 前,会把字段e中的内容全部转换成数字 0 ,再执行这个sql。
这样查询就会非常慢,但是总归要进行这一步。
现在有个sql select * from t1 where e = 0; 他会把所以的全部查询返回
如图:

EXPLAIN select * from t1 where e = 1; 查询结果是

如果想让这条sql走索引,就需要改字段e的这颗 B+Tree,先把存储的字符全部转换成0,但是这会导致整个 B+Tree混乱,导致出现新的问题
假设

这颗 B+ Tree 字符 '1'转换成 数字 1,字符 b 转换成 数字 0, 很明显,转了之后,这颗B+Tree已经被破坏掉了。
如果这个sql要去走索引,代价是非常大的,而且其他sql也在用这个索引。
所以这个sql利用不了索引。
12、Mysql慢查询如何优化
持续更新
13、Mysql聚簇索引和非聚簇索引的区别
都是B+tree的数据结构
聚簇索引:将数据存储与所有放到了一块、并且是按照一定的顺序组织的,找到索引也就找到了数据,数据的物理存放数据与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的。
非聚簇索引:叶子节点不存放数据、存储的是数据行地址,也就是说根据索引查找数据行的位置再取磁盘查找数据,这个就有点类似一本书的目录,比如要找第三章第一节,那先在这个目录里面找,找到对应的页码后再去对应的页码看文
优势:
1、查询通过聚簇索引可以直接获取数据,相比非聚簇索引需要第二次查询(非覆盖索引的情况下)效率要高
2、聚簇索引对于范围查询的效率很高,因为其数据是按照大小排列的
3、聚簇索引适合用在排序的场合,非聚簇索引不适用
劣势:
1、维护索引很昂贵。特别是插入新行或者主键被更新导致要分页(page split) 的时候。建议大量插入新行后,选在负载较低的时间段,通过OPTIMIZE TABLE优化表,因为必须被移动的行数据可能造成碎片。使用独享表空间可以弱化碎片
2、表因为使用UUID(随机id)作为主键,使数据存储稀疏,这就会出现聚簇索引有可能有比全表扫描更慢,所以建表使用int的auto_increment作为主键
3、如果主键比较大的话,那辅助所以将会变的更大,因为辅助索引的叶子存储的是主键值:过长的主键值,会导致非叶子节点占用更多的物理空间
InnoDB中一定有主键,主键一定是聚簇索引,不手动设置、则会使用unique索引,没有unique索引、则会使用数据库内部的一个行的隐藏id莱当作主键索引。在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值。
MyISAM使用的是非聚簇索引,没有聚簇索引,非聚簇索引的两颗B+树看上去没有扫描不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,用过辅助键检索无需访问主键的索引树。
如果涉及到大数据量的排序、全表扫描、count之类的操作的话,还是MyISAM占优势些,因为索引所占空间小,这些操作是需要在内存中完成的。