mysql面试题目

 昨天晚上无意中翻译到baidu的 dba(mysql,redis) 面试题,阅读了一下,发现没有一个自己能完美解释的。这确实是温床导致的思维懒惰。

具体几个问题如下:

1:MySQL InnoDB存储的文件结构?

2:索引树是如何维护的?

3:数据库自增主键可能的问题?

4:  Redis的并发竞争问题如何解决?

5: 了解Redis事务的CAS操作吗?

首先我认为上述几个问题都是比较大的命题,没有一个全面且深刻的认识,很容易测试出面试者的技能边界。

 

问题一

,要求是理解innodb存储文件的结构,我认为这部分可以从3个方面进行阐述

a:从文件生成情况:

  下面截取别人的总结

.frm文件  无论在 MySQL 中选择了哪个存储引擎,所有的 MySQL 表都会在硬盘上创建一个 .frm 文件用来描述表的格式或者说定义; .frm 文件的格式在不同的平台上都是相同的。
.ibd文件 InnoDB 中用于存储数据的文件总共有两个部分,一是系统表空间文件,包括 ibdata1、 ibdata2 等文件,其中存储了 InnoDB 系统信息和用户数据库表数据和索引,是所有表公用的。
当打开 innodb_file_per_table 选项时, .ibd 文件就是每一个表独有的表空间,文件存储了当前表的数据和相关的索引数据。

  实际截图说明

mysql面试题目

 

 mysql面试题目

mysql面试题目

 

b:逻辑定义情况,这里大多数dba都能说说,段(segment),簇(extent),页(page),以下是一个简单的示意图

mysql面试题目

 

 至于mysql 存储引擎是如何管理上述的,涉及面太多,类似oracle的 管理机制,有双向链表等数据结构进行等等,我这里确实阐述不清。

 确实innodb 的表  文件生成情况和其他引擎一样,那如果只阐述这部分,我认为还没达到面试官的要求,因此我又找了些资料,有幸从《mysql运维内参》上看到一些资料。

c: 运行 show table status like 'tablename%' 我们可以看到有Row_format 这行。其中可能的值有

row_format

desc

Compact

is format supported by Antilope. It stores first 768 bytes of BLOB in case its value doesn't fit in page.

Dynamic


COMPACT is format supported by Antilope. It stores first 768 bytes of BLOB in case its value doesn't fit in page.

DYNAMIC is almost the same as COMPACT except only 20 bytes for each BLOB field is used. Benefits - more BLOB fields are possible in a record

COMPRESSED 

COMPRESSED is used for compressed tables. Hence its benefits.

   上面说明了常见的行存储格式,那么具体样子是什么的呢? 截图如下:

Compact:

mysql面试题目

Dynamic:

mysql面试题目

 

 具体行存储情况参阅:http://www.cnblogs.com/wade-luffy/p/6289183.html

问题二:索引树是如何维护的?

 

首先搞定B+树的原理,分裂情况这些基础知识,然后结合mysql的源码进行分析,此问题就好回答了。转载下blog

https://www.cnblogs.com/shenyixin/p/9957872.html,清晰的描述了B+树原理及分裂过程,请博主见谅

 

B+树索引结构

传统关系型数据库(Oracle/MySQL/PostgreSQL…),其主要的索引结构,使用的都是B+树。更有甚者,InnoDB引擎的表数据,整个都是以B+树的组织形式存放的。下图,是一个经典的B+树组织结构图(2层B+树,每个页面的扇出为4)

mysql面试题目

 

 

注意:

  • 此B+树,以InnoDB实现的B+树结构为准;
  • 此B+树,有5条用户记录,分别是1,2,3,4,5;
  • B+树上层页面中的记录,存储的是下层页面中的最小值(Low Key);
  • B+树的所有数据,均存储在B+树的叶节点;
  • B+树叶节点的所有页面,通过双向链表链接起来;

B+树的分裂

在上图B+树的基础上,继续插入记录6,7,B+树结构会产生以下的一系列变化:

插入记录6,新的B+树结构如下:

mysql面试题目

 

插入记录7,由于叶页面中只能存放4条记录,插入记录7,导致叶页面分裂,产生一个新的叶页面。

mysql面试题目

 

传统B+树页面分裂操作分析:

  • 按照原页面中50%的数据量进行分裂,针对当前这个分裂操作,3,4记录保留在原有页面,5,6记录,移动到新的页面。最后将新纪录7插入到新的页面中;
  • 50%分裂策略的优势:
    • 分裂之后,两个页面的空间利用率是一样的;如果新的插入是随机在两个页面中挑选进行,那么下一次分裂的操作就会更晚触发;
  • 50%分裂策略的劣势:
    • 空间利用率不高:按照传统50%的页面分裂策略,索引页面的空间利用率在50%左右;
    • 分裂频率较大:针对如上所示的递增插入(递减插入),每新插入两条记录,就会导致最右的叶页面再次发生分裂;

疑问:

传统50%分裂的策略,有不足之处,如何优化?接着往下看。

B+树分裂操作的优化

由于传统50%分裂的策略,有不足之处,因此,目前所有的关系型数据库,包括Oracle/InnoDB/PostgreSQL,以及本人以前参与研发的Oscar数据库,目前正在研发的NTSE、TNT存储引擎,都针对B+树索引的递增/递减插入进行了优化。经过优化,以上的B+树索引,在记录6插入完毕,记录7插入引起分裂之后,新的B+树结构如下图所示:

mysql面试题目

 

对比上下两个插入记录7之后,B+树索引的结构图,可以发现二者有很多的不同之处:

  • 新的分裂策略,在插入7时,不移动原有页面的任何记录,只是将新插入的记录7写到新页面之中;
  • 原有页面的利用率,仍旧是100%;
  • 优化分裂策略的优势:
    • 索引分裂的代价小:不需要移动记录;
    • 索引分裂的概率降低:如果接下来的插入,仍旧是递增插入,那么需要插入4条记录,才能再次引起页面的分裂。相对于50%分裂策略,分裂的概率降低了一半;
    • 索引页面的空间利用率提高:新的分裂策略,能够保证分裂前的页面,仍旧保持100%的利用率,提高了索引的空间利用率;
  • 优化分裂策略的劣势:
    • 如果新的插入,不再满足递增插入的条件,而是插入到原有页面,那么就会导致原有页面再次分裂,增加了分裂的概率。

 

因此,此优化分裂策略,仅仅是针对递增递减插入有效,针对随机插入,就失去了优化的意义,反而带来了更高的分裂概率。

 

在InnoDB的实现中,为每个索引页面维护了一个上次插入的位置,以及上次的插入是递增/递减的标识。根据这些信息,InnoDB能够判断出新插入到页面中的记录,是否仍旧满足递增/递减的约束,若满足约束,则采用优化后的分裂策略;若不满足约束,则退回到50%的分裂策略。

 

但是,InnoDB的实现,有不足之处,会导致下面提到的一个Bug。

 

Bug#67718的成因

在Bug#67718中提到,在特定的插入情况下,InnoDB的索引页面利用率极低,这是由于InnoDB不正确的使用优化分裂策略导致的。

考虑以下的一个B+树,已有的用户数据是1,2,3,4,5,6,100,并且在插入记录100之后,引起索引页面分裂,记录100在分裂后被插入到新的页面:

mysql面试题目

 

由于插入100能够满足递增的判断条件,因此采用了优化分裂策略,分裂不移动数据,新纪录100插入到新页面之中,原有页面的最后插入位置仍旧是6号记录不变,原有页面仍旧保持递增的插入标识不变。

此时,考虑连续插入9,8,7这几条记录,会得到什么样的B+树?此时,全局递增插入变为全局递减插入。

插入记录9后的B+树结构:

mysql面试题目

由于InnoDB的B+树,上层节点保存的是下层页面中的最小值(Low Key),因此记录9仍旧会插入到【3,4,5,6】页面,此时页面已满,需要分裂。而且判断出记录9仍旧满足页面中的递增判断条件(Last_Insert_Pos = 6,9插入到6之后,并且原来是递增插入的)。因此,采用优化的分裂策略,产生新的页面插入记录9,原有页面记录保持不变。

 

插入记录8后的B+树结构:

mysql面试题目

 

插入记录7,也一样。采用优化的分裂策略,记录7独占一个页面。

 

分析:

  • Bug#67718的主要副作用
    • 是页面的利用率极低,每个索引叶页面,只能存放一条记录;
  • Bug#67718的主要原因
    • InnoDB错误的采用了优化的索引分裂策略。InnoDB判断是否满足递增/递减的插入模式,采用的是页面级的判断,哪怕全局的模式发生了变化,只要页面内记录的模式未变,仍旧会选择优化后的索引分裂策略;
       

修复Bug#67718的建议

在本人做Oscar数据库的索引分裂优化时,当时也同样碰到了此问题。当时的解决方案是:每次分裂,若插入的记录是页面中的最后一条记录,则至少将此记录前一条记录分裂到新页面之中。采用此策略,针对100,9,8这一个系列的插入,会产生以下的系列B+树:

插入100,9,8后的B+树:

mysql面试题目

 

插入100时,移动原有页面最后一条记录到新的页面(将6移动到新页面),此时新页面中的记录为【6,100】。接下来插入9,8,都会插入到新的页面之中,不会产生分裂操作,空间利用率提高,减少了索引页面分裂,解决了Bug#67718的问题。

 

至此,B+树 的 insert操作 已经搞明白了。

删除操作稍稍复杂一些,大概思路如下:

1)查找B+-tree中需删除的元素,如果该元素在B+-tree中存在,则将该元素在其结点中进行删除。
2)删除该元素后,判断该元素是否有左右孩子结点,如果有,则上移孩子结点中的相近元素到父节点中(相近元素指的是:刚被删除元素的相邻后继元素,比如删除D,相近元素就是F等)
3)然后接着判断:如果结点中元素个数小于ceil(m/2)-1,首先找其相邻兄弟结点元素是否足够(结点中元素个数大于ceil(m/2)-1),如果足够,向父节点借一个元素,同时将借的元素的孩子结点中相邻后继元素上移到父结点中;
如果其相邻兄弟都不够,即借完之后其结点元素个数小于ceil(m/2)-1,那进行合并,具体是:将父结点中元素下移到要合并结点中(该元素一般是位于两个合并结点的中间元素),然后进行合并。
4)以上操作按顺序进行递归执行

 

问题三  

这个问题多首先想到的多主环境下的主键重复问题,其次是单实例环境下的问题

首先需要高清楚自增的情况,这里参考下朋友:

潇湘隐者的bloghttp://www.cnblogs.com/kerrycode/p/9294767.html

补充一点,在mysql8.0 虽然 对该问题做了持久化修复,但是仍然存在rollback 事务的情况下,自增值复用的问题。

 看了自增列的基础知识之后,我们针对多主环境的情况分析,可以用如下2个方式进行解决:

(1)

依赖数据库自增机制达到全局ID唯一
使用如下语句:
REPLACE INTO Tickets64 (stub) VALUES ('a'); 
SELECT LAST_INSERT_ID();
这样可以保证全局ID唯一,但这个Tickets64表依旧是个单点。  

replace into 跟 insert 功能类似,不同点在于:replace into 首先尝试插入数据到表中, 1. 如果发现表中已经有此行数据(根据主键或者唯一索引判断)则先删除此行数据,然后插入新的数据。 2. 否则,直接插入新数据。

要注意的是:插入数据的表必须有主键或者是唯一索引!否则的话,replace into 会直接插入数据,这将导致表中出现重复的数据。

这个方法慎用,因为带删除数据的操作,对于应用程序写库来讲无法追溯历史。

 

(2)依赖数据库自增机制达到全局ID唯一并消除单点
在2的基础上,部署两个(多个)数据库实例,
设置自增步长为2(多个则为实例数),即auto-increment-increment = 2
设置auto-increment-offset分别为1,2.....
这样第一台数据库服务器的自增id为 1 3 5 7 9
第二台为2 4 6 8 10,如下图所示:

mysql面试题目

 

那么单实例环境下会有哪些问题呢?

后续进行理解。