Mysql面试题汇总

1.B树和B+树

m阶的B树:

  • 每个节点最多有m个孩子,m-1条记录
  • 除了根节点和叶子节点,每个节点至少有m/2个孩子
    应用:
    文件系统的读取,一个节点对应一页,这样600亿个元素中只需要小于4次查找即可定位到某一存储位置。


    B树

    B+树:

  • 度为m的B+树每个节点有m个孩子,每个元素不保存数据,只用来索引,所有数据都保存在叶子节点
  • 叶子结点中包含了全部元素的信息,按关键字的大小自小而大顺序链接
  • 所有的中间节点元素都同时存在于子节点
    应用:
    数据库索引
    优势:
  • 中间节点只存索引,树更矮胖,存储更多的元素,使得查询的IO次数更少
  • 所有查询都要查找到叶子节点,查询性能稳定
  • 所有叶子节点形成有序链表,便于范围查询


    image

2.hash索引

定义:

哈希索引基于哈希表实现,哈希表中保存着指向每个数据行的指针。对于每一行数据,存储引擎会对所有建立了索引的列计算一个哈希码,key是索引字段,value是行指针。
应用:

MySQL中只有Memory引擎支持哈希索引(也支持B+Tree索引),是其默认的索引类型。
InnoDB引擎中有一个功能叫“自适应哈希索引”,当InnoDB注意到某些索引值被使用得很频繁时,它会在内存中基于B+Tree索引之上再创建一个哈希索引,让B+Tree也具有一些哈希索引的优点。但这是一个完全自动的行为,不受用户控制或配置,不过可以关闭该功能。

特点:

  • 哈希索引的检索效率非常高,不需要想B+Tree索引需要从根结点到支结点
  • 没有按照索引值顺序存储,不能用于排序
  • 只支持等值比较查询
  • 哈希索引只包括哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行中的数据
  • 如果建立的是多行索引,不支持部分行的查找,因为是使用全部内容来计算哈希值的

3.ACID

  1. 隔离性:
    含义:
    一个事务的执行不会收到另一个事务执行的影响
    实现方式:
    锁机制+MVCC
  2. 原子性:
    含义:
    一个事务中的操作,要么全成功,要么全失败。
    实现方式:
    undo log
  • 对于每个 insert,回滚时会执行 delete;
  • 对于每个 delete,回滚时会执行insert;
  • 对于每个 update,回滚时会执行一个相反的 update,把数据改回去
  1. 持久性:
    事务一旦提交,其结果就不能改变,除非新事务的提交执行。
    实现方式:
    redo log 和 Buffer Pool
    redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中,如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
  2. 一致性:
    含义:
    比如总共100块钱,无论如何将钱转来转去,总共还是100
    实现方式:
    一致性是事务追求的最终目标,前问所诉的原子性、持久性和隔离性,其实都是为了保证数据库状态的一致性。当然,上文都是数据库层面的保障,一致性的实现也需要应用层面进行保障。也就是你的业务,比如购买操作只扣除用户的余额,不减库存,肯定无法保证状态的一致。

4.MVCC

当前读和快照读

当前读
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
快照读
像不加锁的select操作就是快照读,即不加锁的非阻塞读

隐藏字段

每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID。
DB_TRX_ID:
记录创建这条记录/最后一次修改该记录的事务ID,更新时才会生成
DB_ROLL_PTR:
回滚指针,指向这条记录的上一个版本
DB_ROW_ID:
隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引

ReadView

查询的时候生成ReadView :没有提交的事务id的列表,列表中的最小事务id即minID和目前已经出现过的最大事务id + 1,即maxID。
当读取某一行对应的数据时,当当前行对应的回滚指针链中找,判断链中对应的事务id,即curID。
(1) curID < minID 已经提交,可读
(1) curID >= maxID ReadView之后生成,不可读
(3) curID 如果在列表中,表示未提交,不可读,否则可读
而在可重复读的隔离级别情况下,每次快照读都会生成一个新的ReadView

5.锁机制

表锁和行锁

MyISAM仅仅支持表锁,而InnoDB同时支持表锁和行锁。
表锁开销小,加锁块,无死锁,粒度大,并发度低。行锁开销大,加锁慢,会出现死锁,粒度小,并发度高。

意向锁

表级锁和行级锁的冲突判断,如果没有意向锁,则判断表锁和行锁冲突的时候需要遍历表上所有行锁,有了意向锁,则只要判断表是否存在意向锁就可以知道是否有行锁了。

读锁和写锁

当一个事务对某几行上读锁时,允许其他事务对这几行进行读操作,但不允许其进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。
当一个事务对某几个上写锁时,不允许其他事务写,但允许快照读。更不允许其他事务给这几行上任何锁。包括写锁。

一条sql语句的加锁过程

给定sqldelete from t1 where id = 10; 判断加锁的方式
组合一:id列是主键,RC隔离级别

image

组合二:id列是二级唯一索引,RC隔离级别
此时name是主键
image

组合三:id列是二级非唯一索引,RC隔离级别
image

组合四:id列上没有索引,RC隔离级别
image

组合五:id列是主键,RR隔离级别
和组合一一致
组合六:id列是二级唯一索引,RR隔离级别
和组合二一致
组合七:id列是二级非唯一索引,RR隔离级别
image

组合八:id列上没有索引,RR隔离级别
image

组合九:Serializable隔离级别
同组合八

6.事务隔离级别

  1. 读未提交
    没有解决更新丢失、脏读、不可重复读、幻读
    更新丢失:A和B两个事务对同一行修改,造成覆盖其他事务的行为。
    不可重复读:
    事务A开启后,事务B更新了一个字段,但没有提交,而事务A却读到了,如果B回滚,那么A读到的就是不正确的字段。
    两个事务同时
  2. 读已提交
    没有解决不可重复读、幻读
    不可重复读:
    事务A开启后,读取一行,事务B更新了这行数据后提交,事务A再读一次,前后不一致。
  3. 可重复读
    没有解决幻读,但如果采用当前读,则可以解决幻读
    幻读:
    事务A开启后,读取了一些行,此时B事务插入一行,那么A读到的就多了。
  4. 串行化

7.慢查询

1.查询慢日志
2.show processlist 查看当前正在进行的sql线程的执行情况,如线程状态,有没有锁表
3.通过EXPLAIN分析较低SQL的执行计划
4.将链表查询分解为多个小查询
5.具体方法:
(1)避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引
(2)很多时候用 exists 代替 in 是一个好的选择
(3)尽可能的使用 varchar 代替 char ,因为首先变长字段存储空间小,可以节省存储空间,
其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些
(4)任何地方都不要使用 select * from t ,用具体的字段列表代替
(5)避免频繁创建和删除临时表,以减少系统表资源的消耗
(6)避免使用 like %xxx%
(7)避免在 where 子句中使用!=或<>操作符
(8)避免在 where 子句中使用 or 来连接条件
(9)in 和 not in 也要慎用,否则会导致全表扫描
(10)应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
6.使用redis等缓存
7.分库分表

8.主从复制

为什么要主从复制:
(1)读写分离
(2)高可用,如果宕机,可以立即切换

原理:

  1. 主库db的更新事件(update、insert、delete)被写到binlog
  2. 主库创建一个binlog dump thread,把binlog的内容发送到从库
  3. 从库启动并发起连接,连接到主库
  4. 从库启动之后,创建一个I/O线程,读取主库传过来的binlog内容并写入到relay log
  5. 从库启动之后,创建一个SQL线程,从relay log里面读取内容,从Exec_Master_Log_Pos位置开始执行读取到的更新事件,将更新内容写入到slave的db

9.bin log 和 redo log 的区别

  • 日志形式不同,bin log是逻辑日志,对应sql语句,只记录更新数据的语句,不记录读的语句。而redo log对应物理日志,记录物理操作。
  • 记录写入磁盘的时间点不同,二进制日志只在事务提交完成后进行一次写入。而innodb存储引擎的重做日志在事务进行中不断地被写入
  • binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用
  • redo log是在InnoDB存储引擎层产生,而binlog是MySQLserver的上层产生的,所有执行引擎都会产生

10.分库分表

水平分表
当博客的量达到很大时候,就应该采取横向分割来降低每个单表的压力,来提升性能。例如博客的冷数据表,假如分为100个表,当同时有100万个用户在浏览时,如果是单表的话,会进行100万次请求,而现在分表后,就可能是每个表进行1万个数据的请求(因为,不可能绝对的平均,只是假设),这样压力就降低了很多很多。减少竞争锁,优化单一表数据量过大而产生的性能问题.
垂直分表
对于一个博客系统,文章标题,作者,分类,创建时间等,是变化频率慢,查询次数多,而且最好有很好的实时性 的数据,我们把它叫做冷数据。而博客的浏览量,回复数等,类似的统计信息,或者别的变化频率比较高的数据,我们把它叫做活跃数据。所以,在进行数据库结构设计的时候,就应该考虑分表,首先是纵向分表的处理。

垂直分库
数据还是始终限制在一台服务器,将数据分布到不同的服务器上,比如将商品库和店铺库分到不同服务器。解决业务耦合,提高性能。

水平分库
比如商品库太大了,单台服务器无法承受,取模放到不同服务器。

11.一条sql的执行过程

  1. 建表
DROP TABLE IF EXISTS student;
CREATE TABLE `student` (
  `id` INT(5) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(10) DEFAULT NULL,
  `subject` VARCHAR(10) DEFAULT NULL,
  `grade` DOUBLE(4,1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=40 DEFAULT CHARSET=utf8;
  1. 插入数据后的形式


    image
  2. 问题
    要求查询出挂科数目多于两门(包含两门)的前两名学生的姓名,如果挂科数目相同按学生姓名升序排列。
  3. 答案:
SELECT `name`,COUNT(`name`) AS num FROM student WHERE grade < 60 GROUP BY `name` HAVING num >= 2 ORDER BY num DESC,`name` ASC LIMIT 0,2; 
image
  1. 过程
    (1)执行from,加载表到内存
    (2)执行where过滤生成临时表
    (3)执行group by分成多个临时表
    (4)执行select语句选取select的字段
    (5)执行having筛选
    (6)执行order by
    (7)执行limit

12.页分裂

一个B+树节点对应一页数据(1MB),页内有单链表形式的数据行,页与页之间使用双向链表连接,删除一条数据时,只是在逻辑上删除,只有当删除行数到一定阈值时才进行物理上的页合并。当第9页要进行插入,但已经满了,而第10页也满了,那么:
(1)创建一个新的页
(2)找到应该从第9页中哪里开始分裂
(3)移动数据
(4)将指针的指向改变

13.三大范式和码

范式
  1. 第一范式:
    数据表的每一列都要保持它的原子特性,也就是列不能再被分割。
    这张表就不符合第一范式规定的原子性


    image
  2. 第二范式:
    消除了非主属性对码的部分依赖
    一组属性X中的其中一个或几个属性能推出Y就说Y部分依赖于X。
    在下图中:
    姓名对学号存在部分依赖
    系名对学号存在部分依赖
    系主任对学号存在部分依赖


    image
  3. 第三范式
    消除了非主属性对码的传递依赖,这不符合非主属性不依赖于其它的非主属性的设计要求。

  4. BC范式
    消除了主属性对码的传递依赖和部分依赖,主属性就是码中的属性

是可以确定一个元组的所有信息的属性名或属性名组。
例如在 { a, b, c, d } 中,
假设知道 a 的值就能确定 a, b, c, d 的值,
假设知道 c, d 的值就可以确定 a, b, c, d 的值,
那么 { a } 就是码,{ c, d } 就是码。
并且 { a, b }, { a, c }, { a, b, c }, { a, b, c, d } 等也都是码,因为它们也可以确定一个元组的所有值,即使很多余。
候选码:候选码的真子集中不存在码,候选码可以有多个。
就上面的例子而言,{ a } 是候选码,{ c, d } 是候选码,因为它们的真子集中不存在码。
而诸如 { a, b } 并不是候选码,因为它的真子集中含有 { a }, 且 { a } 是码。
主码:主码就是主键的意思,主码是任意一个候选码。
还是上面的例子,主码是候选码 { a }, { c, d } 中的其中一个。
既可以是 { a }, 也可以是 { c, d }。

14.int(3)和int(5),varchar(3)和varchar(5)

char和varchar:
char是定长的,如果插入的数据长度小于char的固定长度,则用空格填充,因此存取速度块。varchar插入数据多长,就按照多长来存储,占用的空间少。
varchar(50):
用多少,占多少,但最多存50个字符
int(4):
期望列宽,必须和zerofill关键字连用才生效,单独使用和直接用int没有区别

CREATE table test(
    age int(4) ZEROFILL not null
)

当插入的值超出4位时:与int没有区别
当插入的值不足4位时:会在前面填充0,补足4位

insert into test VALUES(3)
insert into test VALUES(1234)
insert into test VALUES(123456)
image

可以看到3前面补了0

15.聚簇索引和非聚簇索引

聚簇索引:
将数据存储与索引放到了一块,找到索引也就找到了数据,因此不需要回表查询。InnoDB只有主键索引是聚簇索引。
非聚簇索引:
将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,需要先查该索引,再差主键索引。但如果索引全部命中,则不需要回表查询。

16.InnoDB和MyISAM

MyISAM和InnoDB的区别

你可能感兴趣的:(Mysql面试题汇总)