Mysql存储引擎
存储引擎
MySQL 存储引擎是 MySQL 数据库的核心组件之一,负责数据的存储、管理和检索。不同的存储引擎提供了不同的功能和性能特性。
存储引擎作用
- 数据存储
- 决定数据如何存储在磁盘或内存中。
- 管理数据的物理存储结构(如表空间、文件等)
- 数据检索
- 提供数据的读写接口。
- 支持索引机制,优化查询性能。
- 事务支持
- 部分存储引擎(如 InnoDB)支持事务,确保数据的 ACID 特性(原子性、一致性、隔离性、持久性)
- 并发控制:
- 提供锁机制(如行级锁、表级锁),管理多用户并发访问。
- 数据完整性:
- 支持外键约束、唯一约束等,确保数据的完整性和一致性。
- 性能优化:
- 不同的存储引擎针对不同的场景进行了优化(如高并发、高压缩、快速插入等)。
InnoDB
InnoDB 是 MySQL 中最常用、功能最强大的存储引擎之一。从 MySQL 5.5 版本开始,InnoDB 成为默认的存储引擎。
核心特性
-
事务支持(ACID 兼容)
-
行级锁
- InnoDB 支持行级锁,允许多个事务同时读写不同的行,极大地提高了并发性能。
- 与 MyISAM 的表级锁相比,行级锁更适合高并发场景
-
外键约束
- InnoDB 支持外键约束,确保数据的完整性和一致性。
- 外键约束可以防止无效数据的插入或更新
-
崩溃恢复
- InnoDB 提供了崩溃恢复机制,通过日志文件(redo log)确保数据在系统崩溃后能够恢复到一致状态
-
多版本并发控制(MVCC)
- InnoDB 使用 MVCC 机制实现非阻塞读操作,提高了并发性能。
- MVCC 通过保存数据的多个版本来实现事务的隔离性
-
表空间管理
- InnoDB 将数据存储在表空间(tablespace)中。
- 支持独立表空间(每张表一个文件)和共享表空间(所有表共享一个文件)
存储结构
表空间
表空间是 InnoDB 存储结构的核心,用于存储表的数据和索引。InnoDB 的表空间分为以下几种
-
系统表空间(System Tablespace)
- 存储 InnoDB 的元数据、undo 日志、双写缓冲区等。
- 默认文件名为 ibdata1,可以通过参数 innodb_data_file_path 配置。
- 如果启用了独立表空间(innodb_file_per_table=ON),则用户表的数据和索引不存储在系统表空间中
- 如果没有启用独立表空间(innodb_file_per_table=OFF),用户表的数据和索引也会存储在系统表空间中
- 默认文件名为 ibdata1,可以通过参数 innodb_data_file_path 配置
- 文件可以自动扩展(通过 autoextend 配置)
-
独立表空间(File-Per-Table Tablespace)
- 每个表的数据和索引存储在一个独立的 .ibd 文件中。
- 文件名为 表名.ibd,存储在数据库目录下
- 通过参数 innodb_file_per_table 启用(默认启用)。
- 便于管理和维护。
- 可以单独备份和恢复表。
- 减少系统表空间的压力。
-
通用表空间(General Tablespace)
-
多个表可以共享一个表空间文件。
-
通过 CREATE TABLESPACE 语句创建
-
在创建表时指定表空间
CREATE TABLESPACE ts1 ADD DATAFILE 'ts1.ibd' ENGINE=InnoDB; CREATE TABLE t1 (id INT) TABLESPACE ts1;- 减少文件数量,便于管理。
- 支持压缩表
-
-
临时表空间(Temporary Tablespace)
- 存储临时表和临时数据。
- 默认文件名为 ibtmp1
- 临时表空间在 MySQL 启动时创建,关闭时删除
-
Undo 表空间(Undo Tablespace)
- 存储 undo 日志,用于事务回滚和多版本并发控制(MVCC)
- 默认文件名为 undo_001、undo_002 等
- 从 MySQL 5.7 开始,undo 日志可以存储在独立的 undo 表空间中,而不是系统表空间
数据页
- InnoDB 将数据划分为固定大小的页(Page),页是磁盘和内存之间数据传输的最小单位。默认页大小为 16KB,可以通过参数 innodb_page_size 配置
-
页的类型
- 数据页:存储表的数据行。
- 存储表的数据和索引(B+ 树的叶子节点存储数据,非叶子节点存储索引)。
- 是最常用的页类型
- InnoDB 的数据页(叶子节点)通过双向链表连接,支持顺序访问,但物理存储可能分散在不同的页中
- undo 页:存储 undo 日志,用于事务回滚和 MVCC。
- 系统页:存储元数据和其他系统信息
- 等等
- 数据页:存储表的数据行。
-
页的结构
每个页包含以下部分
- 文件头(File Header):记录页的元信息,如页号、页类型等。
- 数据行(Rows):存储实际的数据。
- 页目录(Page Directory):记录数据行的位置,用于快速查找。
- 文件尾(File Trailer):用于校验页的完整性
索引结构
- InnoDB 使用 B+ 树作为索引结构,B+ 树是一种平衡多路搜索树,具有以下特点
- 多层级结构:由根节点、非叶子节点和叶子节点组成。
- 有序性:所有节点中的键值都是有序的。
- 叶子节点链表:叶子节点通过双向链表连接,支持高效的范围查询。
- 高扇出性:每个节点可以存储多个键值和指针,减少树的高度,提高查询效率
- InnoDB 中有两种主要的索引类型
- 聚集索引(Clustered Index)
- 二级索引(Secondary Index)
聚集索引(Clustered Index)
- 聚集索引是 InnoDB 中最重要的索引,它的特点如下
- 数据存储方式
- 聚集索引的叶子节点存储的是 完整的数据行,而不是指向数据的指针。
- 数据和索引是存储在一起的,因此聚集索引决定了数据的物理存储顺序
- 由于聚集索引决定了数据的物理存储顺序,当数据被插入、更新或删除时,数据库管理系统会调整数据的物理位置,以保持与聚集索引的顺序一致
- 假设有一个包含员工信息的表,并以员工ID作为聚集索引。那么,表中的数据行会按照员工ID的顺序存储在磁盘上。如果插入一个新员工,数据库会将其放置在正确的位置,以保持员工ID的顺序
- 为了保持聚集索引的顺序,数据库会重新排列数据的物理存储位置。这可能会导致一定的性能开销
- 聚集索引的选择
- 如果表定义了主键,主键就是聚集索引。
- 如果没有主键,InnoDB 会选择第一个唯一的非空索引作为聚集索引。
- 如果没有唯一索引,InnoDB 会隐式创建一个隐藏的 RowID 作为聚集索引
- 查询效率
- 聚簇索引的非叶子节点存储的是索引键值 + 指针,用于快速定位叶子节点
- 通过聚集索引查询数据时,可以直接定位到叶子节点获取数据,效率非常高。
- 范围查询(如 BETWEEN、>、<)也非常高效,因为叶子节点是有序的,且通过链表连接
- 数据存储方式
二级索引(Secondary Index)
- 二级索引是用户自定义的索引,它的特点如下:
- 数据存储方式
-
二级索引的叶子节点存储的是 索引列的值 + 主键值,而不是完整的数据行。
-
非叶子节点存储的是索引列的值和指向下一级节点的指针(通常是页号或块号),用于快速定位叶子节点
-
通过二级索引查询数据时,需要先找到主键值,然后再通过主键值到聚集索引中查找数据行(即 回表 操作)
-
- 索引结构和查询效率
- 二级索引也是基于 B+ 树的结构,但其叶子节点存储的是主键值和索引列的值
- 二级索引适合用于等值查询
- 避免过多的二级索引,因为每次数据更新都需要维护索引
- 数据存储方式
B+ 树的具体结构
-
非叶子节点(Non-Leaf Node)
- 存储 索引键值 和 指针。
- 指针指向子节点(可能是非叶子节点或叶子节点)。
- 非叶子节点不存储实际数据,只用于导航到叶子节点。
-
叶子节点(Leaf Node)
- *聚集索引的叶子节点:
- **存储 完整的数据行。
- 叶子节点之间通过双向链表连接,支持高效的范围查询。
- 二级索引的叶子节点:
- 存储 主键值和索引列的值。
- 叶子节点之间也通过双向链表连接。
- *聚集索引的叶子节点:
缓冲池(Buffer Pool)
- InnoDB 的 缓冲池(Buffer Pool) 是其核心组件之一,主要用于缓存数据和索引页,以减少磁盘 I/O 操作,提升数据库性能
缓冲池的主要作用
- 缓存数据和索引页:将磁盘上的数据页和索引页加载到内存中,供查询和修改操作使用。
- 减少磁盘 I/O:通过缓存数据,避免频繁访问磁盘,从而提升性能。
- 支持事务的隔离性和一致性:通过缓冲池管理数据的修改和回滚操作
缓冲池的结构
- 缓冲池是一个连续的内存区域,由多个 页(Page) 组成。每个页的大小通常为 16KB(与磁盘上的数据页大小一致)
缓冲池工作流程
- 数据读取
- 当查询数据时,InnoDB 首先检查缓冲池中是否存在所需的页。
- 如果页在缓冲池中(缓存命中),则直接使用。
- 如果页不在缓冲池中(缓存未命中),则从磁盘加载页到缓冲池,并更新 LRU 链表
- 数据修改
- 当修改数据时,InnoDB 首先在缓冲池中找到对应的页。
- 修改页的内容,并将页标记为脏页。
- 将脏页加入刷新链表,等待刷新到磁盘
- 页的淘汰
- 当缓冲池空间不足时,InnoDB 会从 LRU 链表的旧子链表中淘汰页。
- 如果淘汰的页是脏页,则需要先将其写回磁盘
- 页的刷新
- InnoDB 会定期将刷新链表中的脏页写回磁盘(称为 Checkpoint)。
- 刷新操作可以通过后台线程异步执行,以减少对性能的影响
双写缓冲区(Doublewrite Buffer)
双写缓冲区是 InnoDB 用于防止数据页写入不完整的一种机制。
部分写问题
- 在 InnoDB 中,数据页的大小通常为 16KB,而磁盘的块大小通常为 4KB。
- 如果在写入磁盘时发生崩溃,可能导致只有部分数据(如 4KB)被写入磁盘,而其他部分丢失,这种现象称为 部分写。
- 部分写会导致数据页损坏,无法通过 InnoDB 的恢复机制修复
双写缓冲区的解决方案
- 双写缓冲区通过在写入磁盘之前,先将数据页写入一个 临时区域(双写缓冲区),然后再写入实际的数据文件。
- 如果在写入过程中发生崩溃,InnoDB 可以通过双写缓冲区中的副本来恢复损坏的数据页
工作流程
- 写入双写缓冲区:
- 当需要将一个脏页写入磁盘时,InnoDB 首先将该页写入双写缓冲区。
- 双写缓冲区的写入是 顺序写入,性能较高
- 写入实际数据文件:
- 在双写缓冲区写入完成后,InnoDB 再将数据页写入实际的数据文件。
- 刷新双写缓冲区:
- 双写缓冲区的数据会在后台定期刷新到磁盘
与缓冲池的区别
- 缓冲池(Buffer Pool):
- 用于缓存数据和索引页,减少磁盘 I/O,提升查询性能。
- 是内存中的一块区域,存储的是从磁盘加载的数据页。
- 双写缓冲区(Double Write Buffer):
- 用于保证数据页写入磁盘时的完整性,避免部分写问题。
- 是磁盘上的一块区域,存储的是数据页的副本。
日志文件
- 日志文件机制 是其实现事务 持久性(Durability) 和 崩溃恢复(Crash Recovery) 的核心组件。InnoDB 使用 重做日志(Redo Log) 和 回滚日志(Undo Log) 来确保数据的一致性和可靠性
1. Redo Log(重做日志)
Redo Log的作用
- 持久性:确保事务提交后,即使系统崩溃,修改的数据也不会丢失。
- 崩溃恢复:在系统崩溃后,通过重做日志恢复未写入磁盘的数据
Redo Log的结构
- 重做日志由一组固定大小的文件组成,通常命名为 ib_logfile0 和 ib_logfile1。
- 每个日志文件的大小通过参数 innodb_log_file_size 配置。
- 日志文件是循环使用的,写满后会从头开始覆盖
- 在系统崩溃后,InnoDB 会读取重做日志文件,重新应用未写入磁盘的修改操作,确保数据的一致性
Redo Log的写入流程
- 日志记录生成:
- 当事务修改数据时,InnoDB 会生成一条重做日志记录,描述对数据页的修改。
- 日志缓冲写入:
- 重做日志记录首先写入 日志缓冲(Log Buffer),日志缓冲是内存中的一块区域。
- 日志刷盘:
- 当日志缓冲满或事务提交时,InnoDB 会将日志缓冲中的内容写入磁盘的重做日志文件。
- 日志刷盘的时机由参数
innodb_flush_log_at_trx_commit控制:=1:每次事务提交时都刷盘(默认,最安全)。=2:每次事务提交时写入操作系统缓存,但不刷盘。=0:每秒刷盘一次。
2. Undo Log(回滚日志)
回滚日志是 InnoDB 用于实现事务 原子性(Atomicity) 和 多版本并发控制(MVCC) 的机制。它记录了事务修改前的数据状态,用于回滚事务和提供一致性视图
Undo Log的作用
- 事务回滚:当事务需要回滚时,InnoDB 使用回滚日志将数据恢复到修改前的状态。
- MVCC:为并发事务提供一致性视图,支持非锁定读
- 在系统崩溃后,InnoDB 会检查未完成的事务,并使用回滚日志回滚这些事务,确保数据的一致性
Undo Log的结构
- 回滚日志存储在 回滚段(Undo Segments) 中,回滚段位于系统表空间或独立的回滚表空间中。
- 每个事务会分配一个回滚段,用于存储其修改前的数据
Undo Log的写入流程
- 日志记录生成:
- 当事务修改数据时,InnoDB 会生成一条回滚日志记录,记录修改前的数据状态。
- 日志存储:
- 回滚日志记录存储在回滚段中。
- 日志清理:
- 当事务提交后,回滚日志不会立即删除,而是保留一段时间,用于支持 MVCC。
- 当没有事务需要旧版本的数据时,回滚日志会被清理
锁机制
InnoDB 的 锁机制 是其实现事务 隔离性(Isolation) 和 并发控制 的核心组件。InnoDB 支持多种类型的锁,包括 行级锁、表级锁 和 意向锁,这些锁共同协作,确保并发事务的正确性和一致性。
锁的类型
- 行级锁(Row-Level Lock)
- 共享锁(S Lock,Shared Lock):
- 允许事务读取一行数据。
- 多个事务可以同时持有共享锁。
- 语法:
SELECT ... LOCK IN SHARE MODE
- 排他锁(X Lock,Exclusive Lock):
- 允许事务更新或删除一行数据。
- 只有一个事务可以持有排他锁,其他事务不能加任何锁。
- 语法:
SELECT ... FOR UPDATE
- 共享锁(S Lock,Shared Lock):
- 表级锁(Table-Level Lock)
- 表级共享锁(Table S Lock):
- 允许事务读取整个表的数据。
- 多个事务可以同时持有表级共享锁。
- 表级排他锁(Table X Lock):
- 允许事务更新或删除整个表的数据。
- 只有一个事务可以持有表级排他锁
- 表级共享锁(Table S Lock):
- 意向锁(Intention Lock)
- 意向锁是表级锁,用于表明事务将在表中的某些行上加锁。
- 意向共享锁(IS Lock,Intention Shared Lock):
- 表明事务将在某些行上加共享锁。
- 意向排他锁(IX Lock,Intention Exclusive Lock):
- 表明事务将在某些行上加排他锁
- 间隙锁(Gap Lock)
- 间隙锁是对 索引记录之间的间隙 加锁,而不是对具体的记录加锁
- 间隙锁锁定的是一个范围,防止其他事务在这个范围内插入新的记录
- 间隙锁是 共享锁,多个事务可以同时对同一个间隙加间隙锁
- 临键锁(Next-Key Lock)
- 临键锁是 记录锁(Record Lock) 和 间隙锁(Gap Lock) 的结合
- 它锁定的是 索引记录本身 以及 该记录之前的间隙
- 通过锁定记录和间隙,确保在事务执行期间,不会有新的记录插入到锁定范围内
- 在范围查询或等值查询时,临键锁可以防止其他事务插入或修改数据
- 临键锁是 排他锁,只有一个事务可以持有临键锁
锁的实现
-
行级锁的实现
- InnoDB 的行级锁是通过 索引 实现的。
- 如果查询使用了索引,InnoDB 会对索引记录加锁。
- 如果查询没有使用索引,InnoDB 会对整个表加锁(表级锁)。
- 如果查询没有使用索引(例如全表扫描),InnoDB 会对整个表的 所有间隙 加间隙锁
- 行级锁的粒度较小,支持高并发
- InnoDB 的行级锁是通过 索引 实现的。
-
表级锁的实现
- 表级锁是对整个表加锁,粒度较大,通常用于 DDL 操作(如 ALTER TABLE)。
- 表级锁会阻塞其他事务对表的访问,影响并发性能
-
意向锁的实现
- 意向锁是表级锁,用于协调行级锁和表级锁。
- 当事务需要在某些行上加锁时,首先在表上加意向锁,表明其意图
死锁处理
- InnoDB 会自动检测死锁,并选择一个事务进行回滚(通常是影响较小的事务)。
- 可以通过以下方式减少死锁:
- 按照相同的顺序访问资源。
- 使用
SELECT ... FOR UPDATE或LOCK IN SHARE MODE明确加锁
MyISAM
特点:
-
表级锁
- MyISAM 使用 表级锁,而不是行级锁。
- 当对表进行写操作(如
INSERT、UPDATE、DELETE)时,会对整个表加锁。 - 当对表进行读操作时,会对整个表加共享锁。
- 当对表进行写操作(如
- 表级锁的优点是实现简单,开销小;缺点是并发性能较差,写操作会阻塞读操作
- MyISAM 使用 表级锁,而不是行级锁。
-
不支持事务
- MyISAM 不支持事务,也不支持 ACID 特性。
- 如果操作过程中发生崩溃,数据可能会不一致。
- 无法回滚操作
- MyISAM 不支持事务,也不支持 ACID 特性。
-
不支持外键
如何选择
- 选择 InnoDB:
- 如果需要事务支持、高并发读写、数据一致性保证或外键约束。
- 大多数现代应用都推荐使用 InnoDB。
- 选择 MyISAM:
- 如果应用是读密集型、不需要事务支持、或者需要全文索引。
- 适合小型项目或对性能要求不高的场景。

浙公网安备 33010602011771号