本篇文章将从InnoDB存储引擎表的逻辑存储及实现开始进行介绍,然后将重点分析表的物理存储特征,即数据在表中是如何组织存放的。简单来说,表就是关于特定实体的数据集合,这也是关系型数据库模型的核心。

1、索引组织表

在InnoDB存储引擎中,表都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表。在InnoDB存储引擎表中,每张表都有个主键,如果在创建表时没有显示地定义主键,则InnoDB存储引擎会按如下方式选择或创建主键。

  • 首先判断表中是否有非空的唯一索引(Unique NOT NULL),如果有,则该列即为主键。
  • 如果不符合上述条件,InnoDB存储引擎自动创建一个6字节大小的指针。

当表中有多个非空唯一索引时,InnoDB存储引擎将选择建表时第一个定义的非空唯一索引为主键。这里需要注意的是,主键选择根据的是定义索引的顺序,而不是建表时列的顺序。

2、InnoDB逻辑存储结构

从InnoDB存储引擎的逻辑存储结构看,所有数据都被逻辑地存放在一个空间中,称之为表空间(tablespace)。表空间又由段(segment)、区(extent)、页(page)组成。页在一些文档中有时也成为块(block),InnoDB存储引擎的逻辑存储结构大致如下图所示:

2.1、表空间

表空间可以看做是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。在默认情况下Inno存储引擎有一个共享表空间ibdata1,即所有数据都存放在这个表空间内,如果用户启动了参数innodb_file_pre_table,则每张表内的数据可以单独放到一个表空间内。

如果启用了innodb_file_pre_table的参数,需要注意的是每张表的表空间存放的只是数据、索引和插入缓冲Bitmap页,其他类的数据,如回滚信息,插入缓冲索引页、系统事务信息,二次写缓冲等还是存放在原来的共享表空间内。

2.2、段

表空间是由各个段组成的,常见的段有数据段、索引段、回滚段等。因为前面已经介绍过了InnoDB存储引擎表时索引组织的,因此数据即索引,索引即数据。那么数据段即为B+树的叶子节点(Left node segment),索引段即为B+树的非叶子节点(Non-leaf node segment),回滚段较为特殊,将会在后面的章节单独介绍。

2.3、区

区是由连续页组成的空间,在任何情况下每个区的大小都为1MB。为了保证区中页的连续性,InnoDB存储引擎一次从磁盘申请4~5个区。在默认情况下,InnoDB存储引擎页的大小为16KB,即一个区中一共有64个连续的页。

2.4、页

同大多数数据库一样,InnoDB有页的概念(也可以称之为块),页时InnoDB磁盘管理的最小单位。在InnoDB存储引擎中,默认每个页的大小为16KB。在InnoDB存储引擎中,常见的页类型有:

  • 数据页
  • undo页
  • 系统页
  • 事务数据页
  • 插入缓冲位图页
  • 插入缓冲空闲列表页
  • 未压缩的二进制大对象页
  • 压缩的二进制大对象页

2.5、行

InnoDB存储引擎是面向行的(row-oriented),也就是说数据时按行进行存放的。每个页存放的行记录也是有硬性定义的,最多允许存放16KB/2-200行的记录,即7992行记录。

3、InnoDB行记录格式

InnoDB存储引擎和大多数数据库一样,记录是以行的形式存储的。这意味着页中保存着表中一行行的数据。

数据库实例的作用之一就是读取页中存放的行记录。如果用户自己知道页中行记录的组织规则,也可以自行通过编写工具的方式来读取其中的记录,接下来将具体分析各格式存放数据的规则。

3.1、Compact行记录格式

Compact行记录是在MySQL5.0中引入的,其设计目标是高校存储数据,简单来说,一个页中存放的行数据越多,其性能就越高。如下图所示:



从图中可以观察到,Compact行记录格式的首部是一个非NULL边长字段长度列表,并且其是按照列的顺序逆序放置的,其长度为:

  • 若列的长度小于255字节,用1字节表示;
  • 若长度大于255字节,用2字节表示。

变长字段的长度最大不可以超过2字节,这是因为在MySQL数据库中VARCHAR类型的最大长度限制为65535。变长字段之后的第二个部分是NULL标志位,该位指示了该行数据中是否有NULL值,有则用1表示。该部分所占的字节应该为1字节。接下来的部分是记录头信息,固定占用5字节(40位),每位的含义见下表。



最后的部分实际是存储每个列的数据。需要特别注意的是,NULL不占用该部分任何空间,即NULL除了占有NULL标志位,实际存储不占有任何空间。另外有一点需要注意的是每行除了用户定义的列外,还有两个隐藏列,事务ID列和回滚指针列,分别为6字节和7字节的大小。若InnoDB表没有定义主键,每行还会增加一个6字节的rowid列。

3.2、Redundant

Redundant是MySQL5.0版本之前InnoDB的行记录存储方式,MySQL5.0支持Redundant是为了兼容之前版本的页格式Redundant行记录采用如图4-3所示的方法存储。



从图中可以看到,不同于Compact行记录格式,Redundant行记录格式的首部是一个字段长度偏移列表,同样是按照列的顺序逆序放置的。若列的长度小于255字节,用1字节表示;若大于255字节,用2字节表示。第二个部分分为记录头信息,不同于Compact行记录格式,Redundant行记录格式的记录头占用6字节,每位的含义见下表。从表中可以发现,n_fields值代表一行中列的数量,占用10位。同时这也很好地解释了为什么MySQL数据库一行支持最多的列为1023。另一个需要注意的值为1byte_offs_flags,该值定义了偏移列表占用1字节还是2字节。而最后的部分就是实际存储的每个列的数据了。

3.3、行溢出数据

InnoDB存储引擎可以将一条记录中的某些数据存储在真正的数据页面之外。一般认为BLOB、LOB这类的大对象列类型的存储会把数据存放在数据页面之外。但是,这个理解有点偏差,BLOB可以不将数据放在溢出页面,而且即便是VARCHAR列数据类型,依然有可能存放行溢出数据。

3.4、Compressed和Dynamic行记录格式

InnoDB 1.0.x版本开始引入了新的文件格式,以前支持的Compact和Redundant格式称为Antelope文件格式,新的文件格式称为Barracuda文件格式。Barracuda文件格式下拥有两种新的行记录格式:Compressed和Dynamic。

新的两种记录格式对于存放在BLOB中的数据采用了完全行溢出的方式,如下图所示,在数据页中只存放20个指针,实际的数据都存放在Off Page中,而之前的Compact和Redundant两种格式会存放768个前缀字节。



Compressed行记录格式的两一个功能就是,记录在其中的行数据会以zlib的算法进行压缩,因此对于BLOB、TEXT、VARCHAR这类大类型的数据能够进行非常有效的存储。

3.5、CHAR的行结构存储

通常理解VARCHAR是存储变长长度的字符类型,CHAR是存储固定长度的字符类型。根据前面所讨论的行结构的内部存储,可以发现每行的变长字段长度的列表都没有存储CHAR类型的长度。

然而,值得注意的是之前给出的两个例子中的字符集都是单字节的latin1格式。从MySQL 4.1版本开始,CHAR(N)中的N指的是字符的长度,而不是之前版本的字节的长度。也就是说在不同的字符集下,CHAR类型列内部存储的可能不是定长的数据。

CHAR类型被明确视为变长字符类型,对于未能占满长度的字符还是填充0x20。在多字符情况下,CHAR和VARCHAR的实际行存储基本是没有区别的。

4、InnoDB数据页结构

页是InnoDB存储引擎管理数据库的最小磁盘单位。页类型为B-tree Node的页存放的即是表中行的实际数据了。接下来,我们将从底层具体的探究InnoDB数据页的内部存储结构。

InnoDB数据页由一下7个部分组成:

  • File Header(文件头)
  • Page Header(页头)
  • Infimum和Supremum Records
  • user Records(用户记录,即行记录)
  • Free Space(空闲空间)
  • Page Directory(页目录)
  • File Trailer(文件结尾信息)

其中File Header、Page Header、File Trailer的大小是固定的,分别为38、56、8字节,这些空间用来标记该页的一些信息,如Checksum,数据页所在的B+树索引的层数等。User Records、Free Space、Page Directory 这些部分为实际的行记录存储空间,因此大小是动态的。

4.1、File Header

File Header用来记录页的一些头信息。



4.2、Page Header

接着File Header的部分是Page Header,该部分用来记录数据页的状态信息,由14个部分组成,共占用56字节。

4.3、Infimum和Supermum Record

在InnoDB存储引擎中,每个数据页中有两个虚拟的行记录,用来限定记录的边界。Infimum记录是比该页中任何主键值都要小的值,Supremum指比任何可能大的值还要大的值。这两个值在页创建时被建立,并且在任何情况下不会被删除。在Compact行格式和Redundant行格式下,两者占用的字节数各不相同。

4.4、User Record和Free Space

User Record就是之前讨论过的部分,即实际存储行记录的内容。再次强调,InnoDB存储引擎表总是B+树索引组织的。

Free Space很明显指的就是空闲空间,同样也是个链表数据结构。在一条记录被删除后,该空间会被加入到空闲链表中。

4.5、Page Directory

Page Directory(页目录)中存放了记录的相对位置(注意:这里存放的是页相对位置而不是偏移量),有些时候这些记录指针称为Slots(槽)或目录槽(Directory Slots)。

B+树索引本身并不能找到具体的一条记录,能找到的只是该记录所在的页。数据库把页载入到内存,然后通过Page Directory再进行二叉查找。只不过二叉查找的时间复杂度很低,同时在内存中的查找很快,因此通常忽略了这部分查找所用的时间。

4.6、File Trailer

为了检测页是否已经完整的写入磁盘(如可能发生写入过程中磁盘损坏、机器关机等),InnoDB存储引擎的页中设置了File Trailer部分。

在默认配置下,InnoDB存储引擎每次从磁盘读取一个页就会检测该页的完整性,即页是否发生Corrupt,这就是通过File Trailer部分进行检测,而该部分的检测会有一定的开销,用户可以通过参数innodb_checksum来开启或关闭对这个页完整性的检查。

5、Nameed File Formats机制

随着InnoDB存储引擎的发展,新的页数据结构有时用来支持新的功能特性。比如前面提到的InnoDB1.0.x版本提供了新的页数据结构来支持表压缩功能,完全的溢出(Off page)大变长字符类型字段的存储。这些新的页数据结构和之前版本的页并不兼容,因此从InnoDB1.0.x版本开始,InnoDB存储引擎通过Named File Formats机制来解决不同版本下页的兼容问题。

InnoDB存储引擎将1.0.x版本之前的文件格式定义为Antelope,将这个版本支持的文件格式定义为Barracuda。新的文件格式总是包含于之前的版本的页格式。下图显示了barracuda文件格式和Antelope文件格式之间的关系,Antelope文件格式有Compact和Redudant的行格式,Barracude文件格式既包括了Antelope所有的文件格式,另外新加入了之前已经提到过的Compressed和Dynamic行格式。

6、约束

6.1、数据完整性

关系型数据库系统和文件系统的一个不同点是,关系型数据库本身保证保存数据的完整性,不需要应用程序的控制,而文件系统一般需要在程序端进行控制。当前几乎所有的关系型数据库都提供了约束(constraint)机制,该机制提供了一条强大而简易的途径来保证数据库中数据的完整性。一般来说,数据完整性有以下三种形式:

  1. 实体完整性保证表中有一个主键。在InnoDB存储引擎表中,用户可以通过定义Primary Key或Unique Key约束来保证实体的完整性。用户还可以通过编写一个触发器来保证数据完整性。
  2. 域完整性保证数据每列的值满足特定的条件。在InnoDB存储引擎表中,域完整性可以通过以下几种途径来保证:
    • 选择合适的数据类型确保一个数据值满足特定条件。
    • 外键(foregin Key)约束。
    • 编写触发器。
    • 还可以考虑用DEFAULT约束作为强制域完整性的一个方面。
  3. 参照完整性保证两张表之间的关系。InnoDB存储引擎支持外键,因此允许用户定义外键以强制参照完整性,也可以通过编写触发器以强制执行。

对于InnoDB本身而言,提供了一下几种约束:

  • Primary Key
  • Unique Key
  • Foregin Key
  • Default
  • NOT NULL

6.2、约束的创建和查找

约束的创建可以采用以下两种方式:

  • 表建立时就进行约束定义
  • 利用ALTER TABLE命令来进行创建约束

对Unique Key(唯一索引)的约束,用户还可以通过命令CREATE UNIQUE INDEX来建立。对于主键约束而言,其默认约束名为PRIMARY。而对于Unique Key约束而言,默认约束名和列名一样,当然也可以人为指定Unique Key约束的名字。Forgein Ket约束似乎会有一个比较神秘的默认名称。

6.3、约束和索引的区别

前面看到Primary Key和Unique Key的约束,有人会问:这不就是通常创建索引的方法吗?那约束和索引有什么区别呢?

的确,当用户创建了一个唯一索引就创建了一个唯一的约束。但是约束和索引的概念还是有所不同的,约束更是一个逻辑的概念,用来保证数据的完整性,而索引是一个数据结构,既有逻辑上的概念,在数据库中还代表着物理存储的方式。

6.4、对错误数据的约束

在某些默认设置下,MySQL数据库允许非法的或不正确的数据的插入或更新,又或者可以在数据库内部将其转化为一个合法的值,如向NOT NULL的字段插入一个NULL值,MySQL数据库会将其更改为0再进行插入,因此数据库本身没有对数据的正确性进行约束。

6.5、ENUM和SET约束

MySQL数据库不支持传统的CHECK约束,但是通过ENUM和SET类型可以解决部分这样的约束需求。例如表上有一个性别类型,规定域的范围只能是male或female,在这种情况下用户可以通过ENUM类型来进行约束。

6.6、触发器与约束

触发器的作用是在执行INSERT、DELETE和UPDATE命令之前或之后自动调用SQL命令或存储过程。

6.7、外键约束

外键用来保证参照完整性,MySQL数据库的MyISAM存储引擎本身并不支持外键,对于外键的定义只是起到一个注释的作用。而InnoDB存储引擎则完整支持外键约束。

7、视图

在MySQL数据库中,视图是一个命名的虚表,它有一个SQL查询来定义,可以当做表来使用。与持久表不同的是,视图中的数据没有实际的物理存储。

7.1、视图的作用

视图在数据库中发挥着重要的作用。视图的主要用途之一是被用做一个抽象装置,特别是对于一些应用程序,程序本身不需要关心基表的结构,只需要按照视图定义来取数据或更新数据,因此,视图同时在一定程度上起到一个安全层的作用。

7.2、物化视图

Oracel数据库支持物化视图——该视图不是基于基表的虚表,而是根据基表实际存在的实表,即物化视图的数据存储在非易失的设备上。物化视图可以用于预先计算并保存多表的链接(JOIN)或聚集(GROUP BY)等耗时较多的SQL操作结果。这样,在执行复杂查询时,就可以避免进行这些耗时的操作,从而快速得到结果。物化视图的好处是对于一些复杂的统计类查询能直接查询出结果。在Microsoft SQL Server数据库中,成这种视图为索引视图。

8、分区表

8.1、分区概述

分区功能并不是在存储引擎层完成的,因此不是只有InnoDB存储引擎支持分区,常见的存储引擎MyISAM、DBA等都支持。但也并不是所有的存储引擎都支持,如CSV、FEDORATED、MERGE等就不支持。在使用分区功能前,应该对选择的存储引擎对分区的支持就有所了解。

分区的过程是将一个表或索引分解为多个更小、更可管理的的部分。就访问数据库的应用而言,从逻辑上讲,只有一个表或一个索引,但是在物理上这个表或索引可能由数十个物理分区组成。每个分区都是独立的对象,可以独自处理,也可以作为一个更大的对象的一部分进行处理。

MySQL支持的分区类型为水平分区,并不支持垂直分区。此外,MySQL数据库的分区是局部分区索引,一个分区中及存放了数据又存放了索引。而全局分区是指,数据存放在各个分区中,但是所有的数据的索引放在一个对象中。目前,MySQL数据库还不支持全局分区。

8.2、分区类型

1、RANGE分区

RANGE分区主要用于日期列的分区,例如对于销售类的表,可以根据年来分区存放销售记录。

2、LIST分区

LIST分区和RANGE分区非常相似,只是分区列的值是离散的,而非连续的。

3、HASH分区

HASHd的目的是将数据均匀地分布到预先定义的各个分区中,保证各分区的数据量大致都是一样的。在RANGE和LIST分区中,必须明确指定一个给定的列值或列值集合应该保存在哪个分区中;而在HASH分区中,MySQL自动完成这些工作,用户所要做的只是基于将要进行哈希分区的列值指定一个列值或表达式,以及指定被分区的表将要被分割成的分区数量。

4、KEY分区

KEY分区和HASH分区相似,不同之处在于HASH分区使用户定义的函数进行分区,KEY分区使用MySQL数据库提供的函数进行分区。对于NDB Cluster引擎,MySQL数据库使用MD5函数来分区;对于其他存储引擎,MySQL数据库使用其内部的哈希函数,这些函数基于与PASSWORD()一样的运算法则。

5、COLUMNS分区

COLUMNS分区可视为RANGE分区和LIST分区的一种进化。COLUMNS分区可以直接使用费整型的数据进行分区,分区根据分区类型直接比较而得,不需要转化为整型。

8.3、子分区

子分区是在分区的基础上再进行分区,有时也称为这种分区为复合分区。MySQL数据库允许在RANGE和LIST的分区上再进行HASH或KEY分区。

8.4、分区中的NULL值

MySQL数据库允许对NULL值做分区,但是处理的方法与其他数据库可能完全不同。MYSQL数据库的分区总是视NULL值视小于任何的一个非NULL值,折合MySQL数据库中处理NULL值的ORDER BY操作是一样的。因此对于不同的分区类型,MySQL数据库对于NULL值的处理也是各不相同。

8.5、分区和性能

数据库的应用分为两类:一类是OLTP(在线事务处理),如Blog、电子商务、网络游戏等;另一类是OLAP(在线分析处理),如数据仓库、数据集市。在一个实际的应用环境中,可能既是OLTP的应用,也有OLAP的应用。如网络游戏中,玩家操作的游戏数据库应用就是OLTP的,但是游戏厂商可能需要对游戏产生的日志进行分析,通过分析得到的记过来更好地服务于游戏,预测玩家的行为等,而这是OLAP的应用。

对于OLAP的应用,分区的确是可以很好地提高查询的性能,因为OLAP应用大多数查询需要频繁地扫描一张很大的表。

然而对于OLTP的应用,分区就应该小心了。在这种应用下,通常不可能会获取一张大表10%的数据,大部分都是通过索引返回几条记录即可。而根据B+树可以很好地完成操作,不需要分区帮助,并且设计不好的分区会带来严重的性能问题。

【MySQL】(四)表的更多相关文章

  1. mysql 四 表操作

    表介绍 表相当于文件,表中的一条记录就相当于文件的一行内容,不同的是,表中的一条记录有对应的标题,称为表的字段 id,name,qq,age称为字段,其余的,一行内容称为一条记录 本节重点: 1 创建 ...

  2. MySQL四:表操作

    阅读目录 表介绍 一 创建表 二 查看表结构 三 数据类型 四 表完整性约束 五 修改表ALTER TABLE 六 复制表 七 删除表 八 完整性约束 九 数据类型 表介绍 表相当于文件,表中的一条记 ...

  3. 在MySQL向表中插入中文时,出现:incorrect string value 错误

    在MySQL向表中插入中文时,出现:incorrect string value 错误,是由于字符集不支持中文.解决办法是将字符集改为GBK,或UTF-8.      一.修改数据库的默认字符集   ...

  4. MySQL建表规范与常见问题

    一. 表设计 库名.表名.字段名必须使用小写字母,“_”分割. 库名.表名.字段名必须不超过12个字符. 库名.表名.字段名见名知意,建议使用名词而不是动词. 建议使用InnoDB存储引擎. 存储精确 ...

  5. 用Myisamchk让MySQL数据表更健康

    用Myisamchk让MySQL数据表更健康 2011-03-15 09:15 水太深 ITPUB 字号:T | T 为了让MySQL数据库中的数据表“更健康”,就需要对其进行定期体检.在这里笔者推荐 ...

  6. MySQL的表分区详解

    这篇文章主要介绍了MySQL的表分区,例如什么是表分区.为什么要对表进行分区.表分区的4种类型详解等,需要的朋友可以参考下 一.什么是表分区通俗地讲表分区是将一大表,根据条件分割成若干个小表.mysq ...

  7. 详解MySQL大表优化方案( 转)

    当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化: 单表优化 除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑.部署.运维的各种复杂度,一般以整型 ...

  8. MySQL 大表优化方案探讨

    当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化: 单表优化 除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑.部署.运维的各种复杂度,一般以整型 ...

  9. 修改MYSQL数据库表的字符集

    MySQL 乱码的根源是的 MySQL 字符集设置不当的问题,本文汇总了有关查看 MySQL 字符集的命令.包括查看 MySQL 数据库服务器字符集.查看 MySQL 数据库字符集,以及数据表和字段的 ...

  10. 设置MySQL数据表主键

    设置MySQL数据表主键: 使用“primary key”关键字创建主键数据列.被设置为主键列不允许出现重复的值,很多情况下与“auto_increment”递增数字相结合.如下SQL语句所示: My ...

随机推荐

  1. windows下进程间通信的(13种方法)

    转自:http://blog.csdn.NET/shiqz/article/details/5862936 摘 要 随着人们对应用程序的要求越来越高,单进程应用在许多场合已不能满足人们的要求.编写多进 ...

  2. DI 容器实务建议

    整理一些有关使用 DI 容器的一些建议事项,主要的参考数据源是 Jimmy Board 的文章:Container Usage Guidelines. 1.容器设定 避免对同一个组件(DLL)重复扫描 ...

  3. 从零开始人工智能AI(一)-k-nn-矩阵

    参考资料: https://baike.baidu.com/item/%E7%9F%A9%E9%98%B5/18069?fr=aladdin http://blog.csdn.net/c4064957 ...

  4. 一线Python运维开发带你秒懂Flask框架

    相信曾经纠结过这个问题:怎样才能彻底掌握flask? Flask是一个使用 Python 编写的轻量级 Web 应用框架.其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 . ...

  5. Spring Boot2(四):使用Spring Boot多数据源实现读写分离

    前言 实际业务场景中,不可能只有一个库,所以就有了分库分表,多数据源的出现.实现了读写分离,主库负责增改删,从库负责查询.这篇文章将实现Spring Boot如何实现多数据源,动态数据源切换,读写分离 ...

  6. NET中级开发工程师职责要求

    NET中级开发工程师岗位职责1.熟练掌握 WebApi, ASP.NET MVC, Entity Framework,.NET CORE等技术,精通JQuery.Vue.Bootstrap等前端开发框 ...

  7. PATA 1009. Product of Polynomials (25)

    1009. Product of Polynomials (25) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yu ...

  8. 使用docker启动mysql

    安装docker 查询mysql版本, docker search mysql 拉取指定版本的镜像 docker pull NAME 启动镜像, 第一次启动最少需要指定MYSQL_ROOT_PASSW ...

  9. 通用shell函数库

    1.输出字体颜色库 #!/bin/bash export black='\E[0m\c' export boldred='\E[1;31m\c' export boldgreen='\E[1;32m\ ...

  10. kafka迁移topic

    1. 准备移动 这里假设要移动的topic名称为:topicA.topicB vim topics-to-move.json {"topics": [{"topic&qu ...