Oracle数据库存储结构:逻辑存储结构(一)

Oracle数据库存储结构:逻辑存储结构

  • 逻辑存储架构简介
    • 逻辑存储等级
    • 逻辑空间管理
  • 数据块
    • 数据块vs操作系统块
    • 数据块格式
    • 数据块压缩
    • 数据块空间管理
    • 索引块

本文所讨论的内容涉及的数据库版本为 Oracle 19c。

逻辑存储架构简介

Oracle数据库为数据库中的所有数据分配逻辑空间。数据库分配空间的逻辑单位是数据块(data blocks)、区(extents)、段(segments)和表空间(tablespaces)。从物理层面来说,数据都存储在磁盘上的数据文件中。而数据文件中的数据存储在操作系统块中。

图1 逻辑存储对应物理存储
Oracle数据库存储结构:逻辑存储结构(一)_第1张图片

逻辑存储等级

一个段包含一个或多个区,而一个区包含多个数据块。下图展示了一个表空间内部数据块、区、段之间的等级关系。

图2 表空间内的段、区、数据块
Oracle数据库存储结构:逻辑存储结构(一)_第2张图片

从最低粒度到最高粒度,Oracle数据库存储数据的逻辑结构如下:

  • 数据块:是Oracle数据库中最小的逻辑存储单元。一个逻辑数据块对应一定字节(bytes)的物理磁盘空间,比如 2KB。数据块是Oracle数据块可以分配和使用的最小存储单元。
  • :是逻辑上相邻的一组数据块的集合。在图2中,一个24KB的区包含了12个2KB的数据块,而另一个72KB的区则包含了36个数据块。
  • :是为特定数据库对象(比如)分配的一组区的集合。比如,一个表的数据存放在自己的数据段(data segment)中,而每个索引都存放在自己的索引段(index segment)中。每个占用存储的数据库对象都仅由一个段组成一个段可以跨越一个或多个数据文件,但是不能跨越多个表空间
  • 表空间:是包含一个或多个段的数据库存储单元。每个段属于且仅属于一个表空间。因此,一个段的所有区都存储在同一个表空间中。一个段可以包含多个不同数据文件中的区。但是单个区不能跨越数据文件

逻辑空间管理

Oracle数据必须使用逻辑空间管理来跟踪和分配表空间中的区。当有数据库对象请求一个区时,数据库必须有办法找到并提供该区;当一个区不再被需要时,数据库也必须有办法释放该区使其可用。

Oracle数据库使用下面的办法来管理表空间内的空间:

  • 本地管理的表空间(默认):数据库使用表空间内的位图(bitmaps)来管理区。因此,使用本方法时,需要拿出一部分空间用于存放位图。在表空间内,数据库可以使用段空间自动管理(ASSM)或者手动管理(MSSM)来管理段。
  • 字典管理的表空间:数据库使用数据字典来管理区。

#1 本地管理的表空间
本地管理(locally managed)的表空间中,在数据文件头(header)会维护一个位图,用以跟踪数据文件中空闲的和已使用的空间。每个比特都对应一组数据块。当有空间被分配或者释放时,Oracle数据块会修改位图的值来更新数据块的状态。其中,1表示空间已被使用,0表示空闲。

使用本地管理的表空间有以下几点好处:

  1. 避免使用数据字典来管理区:使用数据字典管理区时,如果使用或者释放区空间引起了数据字典中表或undo段的使用或释放操作,就可能发生递归行为,影响数据库性能。
  2. 自动跟踪相邻的可用空间。
  3. 自动确定本地管理的区的大小。

在本地管理的表空间中,段空间可以自动或者手动管理。段空间自动管理(automatic segment space management, ASSM)也是使用位图来实现的。段空间手动管理(manual segment space management, MSSM)则使用一个被称为 free list 的链表结构来管理空闲的段。

#2 字典管理的表空间
该方法使用数据字典(data dictionary)来管理区。当有区被分配或者被释放时,Oracle数据库会更新数据字典中的表。

数据块

Oracle数据库中的数据块又被称为Oracle块(Oracle block)或者页(page)。数据块是数据块 I/O 的最小单位。

数据块vs操作系统块

在物理层面,数据库的数据都存储在由操作系统块(operating system blocks)组成的磁盘文件中。操作系统块是操作系统可以读写的最小数据单元。而Oracle块则是一个逻辑存储结构,它的大小和结构对于操作系统而言是未知的。

图3 数据块vs操作系统块
Oracle数据库存储结构:逻辑存储结构(一)_第3张图片

当数据库请求一个数据块时,操作系统将该操作翻译为请求永久存储中的数据。数据块与操作系统块在逻辑上的分割有如下影响:

  • 应用无需决定磁盘数据的物理地址;
  • 数据库数据可以在多个物理磁盘上条带化(striped)或镜像化(mirrored)。

#1 数据库块大小
每个数据库都有一个数据库块大小。在创建数据库时,初始化参数 DB_BLOCK_SIZE 设定了数据库的数据块大小。这个参数值对 SYSTEMSYSAUX 表空间生效,同时也是其他所有表空间的默认值。只有重新创建数据块,才能改变该参数的大小。

如果 DB_BLOCK_SIZE 的值未设定,那么其值取决于操作系统。一般是 4KB 或者 8KB,且必须是操作系统块的倍数(或者相等)。

#2 表空间块大小
你也可以创建数据块大小不同于 DB_BLOCK_SIZE 的单独的表空间。非标准大小的表空间可以用于将表空间迁移到其他平台。

数据块格式

每个数据块都有能够使数据库跟踪其中数据及其可用空间的内部结构或格式。且无论数据块是否包含表、索引或集群数据时,其格式都是相似的。下面展示了未压缩数据块的格式。

图4 数据块格式
Oracle数据库存储结构:逻辑存储结构(一)_第4张图片

#1 数据块头部
Oracle数据块使用块头部(Block overhead)来管理数据块本身。块头部中有些部分的大小是固定的,但是块头部的总大小是可变的。平均来说,块头部一般总共占用84字节到107字节的空间。块头部不用于存储用户数据。块头部包含以下部分:

  • 数据块头(Block header):存有数据块的基本信息,包括磁盘地址、段的类型。对于事务管理(transaction-managed)的数据块,header部分还存有活跃和历史事务信息。
    每个更新数据块的事务都需要一条事务记录(transaction entry)。Oracle数据库在数据块头中为事务记录预留了空间。在header部分空间耗尽时,空闲空间(Free space)也可以用来存储事务记录。大多数操作系统中,数据块中事务记录所需的空间大致为23 bytes。

  • 表目录(Table directory):
    对一个堆组织(heap-organized)的表而言,表目录包含了关于在该数据块中存有行数据的表的元数据信息。在表集群(table cluster)中,多个表可以将行数据存在同一个数据块中。

  • 行目录(Row directory):
    对一个堆组织的表而言,行目录描述了行数据在数据块中的位置。rowid指向一个特定的文件、数据块以及行编号(row number)。其中,行编号是行目录中一条记录(entry)的索引。行目录的记录中包含了一个指向数据块中行数据位置的指针(pointer)。如果数据库移除了数据块中的一条行数据,就会更新行目录中的记录来修改对应的指针。而rowid一直是保持不变。
    数据库在行目录中分配了空间后,并不会回收已删除行对应的行目录空间。仅在有会话在数据块中插入新的行时,数据库才会重用该行目录空间。

#2 行数据格式
数据块的行数据(Row data)部分包含了真正的数据,例如表的行或者索引键记录。行数据也有自己的内部格式。Oracle数据将行数据存为长度可变的记录。一条行数据被存储在一个或多个 row piece 中。每个 row piece 都有一个行头(row header)和列数据(column data)。

图5 Row piece格式
Oracle数据库存储结构:逻辑存储结构(一)_第5张图片

Row header
Oracle数据库利用row header来管理数据块中的row piece。行头中包含以下部分:

  • row piece中的列;
  • 位于其他数据块中的行的片(pieces),当一条行数据存储在多个row pieces中时;
  • 表集群的集群键(cluster keys)。

一条完全存储在一个数据块中的行至少包含3个字节大小的row header。

Column data
列数据中存储了行中的真正的每一列的数据部分。Row piece通常按照 CREATE TABLE 语句中的顺序来存储列数据(但是并不保证完全按这个顺序)。比如,long类型的列数据通常最后被创建。

正如图5所示,对于row piece中的每个列,Oracle数据库会分开存储列的长度(column length)和列数据本身。每条行数据在数据块header的行目录中都对应有一个插槽(slot)。这个插槽会指向行数据的开头。

Rowid 格式
Oracle数据块使用 rowid 来唯一地标识一行。Rowid是一个包含了数据库可以用来访问一行数据的信息的结构。Rowid并不是以物理形式存储在磁盘上的,而是通过存储数据的文件和数据块推断出来的。

扩展的rowid包含了数据对象编号(data object number)。这种类型的rowid使用64位编码每行的物理地址,可用的编码字符包括 A-Z、a-z、0-9、+和/。以 OOOOOOFFFBBBBBBRRR 为例,可以分为以下四部分:

  • OOOOOO数据对象编号用于标识段(segment)。每个数据库段都会被分配一个数据对象编号。在同一个段中的schema对象都会有相同的数据对象编号。
  • FFF:表空间相关的数据文件编号用于标识存有行数据的数据文件。
  • BBBBBB数据块编号用于标识存有行数据的数据块。数据块编号与数据文件有关,而不是表空间。因此,两条有相同数据块编号的行数据可以存储在同一个表空间的不同数据文件中。
  • RRR行编号用于标识数据块中的行。

Rowid在被分配给一个row piece之后,在特殊情形下可以被修改。

数据块压缩

数据库可以使用表压缩(table compression)来消除数据块中的重复值。使用了压缩技术的数据块的内部结构与未压缩的数据块大致相同,区别在于:在压缩的数据块中会有一个符号表(symbol table),用于存储重复的行和列的数据值。数据库会把出现这些重复值的地方用符号表中的对应符号替换。

数据块空间管理

当数据库从数据块底部开始填充数据时,block header和row data之间的可用空间就会越来越少。数据库可以通过管理数据块中的可用空间来提升性能、避免空间浪费。

#1 数据块中的可用空间
SQL参数 PCTFREE 设定了在数据块中为更新现有行数据而保留的可用空间的最小百分比。PCTFREE 对于防止行迁移和避免空间浪费来说非常重要。

假设,你通过 CREATE TABLE 语句设定了 PCTFREE 的值:

CREATE TABLE test_table (n NUMBER) PCTFREE 20;

随着数据库向数据块中添加行,底部的行数据和顶部的块头部向中间靠拢,导致可用空间逐渐减少。而PCTFREE参数会保证至少有20%的数据块空间可用(free)。比如,如果有一个INSERT操作会导致可用空间减少到10%,那么数据库会阻止该插入操作。

#2 可用空间的优化
一些DML操作可以增加数据块中可用空间的大小。另一方面,被释放的空间在数据块中与可用空间的主要区域也许并不是相邻的,通过合并这些碎片化的空间也可以优化可用空间。

以下的SQL操作可以增大可用空间:

  • DELETE语句;
  • UPDATE语句,如果将现有值更新为更小的值、或者增大现有值致使发生行迁移;
  • INSERT语句,如果导致插入的数据块发生了行数据压缩。

仅仅当满足以下条件时,Oracle数据库会自动地、透明地合并(coalesce)数据块中的可用空间:

  • 有INSERT或UPDATE操作尝试使用一个包含了足够可用空间的数据块来存储一个新的row piece。
  • 可用空间处于碎片化(fragmented)状态且新加入的row piece不能被连续地(contiguous)插入到数据块。

#3 行链接&行迁移
Oracle数据库使用行链接行迁移来管理太大而无法放到单个数据块中的行。以下情形有可能发生:

  1. 行太大而无法(首次)插入到单个数据块中。
    在行链接(row chaining)中,Oracle数据库将行数据存储在由一个或多个数据块构成的链结构中。例如,当行内包含了 LONG 或者 LONG RAW 数据类型的列,或者当行内包含了非常多的列,这类情况下行链接就是无法避免的。

Oracle数据库存储结构:逻辑存储结构(一)_第6张图片

  1. 已经存到数据块中的行,被更新后整体长度增加,现存的可用空间不足以容纳更新后的行。
    在行迁移(row migration)中,Oracle数据库会将整个行迁移到一个新的(能够容纳该行)数据块中。迁移之前的原有row piece中则会包含一个指向新的(存有被迁移行)数据块的指针。被迁移行的rowid不会发生变化

Oracle数据库存储结构:逻辑存储结构(一)_第7张图片

  1. 行内有超过255列数据。
    Oracle数据库在一个row piece中仅仅能够存储255列数据。如果你插入一个包含1000列数据的表,那么数据库就会创建4个row pieces,且通常位于多个链接的数据块中。

索引块

索引块(index block)是一种特殊的数据块,与表数据块(table blocks)管理空间的方式不同。

#1 索引块分类
一个索引包括一个根块(root block)、枝块(branch blocks)和叶块(leaf blocks)。

  • Root块:标识索引的入口点(entry point);
  • Branch块:数据库在搜索索引键时会浏览branch块;
  • Leaf块:存储了指向对应行数据的索引键值rowid。Leaf块会按顺序存储键值,从而提高了数据库搜索的效率。

#2 索引记录存储
索引记录(index entries)存储在索引块中的方式与表行存储在数据块中的方式相同。

数据库管理索引块中行目录的方式不同于数据块中的行目录。行目录中的记录按键值排序。比如,索引键 000000 在行目录中的记录排在索引键 111111 对应的记录之前。这么做可以提高索引扫描的效率。

相对于一个堆组织的表数据块而言,一个索引块中通常包含更多的行。这使得数据库更加容易管理索引,因为避免了为了容纳新数据而可能导致的索引块的频繁分裂。

References
[1] https://docs.oracle.com/en/database/oracle/oracle-database/19/cncpt/logical-storage-structures.html#GUID-13CE5EDA-8C66-4CA0-87B5-4069215A368D

你可能感兴趣的:(Oracle理论篇,oracle,数据库)