【MySQL】存储引擎

目录标题

一、MySQL的体系结构

 MySQL的体系结构主要分为以下四个层次:

  1. 连接层:负责客户端连接管理、授权认证、安全方案等。每个客户端连接在服务端会分配一个线程。
  2. 服务层:提供SQL解析、查询优化、缓存、内置函数(如日期、时间、数学函数)以及跨存储引擎的功能(如存储过程、触发器、视图等)。
  3. 存储引擎层:负责数据的存储和提取。MySQL支持插件式存储引擎,不同的存储引擎决定了数据操作的特性(如是否支持事务、行锁等)。
  4. 文件系统层:将数据库数据存储在磁盘上(如数据文件、日志文件等)。

二、存储引擎

 在创建表时可以通过 ENGINE 关键字指定存储引擎,如下例所示,如果没有指定,MySQL会使用默认存储引擎(MySQL 5.5 之后默认为 InnoDB)。

sql 复制代码
CREATE TABLE mytable (
    id INT PRIMARY KEY,
    name VARCHAR(50)
) ENGINE = InnoDB;

当前数据库支持的常用的存储引擎

  • InnoDB:支持事务、行级锁、外键,高并发性能,默认存储引擎
  • MyISAM 不支持事务,表级锁,访问速度快,早期默认引擎
  • Memory 基于内存存储,数据断电丢失,表级锁,常用于临时表

三、InnoDB

 InnoDB 是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后成为默认的 MySQL 存储引擎。

 其特点如下,

  • DML 操作遵循 ACID 模型,支持事务(Transaction)
  • 行级锁(Row-Level Locking),提高并发访问性能。
  • 支持外键(FOREIGN KEY)约束,保证数据的完整性和正确性。

 每个 InnoDB 表对应一个 .ibd 文件(表空间文件),例如 表名.ibd。该文件存储了该表的表结构(.frm / .sdi)、数据和索引。相关参数:innodb_file_per_table(默认开启,表示每张表独立表空间)。

1.逻辑存储结构

 从大到小依次为:表空间 → 段 → 区 → 页 → 行。

  • 表空间(Tablespace):一个 MySQL 实例可以对应多个表空间(如系统表空间、独立表空间、通用表空间)。用于存储记录、索引等数据。
  • 段(Segment):分为数据段(Leaf node segment,对应 B+Tree 叶子节点)、索引段(Non-leaf node segment,对应 B+Tree 非叶子节点)、回滚段(Rollback segment)。段用来管理多个区(Extent)。
  • 区(Extent):表空间的单元结构,每个区大小为 1 MB。默认情况下,InnoDB 页大小为 16 KB,所以一个区包含 64 个连续页。
  • 页(Page):InnoDB 磁盘管理的最小单元,每个页默认 16 KB。为保证连续性,InnoDB 每次从磁盘申请 4~5 个区。
  • 行(Row):InnoDB 按行存储数据。

2.内存结构

  • Buffer Pool(缓冲池) :缓冲池是主内存中的一个区域,用来缓存磁盘上经常操作的真实数据。在执行增删改查时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后以一定频率刷新到磁盘。这样可以减少磁盘 I/O,加快处理速度。
    • 缓冲池以 页(Page) 为单位,底层采用链表数据结构管理 Page。根据状态,Page 分为三种类型:
    • free page:空闲页,未被使用。
    • clean page:已使用页,数据没有被修改过(与磁盘一致)。
    • dirty page:脏页,数据被修改过,与磁盘不一致。
  • Adaptive Hash Index(自适应哈希索引):用于优化对 Buffer Pool 中数据的查询。InnoDB 会监控对表上索引页的查询,如果观察到哈希索引可以提升速度,则自动建立哈希索引,无需人工干预。相关参数:adaptive_hash_index。
  • Change Buffer(更改缓冲区) :当执行 INSERT、UPDATE、DELETE 等 DML 语句时,如果目标数据页(Page)不在 Buffer Pool 中,InnoDB 不会直接读取磁盘页进行修改,而是将变更操作缓存在 Change Buffer 中。未来当该数据页被读取(例如通过查询或索引扫描)时,InnoDB 会将 Change Buffer 中缓存的变更合并(Merge) 回 Buffer Pool 中的相应页,然后再将合并后的数据刷新到磁盘。
    • Change Buffer 只对 非唯一二级索引 生效。唯一索引和主键索引需要立即检查唯一性约束,因此不能缓存,必须直接读取磁盘页。
    • 二级索引(尤其是非唯一二级索引)的插入、删除、更新往往涉及随机的磁盘位置。如果没有 Change Buffer,每次 DML 都需要直接读写磁盘页,造成大量随机 I/O,性能极低。
  • Log Buffer(日志缓冲区) :用来保存要写入磁盘的日志数据(redo log、undo log)。默认大小为 16 MB。日志缓冲区的日志会定期刷新到磁盘。如果需要更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘 I/O。相关参数如下
    • innodb_log_buffer_size:缓冲区大小。

    • innodb_flush_log_at_trx_commit:日志刷新到磁盘的时机。

      • 1:每次事务提交时写入并刷新到磁盘(最安全)。
      • 0:每秒将日志写入并刷新到磁盘一次。
      • 2:每次事务提交后写入,每秒刷新到磁盘一次。

3.磁盘结构

  • System Tablespace(系统表空间):系统表空间是更改缓冲区的存储区域。如果表是在系统表空间(而不是每个表文件或通用表空间)中创建的,它也可能包含表和索引数据。在 MySQL 5.x 版本中还包含 InnoDB 数据字典、undo log 等。相关参数:innodb_data_file_path。
  • File-Per-Table Tablespaces(独立表空间):每个表的文件表空间包含该表的数据和索引,存储在文件系统上的单个数据文件(表名.ibd)中。相关参数:innodb_file_per_table(默认 ON)。
  • General Tablespaces(通用表空间):需要通过 CREATE TABLESPACE 语法创建通用表空间,在创建表时可以指定使用该表空间。
sql 复制代码
CREATE TABLESPACE xxxx ADD
DATAFILE 'file_name'
ENGINE = engine_name;
  • Undo Tablespaces(撤销表空间):MySQL 实例在初始化时会自动创建两个默认的 undo 表空间(初始大小 16 MB),用于存储 undo log 日志。
  • Temporary Tablespaces(临时表空间):InnoDB 使用会话临时表空间和全局临时表空间,存储用户创建的临时表等数据。
  • Doublewrite Buffer Files(双写缓冲区):InnoDB 引擎将数据页从 Buffer Pool 刷新到磁盘之前,先将数据页写入双写缓冲区文件中,以便系统异常时恢复数据。相关文件为 #ib_16384_0.dblwr、#ib_16384_1.dblwr。
  • Redo Log(重做日志):重做日志用于实现事务的持久性。由两部分组成:重做日志缓冲(redo log buffer)(内存)和重做日志文件(redo log file)(磁盘)。事务提交后,所有修改信息都会写入该日志,用于在刷新脏页到磁盘发生错误时进行数据恢复。重做日志以循环方式写入,默认涉及两个文件:ib_logfile0、ib_logfile1。

4.后台线程

线程类型 默认个数 职责
Master Thread 1 核心后台线程,负责调度其他线程,将缓冲池中的数据异步刷新到磁盘,保持数据一致性(包括脏页刷新、合并插入缓存、undo 页回收)。
IO Thread 多个 负责处理异步 I/O(AIO)请求的回调。具体包括: • Read thread(4个):负责读操作 • Write thread(4个):负责写操作 • Log thread(1个):负责将日志缓冲区刷新到磁盘 • Insert buffer thread(1个):负责将写缓冲区内容刷新到磁盘
Purge Thread 1 回收事务已提交的 undo log。
Page Cleaner Thread 1 协助 Master Thread 刷新脏页到磁盘,减轻 Master Thread 压力,减少阻塞。

5.事务的原理

  • 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败回滚,通过undo log保证
  • 一致性(Consistency):事务执行前后,数据完整性约束没有被破坏,通过undo log + redo log + 其他机制保证
  • 隔离性(Isolation):多个事务并发执行时,相互隔离,互不干扰,通过锁机制 + MVCC保证
  • 持久性(Durability):事务一旦提交,其结果就是永久性的,通过redo log实现

原子性、一致性、持久性:undo log和redo log 保证

隔离性:事务和MVCC机制

(1)redo log(持久性保证)

 重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。

 该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。

 如下例所示,事务提交后,数据页(修改后的脏页)还在 Buffer Pool 中,并不会立即写入磁盘;后台线程负责刷新脏页,但此时如果刷新失败,则会导致数据不一致的问题

 如果直接刷新数据页到磁盘,此时的刷新操作是随机磁盘 I/O(这里的随机就是指更改的操作随机发生在缓冲区中,即一会update a,一会update b),这样性能低,但如果不即使刷新就可能丢失缓冲区中的数据,导致前后不一致。

 采用redo log日志的方式,update操作缓冲区会同步将这个更新操作写入redo log buffer中(记录数据页的变化),随后将数据持久化保存到磁盘文件当中如果后面脏页刷新的时候失败,可以通过redo log进行数据恢复 。(日志文件都是追加的,即顺序IO,效率较高),此过程成为WAL(Write-Ahead Logging),即日志先行。隔一段时间后会进行log文件清理。

(2)undo log(原子性)

 回滚日志用于记录数据被修改前的信息,作用包括:提供回滚功能和支持 MVCC(多版本并发控制)。

 undo log 和 redo log记录物理日志不一样,它是逻辑日志。例如,执行 DELETE 时,undo log 记录一条对应的 INSERT;执行 UPDATE 时,记录一条相反的 UPDATE。

  • 事务回滚:当执行rollback日志回滚时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
  • Undo log销毁:undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志可能还用于MVCC。
  • Undo log存储:undo log采用段的方式进行管理和记录,存放在前面介绍的 rollback segment 回滚段中,内部包含1024个undo log segment。

(3)MVCC(多版本并发控制)

 再进行MVCC多版本并发控制的时候,涉及到以下几个概念:

  • 当前读:读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select ... lock in share mode(共享锁),select ... for update、update、insert、delete(排他锁)都是一种当前读。
sql 复制代码
--服务器1
begin;
select 查询到旧数据
--服务器2进行数据更新操作
select 还是旧数据(因为默认的隔离级别是可重复读)
--当前读,读取的是新数据
select ... lock in share mode 新数据
  • 快照读 :简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
    • Read Committed 隔离级别:每次select,都生成一个快照读。
    • Repeatable Read 隔离级别:开启事务后第一个select语句才是快照读的地方。
    • Serializable 隔离级别:快照读会退化为当前读。
sql 复制代码
--默认的隔离级别
--服务器1
begin;
select 查询到旧数据同时生成快照
--服务器2进行数据更新操作,事务提交
select 还是旧数据(读取的是第一个快照)
  • MVCC:即 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现 MVCC 提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。

当前读并不依赖多版本,而是依赖锁来实现隔离

快照读 是 MVCC 机制的直接体现

a.三个隐式字段

 数据库表中每行记录除了用户定义的列外,还包含以下隐藏字段:

  • DB_TRX_ID:最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID。
  • DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本。
  • DB_ROW_ID:隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。
id age name DB_TRX_ID DB_ROLL_PTR DB_ROW_ID
1 1 tom 100 pointer1 (自动生成)
3 3 cat 101 pointer2 (自动生成)
b.undo log 版本链

 undo log 通过 DB_ROLL_PTR 将不同版本的记录串联起来,形成版本链。每次更新都会产生一条新的 undo log 记录,指向旧版本。

INSERT 产生的 undo log 只在回滚时需要,事务提交后可立即删除。
UPDATE、DELETE 产生的 undo log 在回滚和快照读时都需要,不会立即删除。

c.ReadView(读视图)

ReadView 是快照读执行时 MVCC 提取数据的依据,记录并维护系统当前活跃的事务(未提交)ID。核心字段如下所示:

字段 含义
m_ids 当前活跃的事务 ID 集合
min_trx_id 最小活跃事务 ID
max_trx_id 预分配事务 ID(当前最大事务 ID + 1,因为事务 ID 自增)
creator_trx_id 创建该 ReadView 的事务 ID

 可见性判断规则:当读取一条记录时,拿到该记录的 DB_TRX_ID(记为 trx_id),判断如下:

  • trx_id == creator_trx_id → 可以访问(当前事务自己的修改)。
  • trx_id < min_trx_id → 可以访问(该版本在 ReadView 创建前已提交)。
  • trx_id > max_trx_id → 不可以访问(该事务在 ReadView 生成后才开启)。
  • min_trx_id <= trx_id <= max_trx_id → 如果 trx_id 不在 m_ids 中,则可以访问(说明已提交),否则不可访问。

 不同隔离级别的 ReadView 生成时机:

  • READ COMMITTED:事务中每一次执行快照读都生成一个新的 ReadView。
  • REPEATABLE READ:仅在事务中第一次执行快照读时生成 ReadView,后续复用。

如下图所示,在RC隔离级别下,事务5中的查询id=30记录会生成一个ReadView,当前活跃的事务即还没提交的事务m_ids={3,4,5},min_trx_id=3,max_trx_id=5+1,创建该 ReadView 的事务 ID creator_trx_id=5。如上例所示,当前记录的DB_TRX_ID=4(即最近对这个数据进行操作的是事务4)。按照上面的规则4 != 5,4>3,4<6,3<=4<=6但是4存在m_ids中,都不满足,所以不可以访问,所以这一次快照读不应该读当前的数据,这个时候就会通过指针找上一条记录,再进行对比,直到满足可以访问的条件(trx_id的时候才满足可以访问,可以理解为查询的时候只有事务2提交了,所以可以查询到事务2 更改后的结果)

四、MyISAM

 MyISAM 是 MySQL 早期的默认存储引擎(MySQL 5.5 之前)。其特点如下,

  • 不支持事务,不支持外键
  • 支持表锁,不支持行锁
  • 访问速度快,适合读密集型、写少的场景。

 每个 MyISAM 表对应三个文件:

  • xxx.sdi(或 .frm):存储表结构信息。
  • xxx.MYD:存储数据。
  • xxx.MYI:存储索引。

五、Memory

 Memory 引擎将数据存储在内存中,访问速度极快,但服务器重启后数据丢失。其特点如下,

  • 基于哈希索引,默认支持 B+Tree 索引。
  • 表级锁,不支持事务。
  • 常用于临时表或缓存表。

 每个 Memory 表只对应一个 .sdi 文件(表结构),数据不持久化到磁盘。

六、InnoDB和MyISAM的区别

对比项 InnoDB MyISAM
事务支持 支持 不支持
外键支持 支持 不支持
锁粒度 行锁 + 表锁 表锁
并发性能 高(行锁) 低(表锁)
数据恢复 通过 redo log 自动恢复 需要手动修复
MVCC 支持 支持 不支持
全文索引 支持(MySQL 5.6+) 支持
文件格式 .ibd(数据和索引一起) .MYD(数据)+ .MYI(索引)
适用场景 OLTP(高并发写、事务) OLAP(读多写少,如数据仓库)

七、存储引擎的选择

场景 推荐存储引擎 原因
高并发、事务型业务(如银行、电商订单) InnoDB 支持事务、行锁、高并发
读多写少、对事务无要求(如日志、报表) MyISAM 读速度快,占用空间小
临时缓存、会话数据(数据可丢失) Memory 内存速度极快
需要全文搜索且对事务不敏感 MyISAM 或 InnoDB 5.6+ 两者都支持全文索引,但 InnoDB 更全面
相关推荐
huluang2 小时前
密评多选题 — 陷阱名单(费曼自述法版)
网络·数据库·密码学
曾瑞铭Raymond2 小时前
【侄女零基础升级打怪】Vibe Coding氛围编程 AI编程之MySQL 新手学习指引
mysql·ai编程·零基础学ai·瑞铭进阶升级练习稿·ai氛围编程思维
AOwhisky2 小时前
学习自测与解析:MySQL 系列第三期与第四期
linux·运维·数据库·学习·mysql·云计算
Esaka_Forever2 小时前
PyCharm 社区版无法识别 .db 文件类型
数据库·ide·pycharm
yyuuuzz2 小时前
AI模型部署中的常见稳定性问题
运维·服务器·网络·数据库·人工智能·云计算·github
li星野2 小时前
从零搭建带数据库的文件上传系统:FastAPI + Streamlit + SQLite+加上日志
数据库·sqlite·fastapi
程序猿乐锅3 小时前
【MySQL | 第七篇】 索引使用规则
数据库·sql·mysql
C137的本贾尼3 小时前
崩溃恢复揭秘:从 Redo Log 到数据一致性
数据库
Lyyaoo.3 小时前
【MySQL】锁机制
android·数据库·mysql