Mysql面试题总结

829 阅读9分钟

1. 背景

mysql作为最常用的中间件,还是有很多地方指导费学习的,看这些面试题是要理解的去记忆,然后看看mysql相关的书籍,就可以触类旁通了

2. 面试题

2.1 mysql 高度为3的b+ 树能存储多少数据

索引页存放的是索引值 + 指针,mysql 设计者为了避免每条数据都从磁盘进行读取,设计了页结构 用于磁盘和内存之间的交互,分为索引页和数据页

一个页16KB,磁盘存储数据的最小单元是扇区,扇区512byte ,B+ 树的每个节点只有一个页么

高度为2的时候。一个根节点和若干个叶子节点,根节点的指针数 * 单个叶子结点的记录数就是存储的数据量,假设表单 主键id,bigint占用8byte,指针在mysql中6byte, 一个索引页用16 * 1024/14 = 1170.索引页有1170个索引记录,假设一行数据1KB,一个页就是16行数据,1170 * 16 = 18720行记录

三层B+ 树, 1170 * 1170 * 16 得到千万级的数据,进行1到3次的IO就可以获取

2.2 update 语句是锁表还是锁行

where 命中索引就是锁行,否则锁表

测试语句

set  @@autocommit = 0;

select @@autocommit;

update user set name = 'dyw11' where age = 40;

commit;

alter table user add index idx_age(age)

2.3 mysql 表设计时间列用datetime还是timestamp

2.4 在mysql中一条数据是如何被写进来的,以及页分裂的过程

一张空表insert 数据,首先会被写到跟页中,跟页被写满的时候,会先分配一个数据页,然后把跟页的数据移动到数据页中,再对数据页做页分裂操作,将主键值大的一半移到分裂的数据页中,之后跟页会转化成一级索引,

把主键做成无意义的自增的时候,这样插入数据直接在页的末尾插入,不需要移动页的数据

2.5 Mysql B+树的原理

hdd 物理硬盘是 有512byte的扇区组成,固态硬盘是由4KB的page组成,没有扇区的概念 操作系统都有连续读和缓冲数据的原理,每次IO操作会加载8个扇区的数据到内存中

mysql 的最小存储单元 是页,16KB,会把16KB的数据缓存到buffer pool中,

B+ 树的叶子结点被mysql优化成双向链表

2.5 MVCC原理

mysql事务的4个隔离级别解决的3个读数据的问题

多版本并发控制机制,每条数据都会存在多个版本,采用日志版本链 + 一致性试图来找到自己可见的版本

undo log 日志版本链,每次修改数据都会把修改的数据保存一份,然后在每条数据基础上增加两个隐藏列,trx_id和roller_pointer, 分别存储当前事务id和上一个版本的存储地址

一致性视图, 在可重复读级别下,开启事务以后第一次执行sql查询时会生成一致性视图,在事务提交之前都不会改变,这个视图组成部分:

trx_ids: 当前未提交的活跃的事务id集合

low_limit_id: 生成一致性视图的时候,当前最大的事务id + 1

up_limit_id: 未提交的事务组中的最小的事务id

读提交的情况下每次查询都会生成一致性视图,

看这个最容易懂

www.bilibili.com/video/BV1bG…

2.5 mysql的当前读和快照读的含义

快照读: MVCC机制实现快照读,普通的select查询就是快照读,快照读到的数据可能不是最新的数据。他主要是为了实现可重复读的事务隔离级别

当前读:读取的是最新的数据版本,当使用 update、delete、insert、select... lock in share mode, select... for update 是当前读,当前读是加了锁的crud语句

2.6 MVCC有没有解决幻读的问题,举例子说明

在快照读的情况下,innodb通过MVCC机制解决了幻读现象

在当前读的情况下,innodb无法通过MVCC解决幻读的现象,因为它每次读取的都是最新的数据

第一种案例:事务A 在进行update的时候,将新纪录的隐藏列trx_id 替换成事务A的id 记录在日志版本链里面,所以事务A再次去快照读查询,按照MVCC就会产生幻读了

image.png

第二种案例

image.png

在RR的隔离级别下,连续两个快照读中间即使加入了当前读,也不一定会出现幻读,要判断当前读的临键锁的范围是否覆盖其他事务中的数据,如果没有覆盖,就不会出现幻读

我参照的这个小伙伴:www.bilibili.com/video/BV1fx…

www.bilibili.com/video/BV1wu…

2.7 mysql update是锁表还是锁行

多个事务并行对同一个数据进行修改的时候,会产生事务的竞争,会造成脏读、不可重复读,幻读,所以引入了事务的隔离级别

update走索引,只更新一条语句,则是行锁,不走索引就是表锁

2.8 为什么有些公司禁止使用@Transactional 声明式事务

  1. 在方法上增加@Transaction 声明式事务,如果一个方法中的存在较多耗时操作, 就容易引发长事物问题,而长事物会带来锁的竞争影响性能,同时也会导致数据库 连接池被耗尽,影响程序的正常执行。
  1. 如果方法存在嵌套调用,而被嵌套调用的方法也声明了@Transaction 事物,就会 出现事物的嵌套调用行为,容易引起事物的混乱造成程序运行结果出现异常

2.8 为什么数据量大了,就一定要分库分表么

引入分库分表机制,通过分库可以降低单个服务器节点的 IO 压力,数据库的访问量过高,出现连接数不够用的时候,选择分库,增加db实例的方式增加连接数,从而提高系统的并发性能,单库出现瓶颈: CPU负载过高,导致读写性能较慢,内存不足,磁盘空间不足,或者网络带宽不足

通过分表的方式可以降低单表数据量,从而提升 sql 查询的效率,单表出现瓶颈,单表数据量较大,导致读写性降降低

2.9 如何估算分表的数量,根据什么样的规则来进行分库分表

表的数量设计成2的倍数,不要太多,容易增加维护成本,单表数据量 X 分表数 / 每年的数据量 > 3

按照时间和分片的字段来进行分表

按年按月按日来进行分表,但有时候查询不知道时间,

取分表字段的hashcode值除以表的数量取模得到表的索引,

3.10 十亿级商品数据,分库分表核心流程详解

1.评估是否需要拆分

  1. 详细拆分技术方案 拆分几个库几个表,需要支撑未来几年的发展

拆分策略:

范围拆分:天然水平扩展,单表数据量可控,但是并没有将写流量都均匀的分到每一张表,存在明显的写偏移和读偏移

中间表映射:分表字段和数据库的映射关系存放在一张单独的表上,每次路由前先查询这张表,得到一个具体的路由数据库,增加了复杂度

hash切分:后续的扩容涉及到数据的迁移,存在跨节点查询和分页的问题

选择分表字段

美团外卖的商品数据,用户视角查询商品,有商家ID,商家视角有商品ID,可以选择商品ID作为分表字段,覆盖了最高频的几个使用场景

订单,用户视角查看订单,只有自己的uid,商家视角只有自己的商家ID,运营的视角只有订单ID

3.同步相关影响方

4.进行拆分

3.11 如何解决分库分表带来的问题

分布式Id的问题,雪花算法,leaf算法

无法使用本地事务了,需要引入分布式事务

两阶段提交,协调者首先询问所有的事务参与者是否可以执行事务提交,协调者根据所有事务参与者返回的结果决定是否提交事务

TCC,每个操作都有对应的确定和取消操作,TCC有主服务和从服务两个角色,下单流程走到交易服务,交易服务分别请求订单服务和库存服务

首先主服务会调用所有从服务的try接口进行业务检查和资源预留,接着主服务会根据所有从服务的返回结果决定是否提交事务,如果所有的从服务都返回成功,则调用从服务的confirm接口进行事务确认,否则会调用所有从服务的cancel接口执行事务的取消

本地消息表、事务消息都是分布式事务

实际的高并发业务中都不会使用强一致的分布式事务,金融场景特殊,保证最终的事务一致性手段,重试,回滚,监控告警,幂等,对账,人工补偿

强一致性事务会带来严重的性能损耗,导致服务的吞吐量下降,

会引入额外的复杂度,导致代码的可维护性降低,

概率比较低

跨库查询和分页查询

合适的分表字段

搜索引擎支持,数据冗余到ES,使用ES做查询

分开查询,在内存中聚合

分表字段的选择

能覆盖大部分查询场景

基因法+ 冗余数据,将用户id作为订单id的最后几位

商家Id的维度,同步写或者异步写

大卖家问题

大卖家单独一张表

大卖家拆分映射多个虚拟商家

3.12 线上接口负载剧增快扛不住了,应该如何解决

增加缓存,用空间换时间,缓存是解决性能问题的万金油,

这个要改代码可以增加一些节点,紧急上线,在流量的入口增加限流和分发