
本文将对Oracle数据块(Data Block)与行记录(Row Piece)的内部存储格式进行系统梳理,并结合实际场景(如行链接与迁移)阐明其对性能的影响,旨在为数据库管理与性能优化提供底层参考。
一、数据块格式详解
无论数据块用于存储表、索引还是簇数据,其物理结构均遵循统一格式,如图2-2所示。一个数据块主要由以下部分构成:
头部区域
头部包含块的通用信息和可变信息。固定部分包括数据块地址(DBA)和段类型(如表段、索引段)等元数据。此部分与块内存储的具体数据内容无关。
表目录
如果数据块存储了多个表的数据(如簇表),此区域会记录拥有行数据的表信息。对于普通的堆表,此部分作用相对简单。
行目录
行目录是块内最重要的索引结构之一。它记录了块中每一行(或行片)在“行数据区”的物理位置(行地址),可以视作块内部的行指针数组。需要注意的是,一旦行目录的空间被分配,即使行被删除,这部分空间也不会自动回收。例如,一个曾包含50行记录而后被清空的块,其行目录可能仍占据约100字节的空间。这部分空间仅在新行插入时才会被重用。
开销
通常将数据块头部、表目录和行目录统称为“块开销”。块开销分为固定大小和可变大小两部分,总计通常在84到107字节之间。
行数据区
此区域用于存储实际的表或索引数据。行记录(或行片)依次存放于此。一个行记录可以跨多个数据块存储,这便是行链接。
补充:行链接与行迁移
当一行数据无法完整放入单个数据块时,会涉及跨块存储,主要有两种情况:
行链接:当一行数据在首次插入时就因过大(例如包含LONG、LARGE OBJECT类型字段)而无法放入单个块时,Oracle会将其存储在专为该段预留的一系列连续块中。这种情况难以避免。
行迁移:当一个最初能放入单块的行,因更新操作而增长,而当前块又无足够空闲空间时,Oracle会将整行数据移动(迁移)到一个新的、有足够空间的数据块中。在原块中会保留一个指向新块的“指针”(即迁移行片)。行迁移不会改变行的ROWID。
无论是行链接还是行迁移,由于检索一行数据需要访问多个物理块,都会导致针对该行的I/O性能下降,是数据库性能调优中需重点关注的方面。
二、行记录格式详解
Oracle将每一行数据(列数少于256)存储为一个或多个“行片”。若能整行放入一个块,则只有一个行片;否则,会跨多个块形成链式行片。
超255列的表:对于列数超过255的表,第255列之后的数据很可能在同一块内被链接存储,称为“块内链接”。所有行片仍位于同一块中,因此不会引入额外的I/O开销。
每个行片的格式,自前向后包含:
行头部
位于数据之前,至少占用3字节(对于完整的行片),包含关于行片本身、链式信息、行片中包含的列信息以及簇键(如适用)的元数据。
列数据
在行头部之后,依次存储每个非空列的数据。每一列的存储单元由两部分组成:
列长度:对于长度≤250字节的列,长度指示符占1字节;对于长度>250字节的列,则占3字节。
列值:列值本身占用的空间取决于其数据类型。可变长度类型(如VARCHAR2)的数据所占空间会随实际值变化。
空间优化机制:
对于值为NULL的列,Oracle仅存储其列长度(值为0),不存储任何实际数据。
特别地,对于行尾连续的NULL列,Oracle会做进一步优化:既不存储列值,也不存储其长度指示符,从而最大程度地节省存储空间。
OracleData Block FormatRow ChainingRow MigrationStorage Internals