简单讲讲mysql底层 初步

MySQL 底层采用插件式分层架构 ,核心分为「Server 层」和「存储引擎层」两大块。Server 层负责通用的连接管理、SQL 解析、优化执行;存储引擎层负责实际的数据存储与读取,最核心的默认引擎是 InnoDB(MySQL 5.5 之后成为默认),下面逐层拆解底层原理。

一、MySQL 整体分层架构

1. Server 层(所有存储引擎共用)

Server 层覆盖了 SQL 从接入到执行的全流程核心组件:

  • 连接器:负责客户端连接建立、身份认证(账号密码校验)、权限控制,同时管理连接生命周期。长连接模式下连接可复用,但长时间空闲会自动断开(默认 8 小时)。
  • 查询缓存 :执行查询前先查缓存,命中直接返回。但只要表有更新,缓存就会全部失效,性价比极低,MySQL 8.0 已正式移除该模块
  • 分析器:对 SQL 做词法分析(识别关键字、表名、列名)和语法分析(校验 SQL 语法是否正确),最终生成语法树。
  • 优化器:在执行前生成最优执行计划 ------ 比如选择走哪个索引、多表连接的顺序、是否启用索引下推等,目标是用最低成本执行 SQL。
  • 执行器:根据优化器生成的执行计划,调用存储引擎提供的接口,逐行读取 / 修改数据,最终将结果返回客户端。

2. 存储引擎层(插件式可替换)

存储引擎是真正负责数据落地、索引构建、事务与锁实现的层,不同引擎的能力差异极大:

  • InnoDB:默认引擎,支持事务、行级锁、外键、崩溃恢复,是生产环境首选。
  • MyISAM:不支持事务、只有表锁,查询快但并发差,基本被淘汰。
  • Memory:数据全放内存,速度极快但断电丢失,适合临时表场景。

二、一条 SQL 的完整执行流程

select * from user where id = 10; 为例:

  1. 客户端发起连接,连接器校验身份权限,建立连接。
  2. 8.0 之前走查询缓存,命中直接返回;未命中继续向下。
  3. 分析器解析 SQL,确认表名、列名合法,语法正确。
  4. 优化器判断 id 是主键,选择走主键索引,生成执行计划。
  5. 执行器调用 InnoDB 引擎接口,读取对应行数据。
  6. InnoDB 从内存 / 磁盘加载数据页,返回给执行器。
  7. 执行器将结果集返回给客户端。

三、InnoDB 存储引擎底层核心

InnoDB 的设计围绕「减少磁盘 IO」「保证数据安全」两个核心目标,分为内存结构磁盘结构两大部分。

1. 内存结构(缓冲为王)

所有读写优先操作内存,尽量避免直接访问磁盘。

(1)Buffer Pool(缓冲池)
  • 最核心的内存组件,默认占物理内存的 50%~80%。磁盘数据以 ** 页(16KB)** 为单位加载到缓冲池中,后续读写直接操作内存页。
  • 内部用三条链表管理:
    • free list:空闲页链表,存放未使用的页
    • flush list:脏页链表,存放被修改过、还没刷回磁盘的页
    • LRU 链表:管理缓存页的冷热,采用改进型 LRU 算法(分为 young 热区和 old 冷区),防止全表扫描一次性把热点数据全部淘汰。
(2)Change Buffer(写缓冲)
  • 针对非唯一二级索引的写入优化:写入时如果索引页不在内存中,不立即加载磁盘页,而是先把变更写入 Change Buffer。
  • 后续查询命中该页、或后台线程定时,才将变更合并到真正的索引页中,大幅减少随机磁盘 IO。
(3)Log Buffer(日志缓冲区)
  • 存放 redo log 的内存缓冲区,默认 16MB。事务提交时先写缓冲区,再按策略刷到磁盘,减少写盘次数。
(4)Adaptive Hash Index(自适应哈希索引)
  • InnoDB 自动监控热点索引页,在内存中为其构建哈希索引,将等值查询速度提升数倍,完全自动、无需人工干预。

2. 磁盘结构

(1)存储单位:段 → 区 → 页 → 行
  • 页(Page):InnoDB 最小 IO 单元,默认 16KB,一次 IO 至少读 / 写一个页。
  • 区(Extent):连续 64 个页 = 1MB,用于批量分配空间,减少碎片化。
  • 段(Segment):一个索引对应两个段(叶子节点段、非叶子节点段),由多个区组成。
(2)表空间
  • 独立表空间 :每张表对应一个 .ibd 文件,存表的数据和索引,默认开启。
  • 系统表空间(ibdata1):存放数据字典、Double Write Buffer 等系统信息。
  • Undo 表空间:存放 undo log 回滚日志。
  • 临时表空间:存放临时表、排序等中间数据。
(3)Double Write Buffer(双写缓冲)
  • 解决「部分写失效」问题:如果刷盘时突然断电,16KB 的页只写了一半,数据会损坏。
  • 原理:刷盘前先把页写到系统表空间的双写区(顺序写,开销极低),再写到真正的数据页。崩溃后可从双写区恢复完整的页。

3. 三大核心日志

InnoDB 事务与数据安全的基石是三条日志:

日志类型 所属层级 性质 核心作用
Redo Log InnoDB 引擎层 物理日志(记录 "哪个页做了什么修改") 保证事务持久性,实现崩溃恢复(WAL 预写日志机制),循环写入
Undo Log InnoDB 引擎层 逻辑日志(记录数据修改前的版本) 保证事务原子性(回滚),支撑 MVCC 多版本
Binlog Server 层 逻辑日志(记录 SQL 原始逻辑) 主从复制、数据备份归档,追加写入
  • 两阶段提交:为了保证 redo log 和 binlog 数据一致,事务提交分为 Prepare 阶段(写 redo log 并标记准备提交)和 Commit 阶段(写 binlog、提交事务),宕机后可通过事务状态判断是否需要回滚 / 提交。

四、索引底层:B+ 树原理

索引的本质是「用空间换时间」,通过有序的数据结构减少 IO 次数。InnoDB 默认使用 B+ 树作为索引结构。

1. 为什么选择 B+ 树

  • 对比二叉树 / 红黑树:树高太高。比如百万数据,二叉树高度接近 20,意味着要 20 次磁盘 IO;而 B+ 树高度通常只有 3~4 层,仅需 3~4 次 IO。
  • 对比 B 树:
    1. B+ 树非叶子节点只存索引键,不存数据,一页能容纳更多键,树更矮、IO 更少。
    2. B+ 树所有数据都存在叶子节点,且叶子节点通过双向链表串联,范围查询、排序非常高效。

2. 聚簇索引 vs 二级索引

  • 聚簇索引(主键索引) :叶子节点直接存储完整的行数据。一张表只能有一个聚簇索引,主键就是聚簇索引;没有主键时 InnoDB 会自动选唯一非空索引,都没有则生成隐藏的 DB_ROW_ID 作为聚簇索引。
  • 二级索引(普通索引) :叶子节点只存储主键值。通过二级索引查询时,先找到主键,再去聚簇索引里找完整行数据,这个过程叫回表

3. 常见索引优化原理

  • 覆盖索引:查询的列刚好都在二级索引里,不需要回表,性能极高。
  • 最左前缀原则:联合索引按从左到右的顺序匹配,跳过左边列则索引失效。
  • 索引下推:在索引遍历过程中就过滤不符合条件的列,减少回表次数。

五、事务与 MVCC 底层实现

1. ACID 的底层保障

  • 原子性(Atomicity):靠 Undo Log 实现。事务回滚时,根据 Undo Log 反向执行修改操作,把数据恢复到事务开始前的状态。
  • 持久性(Durability):靠 Redo Log + WAL 机制。事务提交时,先写 Redo Log 再写数据页;即使宕机,重启后也能通过 Redo Log 把未刷盘的修改恢复回来。
  • 隔离性(Isolation):靠 MVCC(多版本并发控制)+ 锁机制实现,读写互不阻塞,提升并发性能。
  • 一致性(Consistency):是最终目标,由原子性、持久性、隔离性共同保障,同时配合数据库约束(主键、唯一键、外键)保证数据合法。

2. MVCC 实现原理

MVCC 让读操作读取历史版本数据,不用加锁,实现读写不冲突。核心依赖三个东西:隐藏列、Undo Log 版本链、Read View

(1)行隐藏列

InnoDB 每行数据背后都有三个隐藏字段:

  • DB_TRX_ID:最后一次修改该行的事务 ID。
  • DB_ROLL_PTR:回滚指针,指向该行上一个版本的 Undo Log。
  • DB_ROW_ID:隐藏主键,没有显式主键时才会有。
(2)Undo Log 版本链

每次修改数据时,都会把旧版本写入 Undo Log,通过 DB_ROLL_PTR 把所有历史版本连成一条链表,最新版本在表头,最老版本在表尾。

(3)Read View(读视图)

读视图用来判断当前事务能看到哪些版本的数据,包含几个核心信息:

  • m_ids:生成 Read View 时,所有活跃未提交的事务 ID 列表。
  • min_trx_id:活跃事务中最小的 ID。
  • max_trx_id:生成 Read View 时,下一个要分配的事务 ID(即最大事务 ID + 1)。
  • creator_trx_id:生成这个 Read View 的事务 ID。
(4)可见性规则

遍历版本链时,对每个版本的 DB_TRX_ID 做判断:

  1. 小于 min_trx_id:这个版本在视图生成前就已提交,可见
  2. 大于等于 max_trx_id:这个版本是视图生成后才开启的事务修改的,不可见
  3. min_trx_idmax_trx_id 之间:
    • 如果在 m_ids 里:事务还没提交,不可见
    • 如果不在 m_ids 里:事务已经提交,可见

如果当前版本不可见,就顺着版本链找下一个,直到找到可见版本或遍历结束。

3. 隔离级别与 MVCC 的关系

  • 读未提交(RU):直接读最新版本,不使用 MVCC。
  • 读提交(RC)每次 SELECT 都生成一个新的 Read View,所以能读到其他事务刚提交的修改。
  • 可重复读(RR)事务中第一次 SELECT 时生成一个 Read View,后续所有查询都复用这个视图,所以整个事务内读到的数据一致。
  • 串行化:所有操作都加锁,不使用 MVCC。

RR 级别下,InnoDB 还通过 Next-Key Lock(临键锁) 解决了幻读问题。

六、锁机制底层

InnoDB 锁机制用于解决并发写冲突,核心是行级锁。

1. 锁的分类

  • 按粒度:表级锁、行级锁
  • 按类型:
    • 共享锁(S 锁):读锁,多个事务可以同时加 S 锁,互不阻塞。
    • 排他锁(X 锁):写锁,一个事务加了 X 锁,其他事务不能加任何锁。
  • 意向锁(IS/IX):表级锁,用来快速标记 "表中某行被加了行锁",避免每次加表锁都要全表扫描。意向锁之间互不冲突。

2. InnoDB 三种行锁算法(RR 级别)

  • Record Lock(记录锁):锁住索引上的某一行记录,必须命中索引才会加行锁,否则会升级为表锁。
  • Gap Lock(间隙锁):锁住索引记录之间的间隙,不锁记录本身,防止其他事务在间隙中插入新行,解决幻读。间隙锁之间不互斥。
  • Next-Key Lock(临键锁):记录锁 + 间隙锁,左开右闭区间,是 InnoDB 默认的行锁算法。当查询命中唯一索引且是等值查询时,会退化为记录锁。

3. 死锁

两个事务互相持有对方需要的锁,循环等待,永远无法释放。

  • InnoDB 内置死锁检测:检测到死锁后,回滚代价最小的事务(解锁行数少的),让另一个事务继续执行。
  • 避免方式:统一加锁顺序、尽量用索引减少锁范围、缩短事务时长。