总结遇到的面试题-MySQL篇

1. 三层B+树存储的数据量

计算的公式是总容量 = 根节点指针数 × 二层节点指针数 × 三层页行数。假设主键为 BigInt(8 字节),指针(Pointer)在 InnoDB 中约为 6 字节,合起来一个索引项约为 14 字节,页的大小默认是16KB,可计算得出索引项 16KB/14字节约为1161个索引项,第二层同理,一共可以指向1161*1161个节点;假如单行 1KB,则一页存16个数据,那么一共就可以存1161*1161*16约两千万行数据。

2. SELECT * FROM table LIMIT 10000, 1000这个查询语句执行非常慢怎么改善

这是MySQL 大分页查询(Deep Paging)性能问题。MySQL 处理 LIMIT offset, size 的逻辑是先取出11000条数据,然后再丢弃掉前 10000 条,只返回最后的 1000 条,如果是在二级索引中查找并且需要回表的话开销会更大。可以通过延迟关联减少回表,也就是先通过“覆盖索引”只查主键 ID,再用这些 ID 去关联原表取完整数据。也就是:

SELECT*FROM orders o JOIN (SELECT id FROM orders LIMIT 10000, 10) AS tmp ON o.id = tmp.id;

只对最后十条进行回表。

3. 怎么实现MySQL乐观锁

有两种办法可以实现乐观锁。第一种是使用版本号,这是最常用的方案:在表中增加一个数字类型的字段 version。使用的SQL语句类似

UPDATE T SET resource ='new_value', version = version +1WHERE id =1AND version =10;

如果更新影响行数为 1则成功,否则失败,可以选择重试和报错。

第二种方法是时间戳,和第一种方法原理相同。MySQL 实现乐观锁的本质是:利用 UPDATE 语句的 WHERE 子句进行版本比对,并结合单条 SQL 的原子性来完成校验。

4. MySQL三个日志

undo log是回滚日志,属于InnoDB 存储引擎层,它是逻辑日志(根据指令记录的),是实现MVCC的核心。

redo log是重做日志,属于InnoDB 存储引擎层,它的作用是确保持久性(Durability),即当数据库发生意外宕机,可以通过它恢复未刷盘的数据,实现 Crash-safe。redo log是物理日志,记录的是具体的物理位置。

bin log是归档日志,属于MySQL Server 层,它的作用是数据备份主从复制,它记录了所有的 DDL(数据定义语言)和 DML(数据操作语言)语句,是逻辑日志。

5. MySQL事务特性,ACID分别是用什么保障的?

MySQL事务特性:原子性、隔离性、持久性、一致性。原子性指的是事务中的操作“要么全做,要么全不做”,通过undo log和回滚机制保证;持久性保证一旦事务提交,其修改就永久保存到磁盘,即使数据库崩溃也不会丢失,由redo log和WAL loggging机制保障;隔离性是为了解决多个事务并发执行时产生的干扰问题(如脏读、幻读),由锁(行锁、间隙锁)和MVCC保障,MVCC利用 undo log 版本链和 Read View(一致性视图),让不同事务能看到不同时间点的数据快照,极大提升了并发性能;一致性是 ACID 的终极目标,它指的是数据库从一个一致性状态转换到另一个一致性状态。

追问:什么是MVCC?MVCC是怎么实现的?

MVCC (Multi-Version Concurrency Control)即多版本并发控制,它的实现主要依赖三个支柱:隐式字段、Undo Log 版本链 和 Read View。在 InnoDB 的聚簇索引记录中,除了定义的字段,系统还会自动添加隐藏字段:记录事务ID、指向上一个版本的undo log记录的回滚指针和ROW_ID(隐藏主键,如果表没有主键时生成);undo log在回滚指针的串联下形成了版本链,根据可见性算法向上回滚找到可见的版本;Read View主要包含m_ids和creator_trx_id,其中m_ids中有min_trx_id和max_trx_id划定可见边界。

Read Committed (RC)每次执行查询(Select)都会重新生成一个新的 Read View,Repeatable Read (RR):仅在事务第一次执行查询时生成一个 Read View,后续所有查询都复用这一个。所以对于快照读(普通的 Select)MVCC 通过 Read View 完美解决了幻读,对于当前读(Select ... for update / Insert / Update) MVCC 无法解决幻读,因为它必须读取最新的数据。这时 InnoDB 靠 间隙锁 (Gap Lock)临键锁 (Next-Key Lock) 来封锁记录之间的空隙,防止其他事务插入新数据。

6. MySQL的索引是存储在内存中还是硬盘中?如果在内存中,是懒加载还是预加载?

MySQL 的索引存储在磁盘中,但为了效率,会“缓存”在内存里,也就是Buffer Pool中。查询数据时,InnoDB 会将相关的索引页(Index Page)从磁盘加载到内存的 Buffer Pool 中。注意,这里是索引页,在内存有限的情况下,Buffer Pool 不一定会缓存完整的索引,只会缓存热点数据页

重启MySQL服务的时候,Buffer Pool 里面几乎没有任何用户数据或索引,所以刚开始的查询会相对比较慢;为了避免重启后瞬时的性能暴跌,MySQL 提供了一个非常实用的功能:Buffer Pool 状态持久化,也就是在 MySQL 关闭时,它会将当前 Buffer Pool 中所有热点页的 “页 ID” 记录到磁盘上的一个文件中。

预加载机制:如果 InnoDB 发现用户正在顺序访问某个区(Extent)里的多个页(例如做全表扫描或大范围查询),它会提前异步地把后续的页加载进 Buffer Pool;触发条件由参数 innodb_read_ahead_threshold 控制(默认 56),即顺序访问了 56 个页后触发预读。

总结来说,MySQL 默认是懒加载,即用到才读;但通过顺序预读和启动预热机制,平衡了磁盘延迟与内存利用率。

7. MySQL索引叶子节点为什么设计为双向链表?为什么不设计成数组,优势是什么?

在 B+ 树中,所有的叶子节点通过指针连接成一个双向链表,可以极大地优化范围查询(例如SELECT * FROM users WHERE id BETWEEN 10 AND 100),相比于B树而言;链表中的数据是按主键顺序排列的,数据库在做 ORDER BY id 时,可以直接按链表顺序读取,无需在内存中进行二次排序。

数组插入和删除开销相比于链表大,不适合数据库这种变更频繁的场景;同时数组需要预分配连续的大块空间,不方便迁移,链表则不需要连续的内存空间。数据库将数据分散在不同的“页”中,当一页满了,只需申请一个新的页,并把指针连上去即可,不需要物理上的连续性;页面写满页分裂后,新分裂出来的页也可以轻松插入到原有链表结构中间,而无需调整其他无关页的物理位置。

8. 一条SQL语句的整个执行过程

MySQL 的架构主要分为 Server 层(负责逻辑和分析)和 存储引擎层(负责数据存取)。MySQL在通过连接器和客户端连接后,然后由分析器分析SQL语句的词法和语法,然后通过优化器找出执行路径里代价(Cost)最小的那一个,执行器会在取人权限后去调用存储引擎的接口。存储引擎(最常用的是 InnoDB)是真正负责数据存储和提取的地方,如果是查询语句的话,存储引擎就按照逻辑取数据。

如果是更新语句,则执行器会首先读取数据到内存(Buffer Pool),有数据可以直接返回,没有则需要加载,然后在内存中更新数据,此时更新的数据称为脏页;为了防止数据库宕机导致内存数据丢失,InnoDB 会把这个更新操作记录到 redo log 中,进入prepare阶段,接着执行器生成这个操作的 binlog(归档日志),并把 binlog 写入磁盘,最后执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成 commit 状态。这里两阶段提交的设计保证了数据的 Crash-safe(崩溃恢复能力),本质上是一致性,在许多对一致性有要求的场景下都用到了2PC,例如RocketMQ中的事务消息、在分布式数据库中处理跨分片的数据等等。

还有没写完的后面再写吧感觉MySQL问的还是挺多的

全部评论

相关推荐

03-16 11:07
南开大学 Java
牛马人的牛马人生:快手卡实习经历的
点赞 评论 收藏
分享
评论
点赞
4
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务