我教我自己:Mysql技术内幕阅读笔记(四)第4章 表

455 阅读13分钟

原文:www.sukidesu.top/index.php/a…

4.1 索引组织表

在InnoDB存储引擎中,表都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(indexorganized table)或者聚集索引。在InnoDB存储引擎表中,每张表都有个主键(Primary Key),如果在创建表时没有显式地定义主键,则InnoDB存储引擎会按如下方式选择或创建主键: 首先判断表中是否有非空的唯一索引(Unique NOT NULL),如果有,则该列即为主键。如果不符合上述条件,InnoDB存储引擎自动创建一个6字节大小的指针。当表中有多个非空唯一索引时,InnoDB存储引擎将选择建表时第一个定义的非空唯一索引为主键。这里需要非常注意的是,主键的选择根据的是定义索引的顺序,而不是建表时列的顺序。

4.2 InnoDB逻辑存储结构

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

4.2.1 表空间

表空间可以看做是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。**如果启用了innodb_file_per_table的参数,需要注意的是每张表的表空间内存放的只是数据、索引和插入缓冲Bitmap页,其他类的数据,如回滚(undo)信息,插入缓冲索引页、系统事务信息,二次写缓冲(Double write buffer)等还是存放在原来的共享表空间ibdata1内。即使在启用了参数innodb_file_per_table之后,共享表空间还是会不断地增加其大小。**同时InnoDB存储引擎不会在执行rollback时去收缩这个表空间。虽然InnoDB不会回收这些空间,但是会自动判断这些undo信息是否还需要,如果不需要,则会将这些空间标记为可用空间,供下次undo使用。

4.2.2 段

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

4.2.3 区

区是由连续页组成的空间,在任何情况下每个区的大小都为1MB。为了保证区中页的连续性,InnoDB存储引擎一次从磁盘申请4~5个区。在默认情况下,InnoDB存储引擎页的大小为16KB,即一个区中一共有64个连续的页。 在用户启用了参数innodb_file_per_talbe后,创建的表默认大小是96KB。区中是64个连续的页,创建的表的大小至少是1MB才对啊?其实这是因为在每个段开始时,先用32个页大小的碎片页(fragment page)来存放数据,在使用完这些页之后才是64个连续页的申请。这样做的目的是,对于一些小表,或者是undo这类的段,可以在开始时申请较少的空间,节省磁盘容量的开销。 [collapse status="false" title="为什么初始只有96KB呢?"] ibd最开始会有三个page,分别是File Space Header 、 Insert Buffer Bitmap 、 File Segment inode [/collapse]

4.2.4 页

同大多数数据库一样,InnoDB有页(Page)的概念(也可以称为块),页是InnoDB磁盘管理的最小单位。在InnoDB存储引擎中,默认每个页的大小为16KB。而从InnoDB 1.2.x版本开始,可以通过参数innodb_page_size将页的大小设置为4K、8K、16K。若设置完成,则所有表中页的大小都为innodb_page_size,不可以对其再次进行修改。除非通过mysqldump导入和导出操作来产生新的库。 在InnoDB存储引擎中,常见的页类型有:□ 数据页(B-tree Node)□ undo页(undo Log Page)□ 系统页(System Page)□ 事务数据页(Transaction system Page)□ 插入缓冲位图页(Insert Buffer Bitmap)□ 插入缓冲空闲列表页(Insert Buffer Free List)□ 未压缩的二进制大对象页(Uncompressed BLOB Page)□ 压缩的二进制大对象页(compressed BLOB Page)

4.2.5 行

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

4.3 InnoDB行记录格式

在InnoDB 1.0.x版本之前,InnoDB存储引擎提供了Compact和Redundant两种格式来存放行记录数据,这也是目前使用最多的一种格式。Redundant格式是为兼容之前版本而保留的。 [scode type="red"]我查看自己的表的存储格式是Dynamic[/scode]

4.3.1 Compact

Compact行记录格式Compact行记录是在MySQL 5.0中引入的,其设计目标是高效地存储数据。简单来说,一个页中存放的行数据越多,其性能就越高。 Compact行记录的格式

  1. Compact行记录格式的首部是一个非NULL变长字段长度列表,并且其是按照列的顺序逆序放置的,其长度为若列的长度小于255字节,用1字节表示;若大于255个字节,用2字节表示。变长字段的长度最大不可以超过2字节,这是因在MySQL数据库中VARCHAR类型的最大长度限制为65535
  2. 第二个部分是NULL标志位,该位指示了该行数据中是否有NULL值,有则用1表示。该部分所占的字节应该为1字节。例如:NULL标志位不再是00而是06,转换成二进制为00000110,为1的值代表第2列和第3列的数据为NULL。
  3. 记录头信息(record header),固定占用5字节(40位) Compact记录头信息
  4. 最后的部分就是实际存储每个列的数据,NULL不占该部分任何空间,即NULL除了占有NULL标志位,实际存储不占有任何空间。每行数据除了用户定义的列外,还有两个隐藏列,事务ID列和回滚指针列,分别为6字节和7字节的大小。若InnoDB表没有定义主键,每行还会增加一个6字节的rowid列。

4.3.2 Redundant行记录格式

Redundant是MySQL 5.0版本之前InnoDB的行记录存储方式,MySQL 5.0支持Redundant是为了兼容之前版本的页格式。 Redundant行记录格式

  1. Redundant行记录格式的首部是一个字段长度偏移列表,同样是按照列的顺序逆序放置的。若列的长度小于255字节,用1字节表示;若大于255字节,用2字节表示。
  2. 第二个部分为记录头信息(record header),不同于Compact行记录格式,Redundant行记录格式的记录头占用6字节(48位)n_fields值代表一行中列的数量,占用10位。同时这也很好地解释了为什么MySQL数据库一行支持最多的列为1023。另一个需要注意的值为1byte_offs_flags,该值定义了偏移列表占用1字节还是2字节。95819-ic3ld5sllli.png
  3. 在Redundant中NULL值会强制补为0

4.3.3 行溢出数据

InnoDB存储引擎可以将一条记录中的某些数据存储在真正的数据页面之外。一般认为BLOB、LOB这类的大对象列类型的存储会把数据存放在数据页面之外。 InnoDB存储引擎并不支持65535长度的VARCHAR。这是因为还有别的开销,通过实际测试发现能存放VARCHAR类型的最大长度为65532。VARCHAR(N)中的N指的是字符的长度。而文档中说明VARCHAR类型最大支持65535,单位是字节。 InnoDB存储引擎的数据都是存放在页类型为B-tree node中。但是当发生行溢出时,数据存放在页类型为Uncompress BLOB页中。而InnoDB的也其实只保存了VARCHAR(65532)的前768字节的前缀(prefix)数据,之后是偏移量,指向行溢出页。 行溢出数据的存储

4.3.4 Compressed和Dynamic行记录格式

新的两种记录格式对于存放在BLOB中的数据采用了完全的行溢出的方式。Compressed行记录格式的另一个功能就是,存储在其中的行数据会以zlib的算法进行压缩,因此对于BLOB、TEXT、VARCHAR这类大长度类型的数据能够进行非常有效的存储。

4.3.5 Char的行结构存储

CHAR类型被明确视为了变长字符类型,对于未能占满长度的字符还是填充0x20。

4.4 InnoDB数据页结构

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

  • File Header用来记录页的一些头信息
  • Page Header,该部分用来记录数据页的状态信息
  • Infimum和Supremum Record,在InnoDB存储引擎中,每个数据页中有两个虚拟的行记录,用来限定记录的边界。Infimum记录是比该页中任何主键值都要小的值,Supremum指比任何可能大的值还要大的值。这两个值在页创建时被建立,并且在任何情况下不会被删除。Infinum和Supremum Record
  • User Record就是之前讨论过的部分,即实际存储行记录的内容。再次强调,InnoDB存储引擎表总是B+树索引组织的。Free Space很明显指的就是空闲空间,同样也是个链表数据结构。在一条记录被删除后,该空间会被加入到空闲链表中。
  • Page DirectoryPage Directory(页目录)中存放了记录的相对位置(注意,这里存放的是页相对位置,而不是偏移量),有些时候这些记录指针称为Slots(槽)或目录槽(Directory Slots)在Slots中记录按照索引键值顺序存放,这样可以利用二叉查找迅速找到记录的指针。由于在InnoDB存储引擎中Page Direcotry是稀疏目录,二叉查找的结果只是一个粗略的结果,因此InnoDB存储引擎必须通过recorder header中的next_record来继续查找相关记录。B+树索引本身并不能找到具体的一条记录,能找到只是该记录所在的页。数据库把页载入到内存,然后通过Page Directory再进行二叉查找。只不过二叉查找的时间复杂度很低,同时在内存中的查找很快,因此通常忽略这部分查找所用的时间。
  • File Trailer,为了检测页是否已经完整地写入磁盘(如可能发生的写入过程中磁盘损坏、机器关机等),InnoDB存储引擎的页中设置了File Trailer部分。在默认配置下,InnoDB存储引擎每次从磁盘读取一个页就会检测该页的完整性,即页是否发生Corrupt,这就是通过File Trailer部分进行检测,而该部分的检测会有一定的开销。[collapse status="false" title="如何检测?"]File Trailer只有一个FIL_PAGE_END_LSN部分,占用8字节。前4字节代表该页的checksum值,最后4字节和File Header中的FIL_PAGE_LSN相同。将这两个值与File Header中的FIL_PAGE_SPACE_OR_CHKSUM和FIL_PAGE_LSN值进行比较,看是否一致(checksum的比较需要通过InnoDB的checksum函数来进行比较,不是简单的等值比较),以此来保证页的完整性(notcorrupted)。MySQL 5.6.6版本开始新增了参数innodb_checksum_algorithm,该参数用来控制检测checksum函数的算法,默认值为crc32[/collapse]

4.5 Named File Formats

随着InnoDB存储引擎的发展,新的页数据结构有时用来支持新的功能特性。

4.6 约束

4.6.1 数据完整性

完整性有以下三个方式:

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

4.7 视图

在MySQL数据库中,视图(View)是一个命名的虚表,它由一个SQL查询来定义,可以当做表使用。与持久表(permanent table)不同的是,视图中的数据没有实际的物理存储。 视图的作用:www.cnblogs.com/sustudy/p/4…

4.8 分区表

分区的过程是将一个表或索引分解为多个更小、更可管理的部分。就访问数据库的应用而言,从逻辑上讲,只有一个表或一个索引,但是在物理上这个表或索引可能由数十个物理分区组成。每个分区都是独立的对象,可以独自处理,也可以作为一个更大对象的一部分进行处理。 MySQL支持水平分区,指将同表中不同行的记录分配到不同的物理文件中。并不支持垂直分[插图]。此外,MySQL数据库的分区是局部分区索引,一个分区中既存放了数据又存放了索引。而全局分区是指,数据存放在各个分区中,但是所有数据的索引放在一个对象中。目前,MySQL数据库还不支持全局分区。启用分区之后,表不再由一个ibd文件组成了,而是由建立分区时的各个分区ibd文件组成。 当前MySQL数据库支持以下几种类型的分区。

  • RANGE分区:行数据基于属于一个给定连续区间的列值被放入分区。MySQL 5.5开始支持RANGE COLUMNS的分区。
  • LIST分区:和RANGE分区类型,只是LIST分区面向的是离散的值。MySQL 5.5开始支持LISTCOLUMNS的分区。
  • HASH分区:根据用户自定义的表达式的返回值来进行分区,返回值不能为负数。HASH分区的目的是将数据均匀地分布到预先定义的各个分区中,保证各分区的数据数量大致都是一样的。
  • KEY分区:根据MySQL数据库提供的哈希函数来进行分区。