4.1 索引组织表
在lnnoDB存储引擎中, 表都是根据主键顺序组织存放的, 这种存储方式的表称为索引组织表(index organized table)。 在InnoDB存储引擎表中, 每张表都有个主键 (Primary Key), 如果在创建表时没有显式地定义主键, 则lnnoDB存储引擎会按如下方式选择或创建主键:
1.首先判断表中是否有非空的唯一索引(Unique NOT NULL), 如果有, 则该列即为主键。
2.如果不符合上述条件,InnoDB存储引擎自动创建一个6字节大小的指针。
当表中有多个非空唯一索引时,InnoDB存储引擎将选择建表时第一个定义的非空唯 一索引为主键。 这里需要非常注意的是, 主键的选择根据的是定义索引的顺序, 而不是建表时列的顺序。
4.2 lnnoDB逻辑存储结构
从lnnoDB存储引擎的逻辑存储结构看,所有数据都被逻辑地存放在一个空间中,称之为表空间(tablespace)。表空间又由段(segment)、区(extent)、页(page)组成。页在一些文档中有时也称为块(block),InnoDB存储引擎的逻辑存储结构大致如图4-1所示。
4.2.1 表空间
表空间可以看做是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。第3章中已经介绍了在默认情况下InnoDB存储引擎有一个共享表空间ibdata1,即所有数据都存放在这个表空间内。如果用户启用了参数innodb_ file _per_ table, 则每张表内的数据可以单独放到一个表空间内。
如果启用了innodb_file_per_table 的参数,需要注意的是每张表的表空间内存放的只是数据。索引和插入缓冲Bitmap 页,其他类的数据,如回滚(undo) 信息,插入缓冲索引页、系统事务信息,二次写缓冲(Double write buffer) 等还是存放在原来的共享表空间内。这同时也说明了另一个问题:即使在启用了参数innodb_ file per_ table 之后,共享表空间还是会不断地增加其大小。
4.2.2段
图4-1中显示了表空间是由各个段组成的,常见的段有数据段、索引段、回滚段等。 因为前面已经介绍过了InnoDB存储引擎表是索引组织的(indexorganized), 因此数据即索引,索引即数据。那么数据段即为B+树的叶子节点(图4-1的Leafnode segment), 索引段即为B+树的非索引节点(图4-1的Non-leafnode segment)。回滚段较为特殊,将会在后面的章节进行单独的介绍。
在lnnoDB存储引擎中,对段的管理都是由引擎自身所完成,DBA不能也没有必要对其进行控制。这和Oracle数据库中的自动段空间管理(ASSM)类似,从一定程度上简化了DBA对于段的管理。
4.2.3区
区是由连续页组成的空间,在任何情况下每个区的大小都为1MB。为了保证区中页的连续性,InnoDB存储引擎一次从磁盘申请4~5个区。在默认情况下,InnoDB存储引擎页的大小为16KB,即一个区中一共有64个连续的页。
lnnoDB 1.0.x版本开始引入压缩页,即每个页的大小可以通过参数KEY_BLOCK_SIZE 设置为 2K、 4K、 8K, 因此每个区对应页的数批就应该为 512、 256、 128。
InnoDB 1.2.x版本新增了参数 innodb_page_ size, 通过该参数可以将默认页的大小设置为 4K、 8K, 但是页中的数据库不是压缩。这时区中页的数扭同样也为 256、 128。总之,不论页的大小怎么变化,区的大小总是为IM。
但是,这里还有这样一个问题:在用户启用了参数 innodb_ file _per_ talbe 后,创建的表默认大小是 96KB。区中是 64 个连续的页,创建的表的大小至少是 1MB 才对啊?其实这是因为在每个段开始时,先用 32 个页大小的碎片页 (fragment page) 来存放数据,在使用完这些页之后才是 64 个连续页的申请。这样做的目的是,对于一些小表,或者 是 undo 这类的段,可以在开始时申请较少的空间,节省磁盘的开容量的开销。
4.2.4 页
同大多数数据库一样,InnoDB有页(Page)的概念(也可以称为块), 页是lnnoDB磁盘管理的最小单位。在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 BLOG Page)
压缩的二进制大对象页(compressed BLOB Page)
4.2.5 行
InnoDB存储引擎是面向列的(row-oriented), 也就说数据是按行进行存放的。每个页存放的行记录也是有硬性定义的, 最多允许存放16KB / 2~200行的记录, 即7992行记录。这里提到了row-oriented的数据库, 也就是说, 存在有column-oriented的数据库。MySQL infobright存储引擎就是按列来存放数据的, 这对于数据仓库下的分析类SQL语句的执行及数据压缩非常有帮助。类似的数据库还有SybaseIQ、Google Big Table。面向列的数据库是当前数据库发展的一个方向, 但这超出了本书涵盖的内容, 有兴趣的读者可以在网上寻找相关资料。
4.3 lnnoDB 行记录格式
InnoDB存储引擎和大多数数据库一样(如Oracle和 Microsoft SQL Server数据库),记录是以行的形式存储的。 这意味着页中保存着表中一行行的数据。 在InnoDB 1.0.x版本之前,InnoDB存储引擎提供了Compact和Redundant两种格式来存放行记录数据, 这也是目前使用最多的一种格式。 Redundant格式是为兼容之前版本而保留的,如果阅读过InnoDB的源代码,用户会发现源代码中是用PHYSICAL RECORD (NEW STYLE) 和PHYSICAL RECORD (OLD STYLE)来区分两种格式的。 在MySQL5.1版本中, 默认设置为Compact行格式。 用户可以通过命令SHOW TABLE STATUS LIKE'table_ name'来查看当前表使用的行格式,其中 row_format属性表示当前所使用的行记录结构类型。
4.3.1 Compact 行记录格式
Compact行记录是在MySQL5.0中引入的, 其设计目标是高效地存储数据。 简单来说, 一个页中存放的行数据越多, 其性能就越高。 图4-2显示了Compact行记录的存储方式:
从图4-2可以观察到, Compact行记录格式的首部是一个非NULL变长字段长度列表, 并且其是按照列的顺序逆序放置的, 其长度为:
1.若列的长度小于255字节, 用1 字节表示;
2.若大于255个字节, 用2字节表示。
变长字段的长度最大不可以超过2字节, 这是因在MySQL数据库中VARCHAR_类型的最大长度限制为65535。变长字段之后的第二个部分是NULL标志位, 该位指示了该行数据中是否有NULL值, 有则用1 表示。该部分所占的字节应该为1字节。接下来的部分是记录头信息(record head er), 固定占用5字节(40位), 每位的含义见表4-1。
最后的部分就是实际存储每个列的数据。 需要特别注意的是, NULL不占该部分任何空间, 即NULL除了占有NULL标志位, 实际存储不占有任何空间。 另外有一点需要注意的是, 每行数据除了用户定义的列外, 还有两个隐藏列, 事务 ID 列和回滚指针列, 分别为 6 字节和 7 字节的大小。 若 InnoDB 表没有定义主键, 每行还会增加一个 6 字节的rowid列。
4.3.2 Redundant行记录格式
Redundant是MySQL5.0 版本之 前InnoDB的 行记录 存储 方式MySQL5.0支持Redundant是为了兼容之前版本的页格式。Redundant行记录采用如图4-3所示的方式存储 。
从图 4-3 可以看到, 不同于 Compact 行记录格式, Redundant 行记录格式的首部是一个字段长度偏移列表, 同样是按照列的顺序逆序放置的。若列的长度小于 255 字节, 用 1 字节表示 ; 若大于 255 字节, 用 2 字节表示。 第二个部分为记录头信息 (record header), 不同于 Compact 行记录格式, Redundant 行记录格式的记录头占用 6字节 (48位), 每位的含义见表 4-2。从表 4-2 中可以发现, n_fields 值代表一行中列的数扯, 占用 10 位。 同时这也很好地解释了为什么 MySQL 数据库一行支持最多的列为 1023。另一个需要注意的值为 Ibyte_ offs_ flags, 该值定义了偏移列表占用 1 字节还是 2 字节。 而最后的部分就是实际存储的每个列的数据了。
4.3.3 行溢出数据
InnoDB存储引擎可以将一条记录中的某些数据存储在真正的数据页面之外。一般认为BLOB、LOB 这类的大对象列类型的存储会把数据存放在数据页面之外。但是,这个理解有点偏差,BLOB可以不将数据放在溢出页面,而且即便是VARCHAR列数据类型,依然有可能被存放为行溢出数据 。
首先对VARCHAR数据类型进行研究。很多DBA喜欢MySQL数据库提供的 VARCHAR类型,因为相对于 OracleVARCHAR2最大存放 4000字节,SQL Se rver最大存放8000字节,MySQL数据库的VARCHAR类型可以存放65535字节。但是,lnnoDB存储引擎并不支持65535长度的VARCHAR。这是因 为还有别的开销,
此外需要注意的是,MySQL 官方手册中定义的 65535 长度是指所有VARCHAR列的长度总和,如果列的长度总和超出这个长度,依然无法创建。
4.3.4 Compressed和Dynamic行记录格式
InnoDB 1.0.x版本开始引入了新的文件格式(fileformat, 用户可以理解为新的页格式), 以前支持的Compact和Redundant格式称为Antelope文件格式,新的文件格式称为Barracuda文件格式。Barracuda文件格式下拥有两种新的行记录格式:Compressed和Dynamic。新的两种记录格式对于存放在BLOB中的数据采用了完全的行溢出的方式,如图4-5所示,在数据页中只存放20个字节的指针,实际的数据都存放在OffPage中, 而之前的Compact和Redundant两种格式会存放768个前缀字节。
Compressed行记录格式的另一个功能就是,存储在其中的行数据会以zlib的算法进 行压缩,因此对于BLOB、TEXT、VARCHAR这类大长度类型的数据能够进行非常有效的存储。
4.3.5 CHAR的行结构存储
通常理解VARCHAR是存储变长长度的字符类型,CHAR是存储固定长度的字符类型。而在前面的小节中,用户已经了解行结构的内部的存储,并可以发现每行的变长字段长度的列表都没有存储CHAR类型的长度。
然而,值得注意的是之前给出的两个例子中的字符集都是单字节的latin1格式。从MySQL4.1版本开始,CHR(N)中的N指的是字符的长度,而不是之前版本的字节长度。也就说在不同的字符集下,CHAR类型列内部存储的可能不是定长的数据。
4.4 lnnoDB数据页结构
相信通过前面几个小节的介绍,读者已经知道页是InnoDB存储引擎管理数据库的 最小磁盘单位。页类型为B-treeNode的页存放的即是表中行的实际数据了。在这一节中,我们将从底层具体地介绍InnoDB数据页的内部存储结构。
InnoDB数据页由以下7个部分组成,如图4-6所示。
File Header (文件头)
Page Header (页头)
Infimun和Supremum Records
User Records (用户记录,即行记录)
Free Space (空闲空间)
Page Directory (页目录)
File Trailer (文件结尾信息)
其中FileHeader、PageHeader、FileTrailer的大小是固定的,分别为38、56、8字节,这些空间用来标记该页的些信息,如Checksum,数据页所在B+树索引的层数等。UserRecords、FreeSpace、PageDirectory这些部分为实际的行记录存储空间,因此大小是动态的。在接下来的各小节中将具体分析各组成部分。
4.4.1 File Header
File Header 用来记录页的一些头信息, 由表 4-3 中 8 个部分组成, 共占用 38 字节。
4.4.2 Page Header
接着File Header部分的是Page Header, 该部分用来记录数据页的状态信息, 由14个部分组成, 共占用56字节, 如表4-5所示。
4.4.3 lnfimum和Supremum Record
在InnoDB存储引擎中,每个数据页中有两个虚拟的行记录,用来限定记录的边界。Infimum记录是比该页中任何主键值都要小的值,Supremum指比任何可能大的值还要大的值。这两个值在页创建时被建立,并且在任何情况下不会被删除。在Compact 行格式和Redundant行格式下,两者占用的字节数各不相同。图4-7显示了Infimum和 Supremum记录。
4.4.4 User Record和Free Space
User Record就是之前讨论过的部分, 即实际存储行记录的内容。再次强调,InnoDB存储引擎表总是B+树索引组织的。
Free Space很明显指的就是空闲空间, 同样也是个链表数据结构。在一条记录被删除后, 该空间会被加人到空闲链表中。
4.4.5 Page Directory
Page Directory (页目录)中存放 了记录的相对位置 (注意, 这里存放的是 页相对位置,而不是偏移量), 有些 时候这些记录 指针称为Slots(槽) 或目录槽(Directory Slots)。 与其他数据库系统不同的是,在lnnoDB中并不是每个记录拥有一个槽,InnoDB存储引擎的槽 是 一个稀疏目录(sparse directory), 即一个槽中可能包含多个记录。 伪记录 lnfimum的n_owned值总是为l, 记录 Supremum的n_owned的取值范围为[l, 8], 其他用户记录 n_owned的取值范围为[4, 8]。 当记录被插入或删除时需要对槽进行分裂或平衡的维护操作。
在Slots中 记录按照索引键值顺序存放, 这样可以利用二叉查找迅速找到记录的指针。
由于在InnoDB存储引擎中 Page Direcotry 是稀疏目录, 二叉查找的结果只是 一个粗略的结果,因此 InnoDB存储引擎必须通过recorder header中的next_record来继续查找相关记录。 同时,Page Directory很好地解释了recorder header中的n_owned值的含义,因为这些记录并不包括在Page Directory中。
需要牢记的是,B+树索引本身并不能找到具体的一条记录, 能找到只是该记录所在的页。 数据库把页载入到 内存,然后通过Page Directory再进行二叉查找 。 只不过二 叉查找的时间复杂度很低, 同时在内存中的查找很快, 因此通常忽略 这部分 查找所用的时间。
4.4.6 File Trailer
为了检测页 是否已经完整地写入磁盘(如可能发生的写人过程中磁盘损坏、 机器关机等),InnoDB存储引擎的页中设置 了File Trailer部分。
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函数来进行比较, 不是简单的等值 比较), 以此来保证页的完整性(not corrupted)。
在默认配置下,InnoDB存储引擎每次从磁盘读取一个页就会检测该页的完整性,即页是否发生Corrupt, 这就是通过File Trailer 部分进行检测,而该部分的检测会有一定的开销。 用户可以通过参数innodb_c heckus ms 来开启或关闭对这个页完整性的检查。
MySQL 5.6.6 版本开始新增了参数innodb_checksum_ algorithm, 该参数用来控制检测checksum函数的算法, 默认值为crc32, 可设置的值有: innodb 、crc32 、none 、strict_innodb、strict_crc32、strict_none。
innodb为兼容之前版本InnoDB页的checksum检测方式,crc32为MySQL5.6.6 版本引进的新的checksum算法, 该算法较之前的innodb有着较高的性能。但是若表中所有页的checksum 值都以strict算法保存, 那么低版本的MySQL数据库将不能读取这些页。none 表示不对页启用checksum检查。
strict_* 正如其名, 表示严格地按照设置的checksum 算法进行页的检测。因此若低版本MySQL数据库升级到MySQL 5.6.6 或之后的版本, 启用strict_crc32 将导致不能读取表中的页。启用strict_crc32 方式是最快的方式, 因为其不再对innodb和crc32算法进行两次检测。故推荐使用该设置。若数据库从低版本升级而来, 则需要进行mysql_upgrade操作。
4.5 Named File Formats 机制
随着 InnoDB 存储引擎的发展,新的页数据结构有时用来支持新的功能特性。比如前面提到的 InnoDB 1.0.x 版本提供了新的页数据结构来支持表压缩功能,完全的溢出(Off page) 大变长字符类型字段的存储。这些新的页数据结构和之前版本的页并不兼容,因此从 lnnoDB 1.0.x 版本开始,InnoDB 存储引通过 Named File Formats 机制来解决不同版本下页结构兼容性的问题。
InnoDB 存储引擎将 1.0.x 版本之前的文件格式 (file format) 定义为 Antelope, 将这个版本支持的文件格式定义为 Barracuda。新的文件格式总是包含于之前的版本的页格 式。图 4-8 显示了 Barracuda 文件格式和 Antelope 文件格式之间的关系,Antelope 文件 格式有 Compact 和 Redudant 的行格式,Barracuda 文件格式既包括了 Antelope 所有的文件格式,另外新加入了之前已经提到过的 Compressed 和 Dynamic 行格式。
4.6 约束
4.6.1 数据完整性
关系型数据库系统和文件系统的一个不同点是,关系数据库本身能保证存储数据的完整性,不需要应用程序的控制,而文件系统一般需要在程序端进行控制。当前几乎所有的关系型数据库都提供了约束(constraint)机制,该机制提供了一条强大而简易的途径来保证数据库中数据的完整性。一般来说,数据完整性有以下三种形式:
实体完整性保证表中有一个主键。在InnoDB存储引擎表中,用户可以通过定义Primary Key或Unique Key约束来保证实体的完整性。用户还可以通过编写一个触发器来保证数据完整性。
域完整性保证数据每列的值满足特定的条件。在InnoDB存储引擎表中,域完整性可以通过以下几种途径来保证:
1.选择合适的数据类型确保一个数据值满足特定条件。
2.外键(Foreign Key)约束。
3.编写触发器。
4.还可以考虑用DEFAULT约束作为强制域完整性的一个方面。
参照完整性保证两张表之间的关系。InnoDB存储引擎支持外键,因此允许用户定义外键以强制参照完整性,也可以通过编写触发器以强制执行。
对于InnoDB存储引擎本身而言,提供了以下几种约束:
Primary Key
Unique Key
Foreign Key
Default
NOTNULL
4.6.2 约束的创建和查找
约束的创建可以采用以下两种方式:
1.表建立时就进行约束定义
2.利用ALTER TABLE 命令来进行创建约束
对Unique Key (唯一索引)的约束, 用户还可以通过命令CREATE UNIQUE INDEX来建立。 对于主键约束而言, 其默认约束名为PRIMARY。而对于Unique Key约束而言,默认约束名和列名一样, 当然也可以人为指定Unique Key约束的名字。Foreign Key约束 似乎会有一个比较神秘的默认名称。
4.6.3 约束和索引的区别
的确, 当用户创建了一个唯一索引就创建了一个唯一的约束。但是约束和索引的概念还是有所不同的, 约束更是一个逻辑的概念, 用来保证数据的完整性, 而索引是一个数据结构, 既有逻辑上的概念, 在数据库中还代表着物理存储的方式。
4.6.4 对错误数据的约束
在某些默认设置下, MySQL数据库允许非法的或不正确的数据的插入或更新, 又或者可以在数据库内部将其转化为一个合法的值, 如向NOT NULL的字段插入一个NULL 值MySQL数据库会将其更改为0再进行插入, 因此数据库本身没有对数据的正确性进行约束。
4.6.5 ENUM和SET约束
MySQL数据库不支持传统的CHECK约束, 但是通过ENUM和SET类型可以解决部分这样的约束需求。 例如表上有一个性别类型, 规定域的范围只能是male 或female,在这种情况下用户可以通过ENUM类型来进行约束。
4.6.6 触发器与约束
通过前面小节的介绍, 用户已经知道完整性约束通常也可以使用触发器来实现, 因此在了解数据完整性前先对触发器来做一个了解。
触发器的作用是在执行INSERT、DELETE和UPDATE命令之前或之后自动调用SQL命令或存储过程。MySQL5.0对触发器的实现还不是非常完善,限制比较多,而从MySQL5.1开始触发器已经相对稳定, 功能也较之前有了大幅的提高。
最多可以为一个表建立6个触发器, 即分别为INSERT、UPDATE、DELETE 的BEFORE和AFTER各定义一个。BEFORE和AFTER代表触发器发生的时间, 表示是在每行操作的之前发生还是之后发生。 当前MySQL数据库只支持FOR EACH ROW的触发方式, 即按每行记录进行触发, 不支持像DB2的FOR EACH STATEMENT 的触发方式。
通过触发器, 用户可以实现MySQL数据库本身并不支持的一些特性, 如对于传统CHECK约束的支持, 物化视图、 高级复制、 审计等特性。 这里先关注触发器对于约束的支持。
4.6.7 外键约束
外键用来保证参照完整性, MySQL数据库的MyISAM存储引擎本身并不支持外键,对于外键的定义只是起到 个注释的作用。 而InnoDB存储引擎则完整支持外键约束。
一般来说, 称被引用的表为父表, 引用的表称为子表。 外键定义时的 ON DELETE 和ON UPDATE表示在对父表进行DELETE和UPDATE操作时, 对子表所做的操作, 可定义的子表操作有:
CASCADE
SET NULL
NO ACTION
RESTRICT
CASCADE表示当父表发生DELETE或UPDATE操作时, 对相应的子表中的数据也进行DELETE或UPDATE操作 。SET NULL表示当父表发生DELETE或UPDATE操作时, 相应的子表中的数据被更新为 NULL值, 但是子表中相 对应的列必须允许为NULL值。NO ACTION表示当父表发生DELETE或UPDATE操作时, 抛出错误, 不允许这类操作发生。RESTRICT表示当父表发生DELETE或UPDATE操作时, 抛出 错误, 不允许这类操作发生。 如果定义外键时没有指定ON DELETE或ON UPDATE, RESTRICT就是默认的外键设置。
在其他数据库中, 如Oracle数据库, 有一种称为延时检查(deferred check)的外键约束, 即检查在SQL语句运行完成后再进行。而目前MySQL数据库的外键约束都是即时检查(immediate check), 因此从上面的定义可以看出, 在MySQL数据库中 NO ACTION和RESTRICT的功能是相同的 。
InnoDB存储引擎在外键建立时会自动地对该列加一个索引, 这和Microsoft SQL Server 数据库的做法 样。因此可以很好地避免外键列上无索引而导致的死锁问题的产生。
4.7 视图
在MySQL数据库中,视图 (View) 是一个命名的虚表,它由一个SQL查询来定义,可以当做表使用。与持久表 (permanent table) 不同的是,视图中的数据没有实际的物理存储。
4.7.1 视图的作用
视图在数据库中发挥着重要的作用。视图的主要用途之一是被用做一个抽象装置,特别是对于一些应用程序,程序本身不需要关心基表(base table) 的结构,只需要按照视图定义来取数据或更新数据,因此,视图同时在一定程度上起到一个安全层的作用。
虽然视图是基于基表的一个虚拟表, 但是用户可以对某些视图进行更新操作, 其本质就是通过视图的定义来更新基本表。 一般称可以进行更新操作的视图为可更新视图 (updatable view)。 视图定义中的 WITH CHECK OPTION 就是针对于可更新的视图的, 即更新的值是否需要检查。
4.7.2 物化视图
MySQL数据库本身并不支持物化视图, 换句话说, MySQL数据库中的视图总是虚拟的。 但是用户可以通过一些机制来实现物化视图的功能。 例如要创建一个ON DEMAND的物化视图还是比较简单的, 用户只需定时把数据导人到另一张表。
4.8 分区表
4.8.1 分区概述
分区功能并不是在存储引擎层完成的, 因此 不是只有lnnoDB存储引擎支持分区,常见的存储引擎 MylSAM、NDB等都支持。 但也并不是所有的存储引擎都支持,如csv、FEDORATED、 MERGE等就不支持。 在使用分区功能前, 应该对选择的存储引 擎对分区的支持有所了解。
MySQL数据库在5.1版本时添加了对分区的支持。分区的过程是将一个表或索引分解为多个更小、更可管理的部分。就访间数据库的应用而言, 从逻辑上讲,只有一个表或一个索引, 但是在物理上这个表或索引可能由数十个物理分区组成。每个分区都是独立的对象, 可以独自处理,也可以作为—个更大对象的一部分进行处理。
MySQL数据库支持的分区类型为水平分区并不支持垂直分区此外,MySQL数据库的分区是局部分区索引, 一个分区中既存放了数据又存放了索引。水平分区, 指将同一表中不同行的记录分配到不同的物理文件中。眶直分区, 指将同一表中不同列的记录分配到不同的物理文件中。而全局分区是指, 数据存放在各个分区中,但是所有数据的索引放在一个对象中。目前, MySQL数据库还不支持全局分区。
大多数DBA 会有这样一个误区 : 只要启用了分区, 数据库就会运行得更快。 这个结论是存在很多问题的。 就我的经验看来, 分区可能会给某些SQL语句性能带来提高, 但是分区主要用于数据库高可用性的管理。 在OLTP 应用中, 对于分区的使用应该非常小心。 总之, 如果只是一味地使用分区, 而不理解分区是如何工作的, 也不清楚你的应 用如何使用分区, 那么分区极有可能会对性能产生负面的影响。
当前MySQL数据库支持以下几种类型的分区。
ORANGE分区:行数据基于属于 个给定连续区间的列值被放入分区。 MySQL5.5开始支待RANGECOLUMNS的分区。
LIST分区:和RANGE分区类型,只是LIST分区面向的是离散的值。 MySQL5.5开始支持LISTCOLUMNS的分区。
HASH分区:根据用户自定义的表达式的返回值来进行分区, 返回值不能为负数。 OKEY分区:根据MySQL数据库提供的哈希函数来进行分区。
不论创建何种类型的分区, 如果表中存在主键或唯一索引时, 分区列必须是唯一索引的一个组成部分。
4.8.2 分区类型
RANGE分区主要用于日期列的分区。LIST分区和RANGE分区非常相似, 只是分区列的值是离散的, 而非连续的。不同于RANGE分区中定义的VALUES LESS THAN语句,LIST分区使用VALUES IN。 因为每个分区的值是离散的,因此只能定义值。
HASH分区的目的是将数据均匀地分布到预先定义的各个分区中, 保证各分区的数据数显大致都是一样的。 在RANGE和LIST分区中, 必须明确指定一个给定的列值或列值集合应该保存在哪个分区中;而在HASH分区中, MySQL自动完成这些工作, 用 户所要做的只是基于将要进行哈希分区的列值指定一个列值或表达式, 以及指定被分区 的表将要被分割成的分区数狱。
MySQL数据库还支持一种称为LINEARHASH的分区, 它使用一个更加复杂的算法来确定新行插人到已经分区的表中的位置。 它的语法和HASH分区的语法相似, 只是 将关键字HASH改为LINEARHASH。LINEAR HASH分区的优点在于,增加、删除、合并和拆分分区将变得更加快捷,这有利于处理含有大扯数据的表。它的缺点在千,与使用HASH分区得到的数据分布相比,各个分区间数据的分布可能不大均衡。
4.KEY分区
KEY分区和HASH分区相似,不同之处在于HASH分区使用用户定义的函数进行分区,KEY分区使用MySQL数据库提供的函数进行分区。对于NDBCluster引擎, MySQL数据库使用MD5函数来分区;对于其他存储引擎,MySQL数据库使用其内部的哈希函数,这些函数基于与PASSWORD()一样的运算法则
5.COLUMNS分区
在前面介绍的RANGE、LIST、HASH和KEY这四种分区中,分区的条件是:数据必须是整型(interger), 如果不是整型,那应该需要通过函数将其转化为整型,如 YEARO, TO_DAYSO, MONTHO等函数。MySQL5.5版本开始支持COLUMNS分 区,可视为RANGE分区和LIST分区的一种进化。COLUMNS分区可以直接使用非整型的数据进行分区,分区根据类型直接比较而得,不需要转化为整型。此外,RANGEOLUMNS分区可以对多个列的值进行分区。
COLUMNS分区支持以下的数据类型:
所有的整型类型,如INT、SMALLINT、TINYINT、BIGINT。FLOAT和DECIMAL则不予支持。
日期类型,如DATE和DATETIME。其余的日期类型不予支持。
字符串类型,如CHAR、VARClfAR、BINARY和VARBINARY。BLOB和TEXT类型不予支持。
4.8.3 子分区
子分区(subpartitioning)是在分区的基础上再进行分区, 有时也称这种分区为复合分区(composite partitioning)。 MySQL数据库允许在RANGE和LIST的分区上再进行HASH或KEY的子分区。
子分区的建立需要注意以下几个问题:
1.每个子分区的数批必须相同。
2.要在一个分区表的任何分区上使用SUBPARTITION来明确定义任何子分区, 就必须定义所有的子分区
3.每个SUBPARTITION子句必须包括子分区的一个名字。
4.子分区的名字必须是唯一的
4.8.4 分区中的NULL值
MySQL数据库允许对NULL值做分区, 但是处理的方法与其他数据库可能完全不同。 MYSQL数据库的分区总是视NULL值视小于任何的一个非NULL值, 这和MySQL数据库中处理NULL值的ORDER BY操作是一样的。 因此对于不同的分区类型, MySQL数据库对于NULL值的处理也是各不相同。
对于RANGE分区, 如果向分区列插入了NULL值, 则MySQL数据库会将该值放入最左边的分区。
4.8.5 分区和性能
我常听到开发人员说 “对表做个分区”,然后数据库的查询就会快了。 这是真的吗?实际上可能根本感觉不到查询速度的提升, 甚至会发现查询速度急剧下降。 因此,在合理使用分区之前, 必须了解分区的使用环境。
数据库的应用分为两类: 一类是OLTP (在线事务处理), 如Blog、 电子商务、 网络游戏等; 另一类是OLAP (在线分析处理), 如数据仓库、 数据集市。 在一个实际的应用环境中, 可能既有OLTP的应用, 也有OLAP的应用。如网络游戏中, 玩家操作的游戏数据库应用就是OLTP的, 但是游戏厂商可能需要对游戏产生的日志进行分析, 通过分 析得到的结果来更好地服务千游戏, 预测玩家的行为等, 而这却是OLAP的应用。
对于OLAP的应用, 分区的确是可以很好地提高查询的性能, 因为OLAP应用大多数查询需要频繁地扫描一张很大的表。假设有一张1亿行的表, 其中有一个时间戳屈性 列。用户的查询需要从这张表中获取一年的数据。 如果按时间戳进行分区, 则只需要扫描相应的分区即可。 这就是前面介绍的PartitionPruning技术。
然而对于OLTP的应用, 分区应该非常小心。 在这种应用下, 通常不可能会获取一张大表中10%的数据, 大部分都是通过索引返回几条记录即可。而根据B+树索引的原理可知, 对于一张大表, 一般的B+树需要2,...,3次的磁盘IO。因此B+树可以很好地完成操作, 不需要分区的帮助, 并且设计不好的分区会带来严重的性能问题。
我发现很多开发团队会认为含有1000W行的表是一张非常巨大的表, 所以他们往往会选择采用分区, 如对主键做 10个 HAS H 的分区, 这样每个分区就只有100W的数据了, 因此查询应该变得更快了, 如SELECT * FROM TABLE WHERE PK =@pk。 但是有没有考虑过这样一 种情况: IOOW和1000W行的数据本身构成的B+树的层次都是一样的, 可能都是2层 。那么上述走主键分区的索引并不会带来性能的提高。好的, 如果IOOOW的B+树的高度是3, IOOW的B+树的高度是2, 那么上述按主键分区的索引可以避免1次IO, 从而提高查询的效率。 这没问题, 但是这张表只有主键索引, 没有任何其他的列需要查询的。 如果还有类似如下的SQL语句: SELECT* FROM TABLE WHERE KEY=@key, 这时对于 KEY的查询需要扫描所有的10个分区, 即使每个分区的查询开销为2次IO, 则一共需要20次IO。 而对于原来单表的设计, 对于KEY的查询只需要2~3次IO。
4.8.6 在表和分区间交换数据
MySQL5.6 开始支持ALTER TABLE … EXCHANGE PARTITION语法。该语句允许分区或子分区中的数据与另一个非分区的表中的数据进行交换。如果非分区表中的数据为空, 那么相当千将分区中的数据移动到非分区表中。若分区表中的数据为空, 则相当于将外部表中的数据导入到分区中。
要使用ALTER TABLE … EXCHANGE PARTITION语句, 必须满足下面的条件:
要交换的表需和分区表有着相同的表结构, 但是表不能含有分区
在非分区表中的数据必须在交换的分区定义内
被交换的表中不能含有外键, 或者其他的表含有对该表的外键引用
用户除了需要ALTER、INSERT 和CREATE权限外, 还需要DROP的权限
此外, 有两个小的细节需要注意:
使用该语句时, 不会触发交换表和被交换表上的触发器
AUTO_INCREMENT列将被重置