数据库设计数据布局和空间管理应当考虑哪些因素呢?又有哪些设计原则?本章先提出问题,看有哪些因素要考虑,然后选取Oracle和MySQL这2款最流行的数据库,看看它们是如何进行空间管理的。我们先看看有哪些因素要考虑:
-
数据最终要反映到持久设备上,空间管理首先要考虑持久设备(如磁盘)的物理特性,考虑如何组织数据才能最大化地发挥存储设备的效率;
-
如何协调平衡多样化的数据,如超大的单表和数据庞大的小表,持久数据和临时数据,系统管理数据和用户数据,索引数据和非索引数据等等;
-
如何平衡数据的逻辑相关性和逻辑独立性,逻辑相关性要求将逻辑上相关的数据聚集在一起,提高联合访问的效率,而逻辑独立性又要求将逻辑上独立的数据独立存放,提高数据失效、迁移、变更的效率;
-
如何平衡并发度和空间利用率,并发度要求尽可能将所有相关的持久化设备都调动起来,将设备能力尽可能均衡地用满,而空间利用率则要求尽可能紧凑地利用空间,避免空间区域和碎片,提高扫描效率;
Oracle的tablespace由若干个datafile组成,datafile以extent为单位进行空间的申请和释放。可以这么说,datafile是空间的提供者,而segment是空间的使用者,两者之间的交互语言就是extent和block。
图1.2-1 Block结构
表1.2-1 Cache Layer结构
表1.2-2 footer结构
block是数据管理的基本单元,在讨论datafile空间管理方式之前,我们先讨论一下block。如图1.2-1所示,所有的block都有3个部分组成:
-
Cache layer :定义 block 的总体信息,详情如表 1.2-1 所示;
-
Data : block 中存放的内容,具体内容随 cache layer 中的 type 不同而不同;
-
Footer :如果 block 发生了更改, scn 和 seq 至少有一个会变动,通过比较 footer 和 cache layer 中的对应部分就可以检测块损坏(如写入磁盘时,写入一半掉电的情况),当然 cache layer 中还有 chkval ,如果启用可以提供更强的校验能力;
图1.2-2 datafile文件结构
了解了block基本文件结构之后,我们开始讨论datafile是如何提供空间的。如图1.2-2所示,datafile由Data File Header、KTFB Bitmapped File Space Header、KTFB Bitmapped File Space Bitmap和Data组成,其中Data File Header、KTFB Bitmapped File Space Header、KTFB Bitmapped File Space Bitmap为文件保留区,保留区占用的block数随db_block_size的变化而变化(如2KB保留32个block,4KB保留16个block,8KB保留8个block,16KB和32KB保留4个block),下面详细看下各个部分的含义:
-
Data File Header :描述本 datafile 的总体信息, Cache Layer.type=0x0b , block size 为 8KB 时占用 2 个 block ,详情见表 1.2-3 ;
-
KTFB Bitmapped File Space Header :描述 bitmap 的总体信息, Cache Layer.type=0x1d , block size 为 8KB 时占用 1 个 block ,详情见表 1.2-4 ;
-
KTFB Bitmapped File Space Bitmap :完整的 bitmap ,用于描述本 datafile 中各个 extent 的详细情况, Cache Layer.type=0x1e , block size 为 8KB 时占用 5 个 block ,详情见表 1.2-5 ;
-
Data :用于存放具体数据;
表1.2-3 Data File Header部分关键信息
表1.2-4 KTFB Bitmapped File Space Header部分关键信息
表1.2-5 KTFB Bitmapped File Space Bitmap部分关键信息
在KTFB Bitmapped File Space Bitmap Block中,对于统一尺寸的extent来说,unit等于extent,即1个bit位代表1个extent。对于自动管理来说,extent的大小是变化的,1个bit位代表1个最小尺寸的extent。由于大extent一定是小extent的整数倍,所以unit将最小exent作为基准,即1个bit位代表64K。当extent的实际大小是1M、8M、64M时就用多个bit位来表示,如1M的extent就用连续的16个bit来标识。
从空间利用率来看,由于某个extent只能属于某个特定的segment,所以小extent空间利用率高,大extent空间利用率低。例如当extent的大小为64M时,表哪怕仅使用了1个字节,也为它分配1个64M的extent,这64MB空间完全属于这个表,其它表无法使用。从性能的角度来看,大的extent空间比较连续,非常适合全表扫描类操作。Oracle的动态管理方式在空间利用率、性能之间做了平衡。
图1.2-3 segment 3级bitmap管理机制
通过上节我们知道,datafile是通过bitmap管理表空间的extent分配情况。segment作为实际使用者,需要更加精确地知道本segment中各block的使用情况。如图1.2-3所示,segment是通过3级位图机制对block进行精细化管理的:
-
L1 BMB ( BitMap Block ):详细描述了其管理的各 data block 的空间使用情况,并有指向其归属的 L2 BMB 指针,详细情况见表 1.2-6 ;
-
L2 BMB :记录了其管理的所有 L1 BMB 地址,并有指向其归属的 L3 BMB 指针,详细情况见表 1.2-7 ;
-
L3 BMB :记录了其管理的所有 L2 BMB 地址,并有指向其归属的 segment header block 指针,实际上 segment header block 本身就是一个特殊的 L3 BMB ;
表1.2-6 L1 BMB部分关键信息
表1.2-7 L2 BMB部分关键信息
随着申请的data block越来越多,需要不断地申请L1 BMB、L2 BMB和L3 BMB进行管理。实际上,通过L1 BMB和L2 BMB已经能够管理非常庞大的data block数量,使用L3 BMB的情况非常少见(当然data segment header block本身就是一个特殊的L3 BMB)。在高并发时,存在多个L1 BMB可用,分散了争用,提高了并发性,可以获得非常高的性能:
-
通过 data segment header block 找到第 1 个含空闲空间的 L2 BMB ( L2 Hint for inserts );
-
对操作进程 PID 做 HASH 运算,得到随机数 N ,在 L2 BMB 中找到第 N 个 L1 BMB;
-
对操作进程 PID 做 HASH 运算,得到随机数 M ,在 L1 BMB 中找到第 M 个有空闲空间的 data block;
-
向找到的 data block 插入数据;
实际插入算法更加复杂,既要考虑一定的随机性和离散性,避免并发插入在特定的data block和BMB上竞争,又要考虑一定的聚集性,提高空间利用率和数据扫描效率(实际上会优先插入HWM以下的data block),最终算法在两者之间进行平衡。
图1.2-4 data segment header block结构
表1.2-8 Extent Control Header部分关键信息
表1.2-9 Extent Map和Auxiliary Map部分关键信息
如图1.2-4所示,data segment header block包含如下几个关键部分:
-
通过 extent control header 、 L3 BMB 、 L2 BMB 、 L1 BMB 就可以知道所有 data block 的空间使用情况、 HWM 情况,然后进行高并发的插入操作;
-
通过 extent control header 、 extent map 、 auxiliary map 就可以知道所有数据的分布情况、 HWM 情况,进行高效的数据扫描,即从 extent map 起始 extent 开始扫描,扫描至 Low HWM ,对于 Low HWM 和 high HWM 之间的 block ,需要参考 L1 BMB ,以避开尚未格式化的 block ;
图1.2-5 extent内block分布示例
最后再看一下extent内的block分布情况,不管block是何种类型,最终都要落到extent内,接受以extent为单位的连续空间管理。图1.2-5给出了extent内block的分布示例,如果extent内存在metadata类型的block,一般都存放在extent的头部。extent在使用和分配上存在如下规律:
-
延迟分配,建表时不分配 extent ,只有在实际插入数据时才会分配 extent ;
-
非统一大小时, extent 的大小是动态变化的(以 block size 等于 8K 为例):
-
0-15 extent ,每个 extent 包括 8 个 block ,此时 segment 总大小 <=1M ,每个 L1 BMB 可以管理 16 个 block ;
-
16-79 extent ,每个 extent 包括 128 个 block ,此时 segment 总大小 <=32M ,每个 L1 BMB 可以管理 64 个 block ;
-
80-199 extent ,每个 extent 包括 1024 个 block ,此时 segment 总大小 <=1G ,每个 L1 BMB 可以管理 256 个 block ;
-
>199 extent ,每个 extent 包括 8192 个 block ,此时 segment 总大小 >1G ,每个 L1 BMB 可以管理 1024 个 block ;
-
-
有些情况下 1 个 L1 BMB 会管理多个 extent ,所以有的 extent 中没有 L1 BMB ,有些情况下 1 个 L1 BMB 无法将单个 extent 中的所有 block 都管理起来,此时 extent 中就会有多个 L1 BMB ;
-
当 tablespace 由多个 datafile 组成时, extent 分片会考虑一定的均衡性, segment 会分布在多个 datafile 中,但单个 extent 不会跨 datafile ;
图1.2-6 data block结构
表1.2-10 Table Directory部分关键信息
表1.2-11 Row Directory部分关键信息
表1.2-10 Row Data部分关键信息
如图1.2-6所示,data block和数据存储强相关的部分主要包括Row Directory和Row Data,Oracle的data block设计有如下特点:
-
row directory :行目录, array 型结构,每行记录占用 2 个字节,指向该行记录在 Row Data 中的位置。 array 的下标是 RowID 的组成部分,所以某行记录一旦落到 block 中,其在 row directory 中的位置就不能改变( row data 是可以被整理的),因为 index 、行迁移、行链接等都是通过 RowID 来定位的;
-
row data :每行记录的头部大小应尽可能地小,从而提高空间利用率。 fb 、 lb 、 cc 仅占用 3 个字节,每列的 length 部分也是变长的,且对于取值为 null 的 number 类型列,直接通过 length=0xff 来表示,不再占用 data 空间;
-
随着行记录的增加, row directory 从上向下增长, row data 从下向上增长;
-
data block 如果写入过满,将来 update 操作很可能产生较多行迁移,影响查询性能,为此通过 PCTFree 参数定义插入时预留多少空间出来,用于将来的 update ( pctfree 对 data block ,以及 B 树的 leaf block 有效,但对 B 树的 root block 和 branch block 无效);
Oracle中有system tablesapce、sysaux tablespace、temporary tablespace、undo tablespace和permanent tablespace,其存放的数据分别如下:
-
system tablespace :存放数据字典;
-
sysaux tablespace :存放工具相关对象信息;
-
temporary tablespace :存放临时数据,以及在内存不足时作为诸多活动的辅助空间(如排序);
-
undo tablespace :存放 undo 日志(前像);
-
permanent tablespace :存放用户数据;
应用可以根据实际情况创建多个temporary tablespace、undo tablespace和permanent tablespace。同时还可以设置bigfile属性,以容纳更大的数据量。每个tablespace都按照segment进行管理,规则如下:
-
1 个表创建 1 个 segment ,如果是分区,每个分区创建 1 个 segment ;
-
1 个索引创建 1 个 segment ,如果是分区索引,每个分区索引创建 1 个 segment ;
-
表的每个 CLOB 、 BLOB 或者 NCLOB 列都会创建 1 个 segment ,如果这些列上有索引,还会创建对应的 LOB 索引 segment ;
MySQL同样也采用了tablespace、segment、extent、page(对应oracle中的block)这几个概念,tablespace由若干个datafile组成,datafile以extent为单位进行空间的申请和释放。不同的是MySQL将extent分成了2种:
-
extent :正常的 extent , 1 个 extent 大小为 1M ,由连续的 page 组成, segment 以 extent 为单位申请和释放空间;
-
frag extent :特殊的 extent , 1 个 frag extent 也是连续的 1M 空间,但是 frag extent 不属于任何 segment ,而是为大家共用。 MySQL 不支持变长 extent ,为了节约空间,每个 segment 的前 32 个 page 在 frag extent 中申请 page ,超过 32 个 page 再以 extent 为单位申请空间;
图1.3-1 page结构
表1.3-1 FIL HEADER结构
表1.3-2 FIL TAILER结构
page是MySQL datafile空间管理的基本单位,其结构如1.3-1所示,组成部分如下:
-
FIL HEADER : page 头,定义了 page 的总体信息,详细情况见表 1.3-1 ;
-
Data :数据部分,格式随着 FIL_PAGE_TYPE 不同而不同;
-
FIL TAILER : page 尾,用于校验异常 page ,详细情况见表 1.3-2 ;
图1.3-2 datafile总体空间布局
了解page的结构,下面看总体空间布局。如图1.3-2所示,MySQL数据文件空间布局相对比较固定,非常有规律可循:
-
FIL_PAGE_TYPE_XDES :该类 page 存放 extent 的管理信息,当 page size 确定后每个 FIL_PAGE_TYPE_XDES page 可以管理的 extent 数量是固定的,这样本 page 就可以按照管理的 extent 数有规律地存放。图 1.3-2 示例是 page size 为 16K 的情况,每个 FIL_PAGE_TYPE_XDES page 可以管理 256 个 extent ,每个 extent 包含 64 个 page ,所以 FIL_PAGE_TYPE_XDES page 每隔 16384 ( 256*64 )个 page 出现 1 个;
-
FIL_PAGE_TYPE_HDR : FIL_PAGE_TYPE_HDR 和 FIL_PAGE_TYPE_XDES 非常类似,除了存放 extent 管理信息之外,还多了一个 SPACE HEADER 结构,作为空间管理的总入口,可以这么说, FIL_PAGE_TYPE_HDR 是一个特殊的 FIL_PAGE_TYPE_XDES 。本类 page 只有 1 个,且固定在第 0 个位置上;
-
FIL_PAGE_IBUF_BITMAP :本类 page 存放每个 page 的空闲情况以及 IBUF 的使用情况,由于 FIL_PAGE_IBUF_BITMAP 和 FIL_PAGE_TYPE_XDES 管理的规模一样多,所以 FIL_PAGE_IBUF_BITMAP page 永远跟在 FIL_PAGE_TYPE_XDES page 后面,位置固定;
-
FIL_PAGE_INODE :本类 page 存放各 segment 的入口信息, page 的类型为 FIL_PAGE_INODE ,位置不固定;
图1.3-3 FIL_PAGE_TYPE_HDR vs FIL_PAGE_TYPE_XDES
表1.3-3 XDES Entry结构
表1.3-4 SPACE HEADER结构
下面详细对比一下FIL_PAGE_TYPE_HDR page和FIL_PAGE_TYPE_XDES page,如图1.3-3所示,两者的结构基本是一样的。FIL_PAGE_TYPE_HDR page的SPACE HEADER结构是有信息的,而FIL_PAGE_TYPE_XDES page的对应位置为空。SPACE HEADER结构存放的是空间管理的总入口信息,所以只需要存在于第0个page中。在讨论SPACE HEADER前,先看一下XDES Entry结构。如表1.3-3所示,XDES Entry通过XDES_BITMAP标识其管理的extent中各page的空间状态,FLST_PREV和FLST_NEXT双向指针将处于相同状态且归属于同1个segment的XDES Entry链接在一起。那么这些链表的头指针在哪里呢?在SPACE HEADER结构中,如表1.3-4所示,SPACE HEADER作为总入口,管理着如下5个重要的链表:
-
FSP_FREE 链表:管理所有空的 extent ,指向状态为 XDES_FREE 的 XDES Entry ;
-
FSP_FREE_FRAG 链表:管理所有含空闲空间的 frag extent ,指向状态为 XDES_FREE_FRAG 的 XDES Entry ;
-
FSP_FULL_FRAG 链表:管理所有已经写满的 frag extent ,指向状态为 XDES_FULL_FRAG 的 XDES Entry ,随着 frag extent 的满和不满,对应的 XDES Entry 会在 FSP_FREE_FRAG 链表和 FSP_FULL_FRAG 链表之间移动;
-
FSP_SEG_INODES_FULL 、 FSP_SEG_INODES_FREE 链表:分别管理所有已经写满和尚未写满的 INDOES ;
-
链表头结构: FLST_LEN 4 个字节 +FLST_FIRST 6 个字节 +FLST_LAST 6 个字节,共计 16 个字节, FLST_LEN 表示链表长度, FLST_FIRST 指向第 1 个节点( 4 个字节的 page no+2 个字节的 page 内偏移), FLST_LAST 指向最后 1 个节点;
通过FSP_FREE_FRAG和FSP_FULL_FRAG链表将所有frag extent管理起来,通过FSP_FREE链表将所有尚未分配给segment的空闲extent管理起来,那么已经分配给segment的extent又是如何管理的呢?实际上,就是通过FSP_SEG_INODES_FULL和FSP_SEG_INODES_FREE链表进行间接管理的,我们将在segment机制章节进一步讨论。
图1.3-4 FIL_PAGE_IBUF_BITMAP结构
表1.3-5 Change Buffer Bitmap结构
最后再看一下FIL_PAGE_IBUF_BITMAP page,MySQL发明了Insert Buffer机制,并将其进一步通用化为Change Buffer机制。其核心思想是对非唯一的二级索引进行insert、delete时,如果对应的page不在缓存中,不必将该page调度进缓存进行实时操作,而是将insert、delete操作缓存到insert buffer page中,待将来读到该page或者后台purge线程再实施真正的insert、delete操作,从而提高效率。但insert buffer机制存在如下约束和要求:
-
唯一索引上的 insert 操作不能缓存,因为要做唯一性校验,必须读到真正的 page 才能做唯一性校验;
-
insert buffer 是以 BTree 组织的,缓存的每条记录的主键为 spaceid 、 page no 、 counter ,通过 spaceid 、 page no 记录缓存的操作时针对哪个 page 的,但这就要求 page no 在缓存过程中不能发生变化,这就需要一个机制来及时识别 page no 是否可能发生变化;
-
还需要一个机制来识别某个 page 上的操作是否有缓存,从而在读到该 page 时,先将缓存的操作 merge 进来,然后再实施真正的读处理;
FIL_PAGE_IBUF_BITMAP page就是为这些目的而设计的,FIL_PAGE_IBUF_BITMAP page固定在FIL_PAGE_TYPE_XDES page后面。如图1.3-4所示,change buffer bitmap中每4个bit对应后面的1个page,8192个字节就可以管理16384个page(8192*8/4)。如表1.3-5所示,4个bit的组成如下:
-
IBUF_BITMAP_FREE :给出某个特定 page 的空闲情况,如果空闲空间过小,会发生 page 分裂, page no 会发生变化,不能缓存,否则可以缓存;
-
IBUF_BITMAP_BUFFERED :标识某个特定 page 上是否做了操作缓存;
-
IBUF_BITMAP_IBUF :标识某个特定 page 本身缓存的一部分;
图1.3-5 FIL_PAGE_INODE page结构
表1.3-6 INODE Entry结构
MySQL是通过INODE Entry管理segment的,每个INODE Entry对应1个segment。下面结合存放INODE Entry的FIL_PAGE_INODE page一起来讨论。如图1.3-5所示,FIL_PAGE_INODE page包括如下2个部分:
-
FSEG_INODE_PAGE_NODE : 12 个字节的双向指针,将相关的 FSEG_INODE_PAGE_NODE page 链接在一起。实际上有 2 个 FSEG_INODE_PAGE_NODE 链表,就是 FIL_PAGE_TYPE_HDR page 中的 FSP_SEG_INODES_FULL 和 FSP_SEG_INODES_FREE 链表,前者将所有已经写满 INODE Entry 的 FSEG_INODE_PAGE_NODE page 链接在一起,后者将尚未写满 INODE Entry 的 FSEG_INODE_PAGE_NODE page 链接在一起;
-
INODE Entry : 1 个 FSEG_INODE_PAGE_NODE page 中可以存放 n 个 INODE Entry (随 page 的大小变化),每个 INODE Entry 管理 1 个具体的 segment ;
如表1.3-6所示,INODE Entry管理着1个具体的segment,其中的关键信息如下:
-
FSEG_FREE 、 FSEG_NOT_FULL 、 FSEG_FULL : 3 个 extent 链表的指针头,结合 XDES Entry 结构中的双向指针,把已经分配给本 segment 的 extent 按照尚未使用、已使用但尚未用满、已经用满 3 种情况管理起来;
-
FSEG_FRAG_ADDR :通过记录 page 号,将 frag extent 中分配给本 segment 的 32 个 page 管理起来;
图1.3-6 空间管理全貌
至此,如图1.3-6所示,我们得到MySQL空间管理的全景图(不含ibuf):
-
SPACE HEADER 中的 FSP_FREE 结合 XDES Entry 将所有尚未分配给 segment 的 extent 管理起来;
-
SPACE HEADER 中的 FSP_FREE_FRAG 和 FSP_FULL_FRAG 结合 XDES Entry 将所有尚未用满和已经用满的 frag extent 管理起来;
-
SPACE HEADER 中的 FSP_SEG_INODES_FREE 和 FSP_SEG_INODES_FULL 结合 FSEG_INODE_PAGE_NODE 将所有尚未用满和已经用满的 FIL_PAGE_INODE 管理起来;
-
每个 segment 有 1 个 INODE Entry (存在于 FIL_PAGE_INODE 中),通过 FSEG_FULL 、 FSEG_NOT_FULL 、 FSEG_FREE 结合 XDES Entry 将所有分配给本 segment 的已经用满、尚未用满、尚未使用的 extent 管理起来;
-
frag extent 既被 SPACE HEADER 管理,其中的 page 同时又被 INODE Entry 中的 FSEG_FRAG_ADDR 管理;
图1.3-7 data page结构
表1.3-7 PAGE HEADER结构
表1.3-8 segment info结构
如图1.3-7所示,data page除了通用的FIL HEADER和FIL TAILER之外,其它关键组成部分如下:
-
PAGE HEADER :描述了本 page 的总体信息,详细见表 1.3-7 ,通过 PAGE_FREE 和每条记录中的 REC_NEXT ,将所有标志为删除的记录链接在一起;
-
SEGMENT INFO :仅在 BTree 的 root page 中设置, SEGMENT INFO1 记录叶子节点 segment 对应的 INODE 信息, SEGMENT INFO2 记录非叶子节点 segment 对应的 INODE 信息;
-
SYS RECORDS : 2 条特殊的系统记录, infimum 和 supremun ,占用 26 个字节,用于 MySQL 特有的边界定位和锁控制;
-
USER RECORDS :用户记录,从上向下增长;
-
PAGE DIRECTORY :目录,从下向上增长,每个 slot 占用 2 个字节,指向 USER RECORDS 中的具体某条记录。目录中的 slot 和记录不是一一对应的,每 4 到 8 条记录在目录中占用 1 个 slot ;
有效的用户记录(删除标志位不为真)和2条系统记录通过记录头的REC_NEXT链接在一起,链接的顺序即为索引的键值顺序,且infimum永远是第1条记录,而supremum永远是最后1条记录。为了加快记录匹配效率,MySQL在page的底部设计了DIRECTORY区域,每隔4-8条记录占用1个slot(slot对应多少条记录是动态变化的,slot之间也不相等,就某个slot来说,通过对应记录头的REC_NEW_N_OWNED记录相应的记录数)。由于DIRECTORY区域是有序的(按主键或索引键逆序存放),可以通过折半查找快速匹配到目标记录。
图1.3-8 compact记录格式
表1.3-9 RECORD HEADER结构
compact格式已经成为MySQL的主流格式。如图1.3-8所示,compact记录格式包括如下组成部分:
-
RECORD_HEADER :记录的头部信息,详情如表 1.3-9 所示,记录在 page 内的偏移一般都是指 RECORD HEADER 的位置,因为记录的其它几个部分都是变长的;
-
VAR_COL_LEN_LST :每个变长列的实际长度在此描述,最大长度小于 255 或者实际长度小于 128 占 1 个字节,否则占 2 个字节;
-
NULL_BITS :每个可为 NULL 的列在此占 1 个 bit ,如果列的实际值为 NULL ,此 bit 位为真, ROW_DATA 部分不会出现此列;
-
ROW_DATA :实际列数据,聚集索引会增加事务 id ( 6 个字节)和回滚指针( 7 个字节) 2 个隐藏列;
MySQL的数据包括系统数据、临时数据、回滚数据、用户数据、重做数据和binlog数据,重做数据和binlog数据相对独立,和tablespace没有太大关系,下面重点看其它几个数据的布局:
-
用户数据: InnoDB 的所有表都是索引组织表,每个索引创建 2 个 segment , 1 个 segment 存放叶子节点 page , 1 个 segment 存放非叶子节点 page 。每张表存放的 tablespace 随着历史的演进有 3 种方案:
-
早期版本:所有数据都存放在 1 个公共的系统 tablespace 中;
-
其后版本:增加支持每张表有 1 个独立的 tablespace 中;
-
近期版本:增加支持通用的 tablespace ;
-
-
系统类数据( InnoDB ):主要有数据字典数据和系统数据,存放在公共的系统 tablespace 中。字典数据表有 SYS_TABLES 、 SYS_INDEXS 、 SYS_COLUMNS 、 SYS_FIELDS ,分别存放表信息、索引信息、表列信息、索引列信息,也采用索引组织表方式存放。系统数据有 insert buf 、 double write 、事务系统数据等等;
-
临时数据:存放用户创建的临时表,对应临时 tablespace ;
-
回滚数据:存放回滚数据,包括 rollback segment 和 undo log segment ,早期版本存放在公共的系统 tablespace 中,近期版本可存放在独立的 undo tablespace 中;
Oracle和MySQL的空间布局总体上非常类似,采用tablespace、segment、extent、block(page) 4层管理机制。extent和block(page)最大化地发挥持久设备的物理特性,tablespace和segment用于在逻辑上平衡数据的独立性和相关性。不过在管理设计上,Oracle和MySQL还是存在非常大的差异性。
在block(page)层面,Oracle的block设计更加通用,MySQL的page设计与索引组织表深度耦合,具体表现在:
-
对于单条记录, MySQL 设计了 NULL_BITS ,稀疏场景下可以有效节省空间。不过 MySQL 的记录头和隐藏列比较厚重,每条记录占用 18 个字节( RECORD HEADER 占 5 个字节,事务 id 占 6 个字节,回滚指针占 7 个字节), Oracle 的记录头仅占 3 个字节;
-
MySQL 的 page 也设计相对比较粗放,某些区域仅在特定场景下有效,如 SEGMENT INFO 仅在 BTree 的 root page 中有效, FIL_PAGE_PREV 和 FIL_PAGE_NEXT 仅对 BTree 的叶子节点 page 有效等等;
-
Oracle 采用了相对地址管理方式, RowID 中保存了 file no 、 block no 、 directory id ,优势是可以快速定位记录,缺点是 block 内的碎片整理可以比较高效,但 block 间的整理涉及记录在 block 间移动,代价非常高 (shrink table ,调整 rowid ,涉及面较广 ) 。 MySQL 采用的是主键管理方式,数据和索引解耦,优势是不仅可做 page 内的碎片整理,还可以做 page 间的碎片整理( optimize table ),缺点是定位记录需在 BTree 中检索,然后在 page 内通过 directory 进行折半查找,定位效率低;
-
Oracle 的 page 组织方式上,记录之间没有强的依赖关系,插入操作可以并发执行,并发间竞争小。 MySQL 是索引组织的,插入操作竞争 page 的概率大,且对于每个插入操作而言,需要在 directory 中进行这边查找,且有可能发生 directory 目录重组,单次插入操作的平均成本也相对较高;
在extent层面,Oracle通过变长extent应对多样化的表大小,MySQL则通过frag extent来达到类似的效果。不过extent对block的管理上,Oracle做得更加精细,也更加准确。采用4个bit描述1个block,区分<25% free、25-50% free、50-75% free、>75% free。MySQL只使用了1个bit(虽然预留了2个bit),只能描述1个page满还是空。当然MySQL还有1个change buffer机制,为了获得好的缓存效果,也统计了page的空间情况,但也只区分了0 free、512 free、1024 free、2048 free,完全针对change buffer机制定制,在两处对page进行了各自的统计,并没有拉通设计。
在segment层面,Oracle采用三级位图进行管理,实际上L1 BMB已经达到block级,和extent对block的管理是拉通的。MySQL则通过3个双向链表FSEG_FREE、FSEG_NOT_FULL、FSEG_FULL和1个FSEG_FRAG_ADDR位图进行管理。实际上,Oracle刚开始也采用的链表方式管理,然后演进到位图方式。链表管理方式需要从链表头开始进行遍历,高并发下竞争非常明显,影响并发效率。当然MySQL在并发插入时瓶颈在索引组织表上,此处的瓶颈还没有显现出来。
在segment的使用上,两者都区分undo segment、temporary segment。Oracle将大字段(BLOB)放在独立的segment中,表数据和索引逻辑独立,所以也分别放在各自的segment中。MySQL是索引组织表,并将索引的叶子节点和非叶子节点分别放在2个不同的segement中。
在tablesapce层面,都区分permanent table、temporary tablespace、undo tablespace,两者没有太大的本质区别。