转自:https://draveness.me/mysql-in...html
做为一名开发人员,在平常的工做中会难以免地接触到数据库,不管是基于文件的 sqlite 仍是工程上使用很是普遍的 MySQL、PostgreSQL,可是一直以来也没有对数据库有一个很是清晰而且成体系的认知,因此最近两个月的时间看了几本数据库相关的书籍而且阅读了 MySQL 的官方文档,但愿对各位了解数据库的、不了解数据库的有所帮助。node
本文中对于数据库的介绍以及研究都是在 MySQL 上进行的,若是涉及到了其余数据库的内容或者实现会在文中单独指出。mysql
不少开发者在最开始时其实都对数据库有一个比较模糊的认识,以为数据库就是一堆数据的集合,可是实际却比这复杂的多,数据库领域中有两个词很是容易混淆,也就是_数据库_和_实例_:程序员
对于数据库和实例的定义都来自于 MySQL 技术内幕:InnoDB 存储引擎 一书,想要了解 InnoDB 存储引擎的读者能够阅读这本书籍。
在 MySQL 中,实例和数据库每每都是一一对应的,而咱们也没法直接操做数据库,而是要经过数据库实例来操做数据库文件,能够理解为数据库实例是数据库为上层提供的一个专门用于操做的接口。面试
在 Unix 上,启动一个 MySQL 实例每每会产生两个进程,mysqld
就是真正的数据库服务守护进程,而 mysqld_safe
是一个用于检查和设置 mysqld
启动的控制程序,它负责监控 MySQL 进程的执行,当 mysqld
发生错误时,mysqld_safe
会对其状态进行检查并在合适的条件下重启。算法
MySQL 从第一个版本发布到如今已经有了 20 多年的历史,在这么多年的发展和演变中,整个应用的体系结构变得愈来愈复杂:sql
最上层用于链接、线程处理的部分并非 MySQL 『发明』的,不少服务都有相似的组成部分;第二层中包含了大多数 MySQL 的核心服务,包括了对 SQL 的解析、分析、优化和缓存等功能,存储过程、触发器和视图都是在这里实现的;而第三层就是 MySQL 中真正负责数据的存储和提取的存储引擎,例如:InnoDB、MyISAM 等,文中对存储引擎的介绍都是对 InnoDB 实现的分析。数据库
在整个数据库体系结构中,咱们可使用不一样的存储引擎来存储数据,而绝大多数存储引擎都以二进制的形式存储数据;这一节会介绍 InnoDB 中对数据是如何存储的。后端
在 InnoDB 存储引擎中,全部的数据都被逻辑地存放在表空间中,表空间(tablespace)是存储引擎中最高的存储逻辑单位,在表空间的下面又包括段(segment)、区(extent)、页(page):缓存
同一个数据库实例的全部表空间都有相同的页大小;默认状况下,表空间中的页大小都为 16KB,固然也能够经过改变 innodb_page_size
选项对默认大小进行修改,须要注意的是不一样的页大小最终也会致使区大小的不一样:
从图中能够看出,在 InnoDB 存储引擎中,一个区的大小最小为 1MB,页的数量最少为 64 个。
MySQL 使用 InnoDB 存储表时,会将表的定义和数据索引等信息分开存储,其中前者存储在 .frm
文件中,后者存储在 .ibd
文件中,这一节就会对这两种不一样的文件分别进行介绍。
不管在 MySQL 中选择了哪一个存储引擎,全部的 MySQL 表都会在硬盘上建立一个 .frm
文件用来描述表的格式或者说定义;.frm
文件的格式在不一样的平台上都是相同的。
<pre>CREATE TABLE test_frm( column1 CHAR(5), column2 INTEGER);
</pre>
当咱们使用上面的代码建立表时,会在磁盘上的 datadir
文件夹中生成一个 test_frm.frm
的文件,这个文件中就包含了表结构相关的信息:
MySQL 官方文档中的
11.1 MySQL .frm File Format 一文对于
.frm
文件格式中的二进制的内容有着很是详细的表述,在这里就不展开介绍了。
InnoDB 中用于存储数据的文件总共有两个部分,一是系统表空间文件,包括 ibdata1
、ibdata2
等文件,其中存储了 InnoDB 系统信息和用户数据库表数据和索引,是全部表公用的。
当打开 innodb_file_per_table
选项时,.ibd
文件就是每个表独有的表空间,文件存储了当前表的数据和相关的索引数据。
与现有的大多数存储引擎同样,InnoDB 使用页做为磁盘管理的最小单位;数据在 InnoDB 存储引擎中都是按行存储的,每一个 16KB 大小的页中能够存放 2-200 行的记录。
当 InnoDB 存储数据时,它可使用不一样的行格式进行存储;MySQL 5.7 版本支持如下格式的行存储方式:
Antelope 是 InnoDB 最开始支持的文件格式,它包含两种行格式 Compact 和 Redundant,它最开始并无名字;Antelope 的名字是在新的文件格式 Barracuda 出现后才起的,Barracuda 的出现引入了两种新的行格式 Compressed 和 Dynamic;InnoDB 对于文件格式都会向前兼容,而官方文档中也对以后会出现的新文件格式预先定义好了名字:Cheetah、Dragon、Elk 等等。
两种行记录格式 Compact 和 Redundant 在磁盘上按照如下方式存储:
Compact 和 Redundant 格式最大的不一样就是记录格式的第一个部分;在 Compact 中,行记录的第一部分倒序存放了一行数据中列的长度(Length),而 Redundant 中存的是每一列的偏移量(Offset),从整体上上看,Compact 行记录格式相比 Redundant 格式可以减小 20% 的存储空间。
当 InnoDB 使用 Compact 或者 Redundant 格式存储极长的 VARCHAR 或者 BLOB 这类大对象时,咱们并不会直接将全部的内容都存放在数据页节点中,而是将行数据中的前 768 个字节存储在数据页中,后面会经过偏移量指向溢出页。
可是当咱们使用新的行记录格式 Compressed 或者 Dynamic 时都只会在行记录中保存 20 个字节的指针,实际的数据都会存放在溢出页面中。
固然在实际存储中,可能会对不一样长度的 TEXT 和 BLOB 列进行优化,不过这就不是本文关注的重点了。
想要了解更多与 InnoDB 存储引擎中记录的数据格式的相关信息,能够阅读 InnoDB Record Structure
页是 InnoDB 存储引擎管理数据的最小磁盘单位,而 B-Tree 节点就是实际存放表中数据的页面,咱们在这里将要介绍页是如何组织和存储记录的;首先,一个 InnoDB 页有如下七个部分:
每个页中包含了两对 header/trailer:内部的 Page Header/Page Directory 关心的是页的状态信息,而 Fil Header/Fil Trailer 关心的是记录页的头信息。
在页的头部和尾部之间就是用户记录和空闲空间了,每个数据页中都包含 Infimum 和 Supremum 这两个虚拟的记录(能够理解为占位符),Infimum 记录是比该页中任何主键值都要小的值,Supremum 是该页中的最大值:
User Records 就是整个页面中真正用于存放行记录的部分,而 Free Space 就是空余空间了,它是一个链表的数据结构,为了保证插入和删除的效率,整个页面并不会按照主键顺序对全部记录进行排序,它会自动从左侧向右寻找空白节点进行插入,行记录在物理存储上并非按照顺序的,它们之间的顺序是由 next_record
这一指针控制的。
B+ 树在查找对应的记录时,并不会直接从树中找出对应的行记录,它只能获取记录所在的页,将整个页加载到内存中,再经过 Page Directory 中存储的稀疏索引和 n_owned
、next_record
属性取出对应的记录,不过由于这一操做是在内存中进行的,因此一般会忽略这部分查找的耗时。
InnoDB 存储引擎中对数据的存储是一个很是复杂的话题,这一节中也只是对表、行记录以及页面的存储进行必定的分析和介绍,虽然做者相信这部分知识对于大部分开发者已经足够了,可是想要真正消化这部份内容还须要不少的努力和实践。下文是详细分析。
本文主要介绍InnoDB
存储引擎的逻辑存储结构
最高层
,全部数据
都存放在Tablespace中分类
System Tablespace
Separate Tablespace
General Tablespace
System Tablespace
即咱们常见的共享表空间
,变量为innodb_data_file_path
,通常为ibdata1
文件undo logs
,change buffer
,doublewrite buffer
等信息(后续将详细介绍),在没有开启file-per-table
的状况下,还会包含全部表的索引和数据
信息没有开启file-per-table
时存在的问题
System Tablespace
中,占用空间会愈来愈大
碎片愈来愈多
(如truncate table
时,占用的磁盘空间依旧保留在System Tablespace
)<pre>12345678910111213141516171819</pre> | <pre>mysql> SHOW VARIABLES LIKE 'innodb_data_file_path';+-----------------------+------------------------+ | Variable_name | Value | +-----------------------+------------------------+ | innodb_data_file_path | ibdata1:12M:autoextend | +-----------------------+------------------------+1 row in set (0.01 sec)mysql> SHOW VARIABLES LIKE '%datadir%';+---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | datadir | /var/lib/mysql/ | +---------------+-----------------+1 row in set (0.01 sec)mysql> system sudo ls -lh /var/lib/mysql/ibdata1[sudo] password for zhongmingmao:-rw-r----- 1 mysql mysql 76M May 6 20:00 /var/lib/mysql/ibdata1</pre> |
---|
Separate Tablespace
这个术语,这里只为了行文方便,表示在开启file-per-table
的状况下,每一个表有本身独立的表空间
,变量为innodb_file_per_table
每一个表的索引和数据信息
,后缀通常为.ibd
96KB
好处
System Tablespace
愈来愈大truncate table
,操做系统会自动回收空间
)<pre>123456789101112131415161718192021222324252627282930313233</pre> | <pre>mysql> use testReading table information for completion of table and column namesYou can turn off this feature to get a quicker startup with -ADatabase changedmysql> show tables;+----------------+ | Tables_in_test | +----------------+ | t | +----------------+1 row in set (0.00 sec)mysql> SHOW VARIABLES LIKE 'innodb_file_per_table';+-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | innodb_file_per_table | ON | +-----------------------+-------+1 row in set (0.00 sec)mysql> SHOW VARIABLES LIKE '%datadir%'; +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | datadir | /var/lib/mysql/ | +---------------+-----------------+1 row in set (0.01 sec)mysql> system sudo ls -lh /var/lib/mysql/testtotal 112K-rw-r----- 1 mysql mysql 61 Apr 28 10:18 db.opt-rw-r----- 1 mysql mysql 8.4K May 7 17:03 t.frm-rw-r----- 1 mysql mysql 96K May 7 17:03 t.ibd</pre> |
---|
General Tablespace
是MySQL 5.7.6
引入的新特性,具体内容请参照下面连接Segment分为三种
Leaf node segment
:数据段
,B+Tree的叶子节点Non-Leaf node segment
:索引段
,B+Tree的非叶子节点Rollback segment
:回滚段,存放undo log
,默认是位于System Tablespace
B+Tree索引
,由Leaf node segment
和Non-Leaf node segment
组成多个Extent和Page
组成Extent
是由连续页(默认页大小为16KB
)组成,在默认页大小
时,为64个连续页
,大小为64*16KB=1MB
4KB*256
or 8KB*128
or 16KB*64
or 32KB*64
or 64KB*64
页的连续性
,InnoDB能够一次性从磁盘申请4个Extent
节省磁盘空间
,如表的数据量很小(Leaf node segment
和Non-Leaf node segment
都很小)或Rollback segment
,Segment一开始不会直接申请Extent
,而是先用32个碎片页
(用于叶子节点
)来存放数据,用完以后才继续对Extent(1MB)
的申请Page
是InnoDB磁盘管理的最小单位
,变量为innodb_page_size
<pre>1234567</pre> | <pre>mysql> SHOW VARIABLES LIKE 'innodb_page_size';+------------------+-------+ | Variable_name | Value | +------------------+-------+ | innodb_page_size | 16384 | +------------------+-------+1 row in set (0.17 sec)</pre> |
---|
按行
进行存放的Row_FORMAT
将在后续详细介绍接下来是Page数据页详解,这是最重要的一部分。
本文主要介绍InnoDB
存储引擎的数据页结构
参考连接:Fil Header
38 Bytes
,记录页的头信息
名称 | 大小(Bytes) | 描述 |
---|---|---|
FIL_PAGE_SPACE | 4 | 该页的checksum 值 |
FIL_PAGE_OFFSET | 4 | 该页在表空间中的页偏移量 |
FIL_PAGE_PREV | 4 | 该页的上一个页 |
FIL_PAGE_NEXT | 4 | 该页的下一个页 |
FIL_PAGE_LSN | 8 | 该页最后被修改的LSN |
FIL_PAGE_TYPE | 2 | 该页的类型,0x45BF为数据页 |
FIL_PAGE_FILE_FLUSH_LSN | 8 | 独立表空间中为0 |
FIL_PAGE_ARCH_LOG_NO | 4 | 该页属于哪个表空间 |
参考连接:Page Header
56 Bytes
,记录页的状态信息
名称 | 大小(Bytes) | 描述 |
---|---|---|
PAGE_N_DIR_SLOTS | 2 | 在Page Directory 中Slot 的数量,初始值为2 |
PAGE_HEAP_TOP | 2 | 堆中第一个记录的指针 |
PAGE_N_HEAP | 2 | 堆中的记录数,初始值为2 |
PAGE_FREE | 2 | 指向可重用空间 的首指针 |
PAGE_GARBAGE | 2 | 已标记为删除(deleted_flag )的记录的字节数 |
PAGE_LAST_INSERT | 2 | 最后插入记录的位置 |
PAGE_DIRECTION | 2 | 最后插入的方向,PAGE_LEFT(0x01) ,PAGE_RIGHT(0x02) ,PAGE_NO_DIRECTION(0x05) |
PAGE_N_DIRECTION | 2 | 一个方向上连续插入记录的数量 |
PAGE_N_RECS | 2 | 该页中记录(User Record )的数量 |
PAGE_MAX_TRX_ID | 8 | 修改该页的最大事务ID(仅在辅助索引 中定义) |
PAGE_LEVEL | 2 | 该页在索引树中位置,0000表明叶子节点 |
PAGE_INDEX_ID | 8 | 索引ID,表示该页属于哪一个索引 |
PAGE_BTR_SEG_LEAF | 10 | B+Tree叶子节点所在Leaf Node Segment 的Segment Header(可有可无) |
PAGE_BTR_SEG_TOP | 10 | B+Tree非叶子节点所在Non-Leaf Node Segment 的Segment Header(可有可无) |
参考连接:The Infimum and Supremum Records
虚拟的行记录
,用来限定记录(User Record
)的边界(Infimum为下界
,Supremum为上界
)Infimum
和Supremum
在页被建立
是自动建立,不会被删除
Compact
和Redundant
行记录格式下,Infimum
和Supremum
占用的字节数是不同
的参考连接:User Records
实际插入的行记录
在Page Header
中PAGE_HEAP_TOP
、PAGE_N_HEAP
的HEAP
,实际上指的是Unordered User Record List
依据B+Tree键的顺序
来插入新行
,由于这可能须要移动大量的数据
Free Space的顶部
)或者是已删除行留下来的空间
顺序性
,在每一个记录中都有一个指向下一条记录的指针
,以此构成了一条单向有序链表
链表
,在一个记录被删除
后,该空间会被加入到空闲链表中参考连接:Page Directory
行记录
(User Record
)的相对位置
(不是偏移量)行记录指针称
为Slot
或Directory Slot
,每一个Slot
占用2Byte
并非每个行记录都有一个Slot
,一个Slot中可能包含多条行记录,经过行记录中n_owned
字段标识Infimum
的n_owned老是1
,Supremum
的n_owned为[1,8]
,User Record
的n_owned为[4,8]
Slot
是按照索引键值的顺序
进行逆序
存放(Infimum是下界,Supremum是上界
),能够利用二分查找
快速地定位一个粗略的结果
,而后再经过next_record
进行精确查找
B+Tree索引
自己并不能直接找到具体的一行记录
,只能找到该行记录所在的页
内存
中,而后经过Page Directory
再进行二分查找
参考连接:Fil Trailer
8 Bytes
,为了检测页是否已经完整地写入磁盘
innodb_checksums
,InnoDB从磁盘读取一个页
时是否会检测页的完整性
innodb_checksum_algorithm
,检验和算法
微信公众号【黄小斜】做者是蚂蚁金服 JAVA 工程师,专一于 JAVA 后端技术栈:SpringBoot、SSM全家桶、MySQL、分布式、中间件、微服务,同时也懂点投资理财,坚持学习和写做,相信终身学习的力量!关注公众号后回复”架构师“便可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送做者原创的Java学习指南、Java程序员面试指南等干货资源