文章目录
前言
在上一篇中,闲聊MySQL:(四)深入分析InnoDB之内存架构,我们对MySQL InnoDB的内部架构模型进行了简单的介绍,主要分析了InnoDB的内存结构,我们用下图一起来回顾一下:
本篇,我们继续聊聊InnoDB,了解一下上图的右半部分,其硬盘存储架构。
InnoDB硬盘存储架构组成
InnoDB的硬盘存储架构主要分为几大部分组成,包括表空间、数据表、数据字典、双写缓冲区、更改缓冲区、Undo Logs、Redo Log,接下来我们分别介绍一下这些区域的作用。
表(Tables)
逻辑存储结构
InnoDB中,所有数据都被逻辑地存放在一个空间中,我们称之为表空间(tablespace)。我们可以从下图,来看一下数据的逻辑存储结构:
从上图可以看到,表空间又由段(segment)、区(extent)、页(page)组成。真正的每一行数据,都是存储在页中的。
无论什么存储引擎中,每个表都有一个以表名命名的.frm文件,存放表的元数据(meta)信息,包括表结构的定义信息等。而表的数据,则是存储在表空间当中。
表空间(Tablespace)
表空间可以看做是InnoDB引擎存储结构的最高层,所有的数据都存放在表空间当中。InnoDB有一个的系统表空间ibdata,默认情况下,创建的表都会被存放在这个表空间下。
也可以指定将表存储在单独的表空间中,当参数innodb_file_per_table启用时,数据表将隐式的创建在独立的file-per-table表空间上。在5.6.6版本后,默认启用。
当创建数据表在file-per-table表空间上时,MySQL也会创建一个.ibd的表空间文件在数据库目录下。当一个表被创建在系统表空间上时,它会被分配到一个在数据库data目录下已经存在的ibdata文件中去。
需要注意的是,每张表的表空间内存放的只是数据、索引和Change Buffer更改缓冲区,而其他类的数据,如撤销(Undo)信息、系统事务信息、二次写缓冲(double write buffer)等还是存放在原来的共享表空间内。
段(segment)
表空间是由各个段组成的,常见的段有数据段、索引段、回滚段等。InnoDB存储引擎表是索引组织的(index organized),因此数据即索引,索引即数据。数据段即为B+树的页节点(leaf node segment),索引段即为B+树的非页节点(non-leaf node segment)。
区(extent)
区(extent)是由64个连续的页组成的,默认情况下,每个页大小为16KB,即每个区的大小为1MB。
但是当用户启用参数innodb_file_per_talbe后,创建的表默认大小为96KB。表是由区组成,那么默认大小应该是1M才对呀?
这是因为在每个段开始时,先有32个页大小的碎片页(fragment page)来存放数据,当这些页使用完之后才是64个连续页的申请。这样的好处是,对于一些小表,可以在开始时申请较少的空间,节省磁盘容量开销。
页(page)
页是InnoDB磁盘管理的最小单位,数据表中的实际每一行数据也是存储在页当中。在上一篇中,我们讲到InnoDB内部缓冲池的操作,都是基于对页的操作。
页的大小,可以通过参数 innodb_page_size 进行调整,可以设定为4K、8K、16K,默认为16K。
行(row)
InnoDB存储引擎是面向行的(row-oriented),也就是说数据的存放按行进行存放。每个页中可以存放多行记录。
行记录格式(row format)
行记录格式决定了行数据如何进行物理存储,直接关系到了SQL语句执行的性能。
InnoDB目前支持四种行记录格式:
- REDUNDANT
- COMPACT
- DYNAMIC
- COMPRESSED
在MySQL 5.6版本中,默认的行记录格式为COMPACT。而在最新的8.0版本中,默认的行记录格式为DYNAMIC。
它们的区别可以参考下表:
行格式 | 紧凑存储特性 | 增强的可变长度列存储 | 大索引键前缀支持 | 压缩支持 | 支持的表空间类型 |
---|---|---|---|---|---|
REDUNDANT | 否 | 否 | 否 | 否 | 否 |
COMPACT | 是 | 否 | 否 | 否 | 否 |
DYNAMIC | 是 | 是 | 是 | 是 | 否 |
COMPRESSED | 是 | 是 | 是 | 是 | 是 |
数据表的注意事项
InnoDB中,对于数据表的创建,有一定的限制,我们在创建表时需要进行注意:
-
1、单表最大可以支持1017列
-
2、单表最大支持64个二级索引
-
3、默认情况下,索引键的前缀长度限制为767 bytes。例如,当你使用了一个超过255个字符的 TEXT或VARCHAR类型的字段作为索引,假设在utfmb4的字符集下每个字符占4个字节,这时你可能会触发索引长度的限制。这种情况,可以启用 innodb_large_prefix 参数,在使用DYNAMIC或者COMPRESSED 行格式化的时候,索引字段长度限制最大可以达到3072字节,即768个字符。
在主从模式下,如果在master上启用了该参数,记得在slave上也要开启对应的参数,避免报错的情况发生。 -
4、索引指定最大字段的个数被限定为16个字段,超出会抛出异常
ERROR 1070 (42000): Too many key parts specified; max 16 parts allowed
-
5、MySQL限定一行数据所有
varchar
字段大小必须小于65535个字节,超出会抛出异常。mysql> CREATE TABLE t (a VARCHAR(8000), b VARCHAR(10000), -> c VARCHAR(10000), d VARCHAR(10000), e VARCHAR(10000), -> f VARCHAR(10000), g VARCHAR(10000)) ENGINE=InnoDB; ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs
数据字典
InnoDB数据字典由内部系统表组成,用于维护用户表的各种信息的一组内部表。这些系统表包含用于跟踪对象(如表,索引和列)的元数据。
这些数据存储在系统表空间中。在8.0版本以前,由于历史原因,数据字典除了存在于系统表空间(.ibd)中,还存在于数据表(.frm)中,而且在一定程度上彼此的信息是重叠的。
在最新的8.0版本中,数据字典信息全部存储在了InnoDB数据表中。
InnoDB系统表空间内的数据字典实现方式,由4个最基本的系统表来存储表的元数据:表、列、索引、索引列等信息。
这4个表分别是SYS_TABLES、SYS_COLUMNS、SYS_INDEXES、SYS_FIELDS。
SYS_TABLES
存储所有以InnoDB为存储引擎的表,每条记录对应一个表。
SYS_COLUMNS
存储每张数据表的列信息,每一列对应一条记录。
SYS_INDEXES
存储每张数据表的索引信息,每条记录对应一个索引。
SYS_FIELDS
存储每张数据表定义的索引列,每条记录对应一个索引列。
双写缓冲区(Doublewrite Buffer)
双写缓冲区是一个位于系统表空间的存储区域。它的作用是保证InnoDB引擎的数据页可靠性。
在没有双写缓冲区之前,当发生数据库宕机时,可能InnoDB引擎正在写入某个页到表中,而这个页只写了一部分,比如一个页16K,当只写到4K的时候,之后就发生了宕机。这种情况下,就可能出现数据丢失的问题。
为了避免上述的场景出现,在数据写入时,InnoDB先把从缓冲池中的得到的page写入系统表空间的双写缓冲区。之后,再把page写到.ibd数据文件中相应的位置。
如果在写page的过程中发生意外崩溃,InnoDB在稍后的恢复过程中在双写缓冲区中找到完好的page副本用于恢复。
下图是双写的过程:
doublewrite是顺序写,开销并不大。
所以在正常的情况下, MySQL写数据page时,会写两遍到磁盘上,第一遍是写到双写缓冲区中,第二遍是从双写缓冲区写到真正的数据文件中。
如果发生了极端情况(断电),InnoDB再次启动后,发现了一个page数据已经损坏,那么此时就可以从双写缓冲区中进行数据恢复了。
回滚日志(Undo Logs)
回滚日志undo log 是与事务关联的undo log记录的集合。当我们对记录做了变更操作时就会产生undo记录,Undo记录默认被记录到系统表空间(ibdata)中,但从5.6开始,也可以使用独立的Undo 表空间。
在之前的文章介绍InnoDB MVCC机制的时候,我们介绍了undo log日志。
如果您对InnoDB MVCC机制不太熟悉,可以查看这里:
闲聊MySQL:(三)深入分析InnoDB之多版本控制MVCC
重做日志(Redo Log)
重做日志redo log是在数据库崩溃后,恢复期间用于纠正由未完成事务写入的数据的基于硬盘里的一种日志。
简单点说,重做日志是把那些之前仅仅写到缓冲池中,但还没来得及刷新到硬盘里的脏页,把结果重新执行一遍。
也就是说,在意外关闭之前没有完成更新的数据文件,将在初始化期间以及连接被接受之前自动恢复。
默认情况下,重做日志在物理上表现为一组名为ib_logfile0和ib_logfile1的文件。
而在InnoDB的内存中,有一块区域与redo log日志文件进行对应,叫做redo log缓冲区。
redo log缓冲区是一块内存区域,保存将要写入redo log的数据。
redo log缓冲区大小由innodb_log_buffer_size配置选项定义。redo log缓冲区会定期把内存中的回滚日志刷到磁盘上。一个大的redo log缓冲区意味着允许大事务运行,而无需在事务提交之前将redo log写入磁盘。因此,如果您有更新,插入或删除多行的事务,则使用更大的redo log缓冲区可节省磁盘I/O。
重做日志缓冲区的实现机制
InnoDB 修改和新增数据操作,实际上操作的是缓冲池Buffer Pool中的数据。
为了保障数据的安全稳定,不丢失数据。InnoDB并不是一个事务提交后就将 Buffer Pool 中被修改的数据同步到硬盘上,而是要先记录到redo log日志中,以防崩溃之后可以恢复。
最后再从Buffer Pool 中把数据页连续写入磁盘。
而是记录到redo log日志中也不是直接写磁盘,而是先写到redo log 缓冲区中。
可以通过参数innodb_flush_log_at_trx_commit
参数控制如何将redo log缓冲区的内容写入到日志文件。
参数innodb_flush_log_at_timeout
控制redo log缓存写到redo log文件的频率。
- innodb_flush_log_at_trx_commit = 0, InnoDB 中的 Log Thread 每隔 1秒将 log buffer 中的数据写入文件,同时还会通知文件系统进行与文件同步的 flush操作,保证数据确实已经写入磁盘。
- innodb_flush_log_at_trx_commit = 1, InnoDB 默认设置。每次事务的结束都会出发 Log Thread 将 Log Buffer 中的数据写入文件、并通知文件系统同步文件。这个设置最安全,能够保证不论是 MySQL 崩溃、OS崩溃还是主机断电都不会丢失任何已经提交的数据。
- innodb_flush_log_at_trx_commit = 2, 每次事务结束的时候将数据写入事务日志,仅仅是调用了文件系统的文件写入操作。而文件系统都是有缓存机制的,所以 Log Thread 的写入并不能保证内容已经写入到物理磁盘完成持久化的动作。文件系统什么时候会将缓存中的数据同步到物理磁盘、文件, Log Thread 就完全不知道,所以,当设置 2 的时候, MySQL 崩溃并不会造成数据的丢失,但是 OS 崩溃或主机断电后可能丢失的数据量就完全控制在文件上了。
从上面的三种方式可以看出:
设置1 最安全,由于 IO 同步操作多,所以,性能最低。
设置 0 ,则每秒一次同步,性能相对高一下。
如果设置 2 ,性能可能是三种最好的。但是也可能出现故障后丢失数据最多的一种。如果 OS 足够稳定,主键硬件设备足够好,且主机供电系统足够安全,可将设置为2 ,让系统整体性能尽可能高。【建议设置为 2】
结语
本篇,我们对InnoDB架构中内部硬盘结构进行了简要了解,了解到了InnoDB对内部文件存储的各种机制。
InnoDB的内部结构非常复杂,本篇只是简单的对其进行了介绍,如果您感兴趣,请仔细去看一遍MySQL的官方文档对于各部分结构的描述,这样可以更加全面具体的了解InnoDB的架构。
下一篇,我们继续聊InnoDB,对InnoDB的索引结构进行分析,敬请期待!
更多精彩文章, 请关注我的个人公众号:老宣与你聊Java
让我们一起共同学习成长!
感谢您的支持!