存储引擎就是如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法。因为在关系数据库中数据的存储是以表的形式存储的,所以存储引擎也可以称为表类型(即存储和 操作此表的类型)
MySQL数据库提供了多种存储引擎。用户可以根据不同的需求为数据表选择不同的存储引擎,用户也可以根据自己的需要编写自己的存储引擎。
SQL 解析器、SQL 优化器、缓冲池、存储引擎等组件在每个数据库中都存在,但不是每 个数据库都有这么多存储引擎。例如,有的应用需要满足事务的要求,有的应用则不需要对事务有这么强的要求;有的希望数据能持久存储,有的只希望放在内存中,临时并快速地提供对数据的查询。
相关命令
1 | # 查看所有支持的存储引擎 |
Innodb存储引擎支持以下核心特性:事务、行级锁、外键、MVCC、备份和恢复、自动故障恢复,此处重点讲解Innodb存储引擎
如下图所示,InnoDB的逻辑架构主要分为三个大的组成部分:
在内存中的架构(In-Memory Structures);
操作系统缓存(Operating System Cache)。
在硬盘上的架构(On-Disk Structures);
InnoDB的内存架构分为4个部分:
缓冲池(Buffer Pool)
1 | 缓冲池是一块用于缓存被访问过的表和索引数据的内存区域,缓冲池允许在内存中处理一些被用户频繁访问的数据,在某一些专用的服务器上,甚至有可能使用80%的物理内存作为缓冲池。 |
写缓冲(Change Buffer)
1 | 写缓冲是为了缓存缓冲池(Buffer Pool)中不存在的二级索引(Secondary Index)页的变更操作的一种特殊的内存数据结构。 |
日志缓冲(Log Buffer)
1 | InnoDB将数据的每次写优化为了批量写,这便以降低磁盘IO的次数,为了防止一些数据尚未写入硬盘就断电了,需要记录日志。 |
自适应哈希索引(Adaptive Hash Index)
1 | 在InnoDB中,用户是不可以直接去创建哈希索引的,这个自适应哈希索引是InnoDB为了加速查询性能,会根据实际需要来决定是否对于一些频繁需要被访问的索引页构建哈希索引,它会利用key的前缀来构建哈希索引。这样做可以提高查询性能,因为索引采用类似B+树的结构进行存储,B+树的单key查询时间复杂度为O(log2n),但是优化为哈希索引后,单key的查询时间复杂度就为O(1)了。 |
操作系统为了提升性能而降低磁盘IO的次数,在InnoDB的缓存体系与磁盘文件之间,加了一层操作系统的缓存/页面缓存。用户态innodb存储引擎的进程向操作系统发起write系统调用时,在内核态完成页面缓存写入后即返回,如果想立即将页面缓存的内容立即刷入磁盘,innodb存储引擎需要发起fsync系统调用才可以。
fsync和write操作是系统调用函数,在很多持久化场景都有使用到,比如 Redis 的AOF持久化中也使用到两个函数。
InnoDB在硬盘上总共分为六个部分,也就是:
表(Tables)
1 | 1、如果已经指定了数据的默认存储引擎,那么创建表的时候,无需指定再指定存储引擎。 |
表空间(Tablespaces)
1 | 在InnoDB中,表空间总共分为: |
索引(Indexes);
1 | 按键的类别划分:主键索引和二级索引/辅助索引; |
双写缓冲(Doublewrite Buffer)
1 | 双写缓冲是一个在系统表空间System Tablespaces中存储区,在这个存储区中,在InnoDB将页面写入InnoDB数据文件中的适当位置之前,会先从缓冲池中刷新页面 。如果在页面写入过程中发生操作系统,存储子系统或mysqld进程崩溃,则InnoDB可以在崩溃恢复期间从双写缓冲中找到页面的原来的数据。 |
Redo日志:记录的是尚未完成的操作,断电则用其重做
1 | redo即redo日志,是用于记录数据库中数据变化的日志,只要修改了数据块那么就会记录redo信息,当然nologging除外了。 |
Undo段:记录的改动之前的旧数据,一旦改错,可以回滚
1 | undo即undo段,是指数据库为了保持读一致性,存储历史数据在一个位置。 |
InnoDB存储引擎的逻辑存储结构和 Oracle大致相同 ,所有数据都被逻辑地存放在一个空间中 ,我们称之为表空间 ( tablespace ) ,表空间又由:段 ( segment ) 、区 ( extent ) 、页 ( page ) 组成 。页在一些文档中有时也称为块(block)或磁盘块,一次io操作的是一个磁盘的数据,即一页数据。
InnoDB存储引擎的逻辑存储结构大致如下图所示
详解如下
Row行
1 | 一个Row存放的是一行内容,有trx id,回滚指针,该行包含的n列内容 |
Page页:最多包含7992行记录
1 | 多个Row组织到一个Page页中,一个Page页即一个磁盘块大小,是io操作的最小物理存储单元,也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。 |
Extent区:由64个连续的页组成的
1 | 区是由64个连续的页组成的,每个页大小为16KB,即每个区的大小为1MB。 |
Segment 段 :最多由4个区组成
1 | 对于大的数据段,InnoDB存储引擎最多每次可以申请4个区,以此来保证数据的顺序性能。 |
Tablespace 表空间
1 | 表空间由三种段构成 |
总结:
7992行—>一页(16kB)
64个页—>一个区(1MB)
4个区—> 一个数据段(4M)
叶子节点数据段+非叶子节点数据段+回滚数据段-》一个表空间
从mysql5.6版本开始,引入了表空间传输的功能。可以把一张表从一个数据库移动到另一个数据库中或者另一台机器上。使用该功能必须满足如下条件:
Mysql版本必须是5.6及以上
使用独立表空间方式,现在版本默认开启innodb_file_per_table
源库和目标库的page size必须一致,表结构必须一致,比如:
1 | # 如果是把从5.6迁移到5.7,需要注意表的row_format也应该保持一致,可以用下述sql查看 |
如果要做表的导出操作,该表只能进行只读操作
操作步骤
在原机器上为t1表加读锁(只能读,不能写,目的是保证数据一致性),然后把数据从内存导出到磁盘上。
1 | FLUSH TABLES t1 FOR EXPORT; |
在新机器上创建与原表一样的表结构
1 | create table t1(字段1 类型,字段2 类型,...); |
然后关闭t1表的数据空间,即删除.ibd文件
1 | ALTER TABLE t1 DISCARD TABLESPACE; |
在原机器上将原表的t1.cfg和t1.ibd拷贝到主机B的数据目录下。注意拷贝的ibd文件的属主属组与权限问题。
1 | # 拷贝完后原机器执行 |
在新机器上恢复表空间
1 | # 导入表空间,自动进行恢复过程 |
如下图所示,一条数据记录的更新过程可以分为8个步骤:
各部分作用简介
1 | # 1、缓冲池 buffer pool |
综上,执行一条更新sql语句,存储引擎执行流程可以分为三大阶段
redo log刷盘策略
binlog刷盘策略
commit标记的意义
commit写入redo log,才能判定事务成功;因为此时,redo log中有这次更新记录,binlog也有这次更新记录,redo log和binlog保持了一致,否则
redo log刷盘成功,binlog还没刷盘
数据库宕机,没有commit标记写到redo log中,事务判定为失败。
因为redolog中有这次更新日志,binlog中没有这次更新日志,会出现数据不一致问题
redo log刷盘成功,binlog刷盘成功
commit标记还没来得及写入redo log中,数据库宕机,同样判定事务提交失败
内存(buffer pool)中更新过脏数据什么时候刷盘
后台io线程有时间会把内存buffer pool中更新过的脏数据(因为更新过,和磁盘上的数据不一样,所以叫脏数据)刷回到磁盘上,哪怕这时候mysql宕机,也没有关系,可通过redo log和binlog恢复数据到内存中,io线程有时间再把数据刷盘,那何时刷呢?
1 | - 1、redo log满的情况下才会主动刷入磁盘 |
LRU(Least Recently Used) 淘汰策略
1 | 以上我们了解了 InnoDB 的更新和插入操作的具体实现原理,接下来我们再来了解下它的实现和优化方式。 |