Oracle 数据布局探秘:段与区块的内部机制

前言

在 Oracle 数据库的庞大架构中,数据存储的效率与性能是决定整个系统健康状况的关键因素。Oracle 采用了一套精妙的逻辑存储管理体系来组织和分配数据,其中,"段(Segment)"和"区(Extent)"是两个核心的逻辑单元。

它们直接关系到数据如何被物理地写入和读取,以及存储空间的利用效率。理解并掌握 Oracle 的段与区管理方式,对于数据库管理员优化性能、规划容量、避免空间问题至关重要。

本文将深入探讨 Oracle 的段管理机制,特别是围绕段和区这两个概念,解析传统管理方式的痛点以及现代自动化管理方法的优势,旨在为您揭示 Oracle 存储管理的奥秘。

一、段空间管理方式

1、自动管理方式(ASSM(Auto Segment Space Management))

复制代码
简单说就是采用位图管理方式,每个段的段头都有一组位图,位图描述每个块的满度,根据满度的不同将每个块登记到相应的位图上,位图自动跟踪每个块的使用空间,该位图不是精确的,而是按 25%的区间来分: 0-25%的空闲空间、 25%-50%的空闲空间、50%-75%的空闲空间和 75%-100%的空闲空间。

 比如块大小为 8k,你要插入一行是 4k 的表行,那么 oracle 就给你在满度 50%的位图上找个登记的块。ASSM 的前提是 EXTENT MANAGEMENT LOCAL,在 ORACLE9I 以后,缺省状态为自动管理方式, ASSM 废弃 pctused 属性。

2、手工管理方式(MSSM(Manual Segment Space Management))

这是传统的方法,采用 FREELIST 空闲列表管理段,现在仍然在使用,未被淘汰,保留 pctfree 和 pctused 属性,这些概念后面介绍 block 时再讨论。

二、段和表的关系

复制代码
一个单纯的表就分配一个段,但往往表没那么单纯,比如表上经常会有主键约束,那么就会有索引,索引有索引段,还有分区表,每个分区会有独立的段,再有就是 oracle 的大对象, 如果你的表里引用 blob,clob,那么这个表就又被分出多个段来。

2.1、查看表与段的关系

cpp 复制代码
--创建用户并授权
SYS@pdb> create user u1 identified by u1;

User created.

SYS@pdb> grant connect,resource to u1;

Grant succeeded.


SYS@pdb> grant select any dictionary to u1;

Grant succeeded.

2、创建表
U1@pdb>  create table t1(id number);

Table created.


3、查看段分配情况
U1@pdb> select segment_name,segment_type,segment_subtype,tablespace_name,bytes,blocks,extents from user_segments; 

no rows selected

这时候看到在查询段信息的时候, Oracle 没有分配段,原因是在19C 中采用了默认延迟段的创建方式。也就是说,在创建表的时候,暂时先不分配段,待插入第一条数据的时候再进行创建。

我们对表插入一条数据,再次查询段的情况:

cpp 复制代码
U1@pdb> insert into t1 values(1);

1 row created.

U1@pdb> commit

U1@pdb> select segment_name,segment_type,segment_subtype,tablespace_name,bytes,blocks,extents from user_segments; 

SEGMENT_NAME                   SEGMENT_TYPE       SEGMENT_SU TABLESPACE_NAME                     BYTES     BLOCKS    EXTENTS
------------------------------ ------------------ ---------- ------------------------------ ---------- ---------- ----------
T1                             TABLE              ASSM       USERS                               65536          8          1

可以看到,T1表分配一个1个区。

插入更多的数据

cpp 复制代码
--sys用户执行
SYS@pdb>  insert into u1.t1 select object_id from dba_objects where rownum<=10000;

10000 rows created.


--u1用户查看
U1@pdb> select segment_name,segment_type,segment_subtype,tablespace_name,bytes,blocks,extents from user_segments where segment_name='T1';

SEGMENT_NAME                   SEGMENT_TYPE       SEGMENT_SU TABLESPACE_NAME                     BYTES     BLOCKS    EXTENTS
------------------------------ ------------------ ---------- ------------------------------ ---------- ---------- ----------
T1                             TABLE              ASSM       USERS                              196608         24          3

可以看到,随着数据插入,T1表分配一个3个区。

另外,在建表时候可以带上 SEGMENT CREATION IMMEIDATE 选项,可以不使用默认延迟段的创建方式,例如:

cpp 复制代码
U1@pdb> create table t2(id number) segment creation immediate;

Table created.


U1@pdb> select segment_name,segment_type,segment_subtype,tablespace_name,bytes,blocks,extents from user_segments where segment_name='T2'; 

SEGMENT_NAME                   SEGMENT_TYPE       SEGMENT_SU TABLESPACE_NAME                     BYTES     BLOCKS    EXTENTS
------------------------------ ------------------ ---------- ------------------------------ ---------- ---------- ----------
T2                             TABLE              ASSM       USERS                               65536          8          1

延迟段的方式是由参数 deferred_segment_creation 来控制,默认值为 TRUE。也可通过更改此参数实现禁用延迟段。该参数可以在会话级别修改,也可以在 spfile 修改,下次启动生效。

cpp 复制代码
SYS@pdb> show parameter deferred_segment_creation

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
deferred_segment_creation            boolean     TRUE

--session级别修改deferred_segment_creation参数
SQL> alter session set deferred_segment_creation=false;

U1@pdb2> create table t3(id number) ;

Table created.

U1@pdb2> select segment_name,segment_type,segment_subtype,tablespace_name,bytes,blocks,extents from user_segments; 

SEGMENT_NAME                   SEGMENT_TYPE       SEGMENT_SU
------------------------------ ------------------ ----------
TABLESPACE_NAME                     BYTES     BLOCKS    EXTENTS
------------------------------ ---------- ---------- ----------
T1                             TABLE              ASSM
USERS                              196608         24          3

T2                             TABLE              ASSM
USERS                               65536          8          1

T3                             TABLE              ASSM
USERS                               65536          8          1

创建带主键的表,观察段的分配情况:

cpp 复制代码
U1@pdb> create table t3(id number primary key) segment creation immediate;

Table created.U1

@pdb>  select * from user_segments;

这里可以看到表和主键上的索引都会分配单独的段

创建带大对象字段的表,观察段的分配情况:

cpp 复制代码
U1@pdb> create table t4(id number,name clob) segment creation immediate;

Table created.

U1@pdb> select * from user_segments;
这里可以看到上面的语句,产生了段类型为TABLE及段类型为LOBSEGMENT的对象

2.2 延迟段分配在索引上的使用

cpp 复制代码
--创建索引时指定不分配空间
CREATE INDEX IDX_T1 ON T1(ID) UNUSABLE;

--查看索引段分配情况
select segment_name,segment_type,segment_subtype,tablespace_name,bytes,blocks,extents from user_segments; 
发现索引未分配空间

--将索引重建
 ALTER INDEX idx_t1 REBUILD;

--再次查看索引段分配情况
select segment_name,segment_type,segment_subtype,tablespace_name,bytes,blocks,extents from user_segments; 
发现索引分配空间

--将索引分配的空间回收
ALTER INDEX idx_t1 unusable;

三、区 extent

区是 ORACLE 进行存储空间分配的最小单位,是由一系列逻辑上连续的 Oracle 数据块组成的逻辑存储结构。段中第一个区叫初始区,随后分配的区叫后续区。

管理方式有两种:基于字典的管理(已淘汰)和基于本地管理。

3.1 表和区(extent)的关系

当建立表的时候建立段(segment),然后自动分配相应的 extent(1 个或者多个)

cpp 复制代码
select * from dba_extents t where t.owner='U1' and t.segment_name='T1';

可以手工 allocate extent 提前分配 extent(用于需大量插入数据的表)。

cpp 复制代码
1、u1用户下创建表t5,设置立即创建段
U1@pdb> create table u1.t5(id number) segment creation immediate;

Table created.

2、查看t5表分配的区
U1@pdb> select * from user_extents where segment_name='T5';
SEGMENT_NAME                   PARTITION_NAME                                                                                     SEGMENT_TYPE        TABLESPACE_NAME                 EXTENT_ID      BYTES     BLOCKS
------------------------------ -------------------------------------------------------------------------------------------------------------------------------- ------------------ ------------------------------ ---------- ---------- ----------
T5                                                                                                                                TABLE               USERS                                   0      65536          8

3、sys用户插入数据
SYS@pdb> insert into u1.t5 select object_id from dba_objects where rownum<=10000;
SYS@pdb> COMMIT;

Commit complete.

4、再次查看t5表分配的区
U1@pdb> select * from user_extents where segment_name='T5';
SEGMENT_NAME                   PARTITION_NAME                                                                                     SEGMENT_TYPE        TABLESPACE_NAME                 EXTENT_ID      BYTES     BLOCKS
------------------------------ -------------------------------------------------------------------------------------------------------------------------------- ------------------ ------------------------------ ---------- ---------- ----------
T5                                                                                                                                TABLE               USERS                                   0      65536          8
T5                                                                                                                                TABLE               USERS                                   1      65536          8
T5                                                                                                                                TABLE               USERS                                   2      65536          8

可以看到段T5的初始区 ID 为 0,大小为 65536 bytes, 向表段中插入数据,观察Oracle 为该段分配更多的区(extent) 。

3.2 区和表数据的关系

将表 T5 数据全部删除,观察区的情况:

cpp 复制代码
1、删除前,查看一下全表扫描T5所需要的块数
Execution Plan
----------------------------------------------------------
Plan hash value: 1231860717

-------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     7   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE    |      |     1 |            |          |
|   2 |   TABLE ACCESS FULL| T5   | 10000 |     7   (0)| 00:00:01 |
-------------------------------------------------------------------

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         23  consistent gets
          0  physical reads
          0  redo size
        550  bytes sent via SQL*Net to client
        388  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed


SYS@pdb>  delete from u1.t5;

10000 rows deleted.

SYS@pdb> commit;

Commit complete.

3、再次查看执行计划,仍然扫描和删除前一样的blocks
SYS@pdb> set autot trace
SYS@pdb> select count(1) from u1.t5;


Execution Plan
----------------------------------------------------------
Plan hash value: 1231860717

-------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     7   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE    |      |     1 |            |          |
|   2 |   TABLE ACCESS FULL| T5   | 10000 |     7   (0)| 00:00:01 |
-------------------------------------------------------------------

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         23  consistent gets
          0  physical reads
          0  redo size
        549  bytes sent via SQL*Net to client
        388  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

4、查看T5表区的信息
SYS@pdb> select segment_name,file_id,extent_id,blocks,bytes from dba_extents where segment_name='T5';

SEGMENT_NAME                      FILE_ID  EXTENT_ID     BLOCKS      BYTES
------------------------------ ---------- ---------- ---------- ----------
T5                                     12          0          8      65536
T5                                     12          1          8      65536
T5                                     12          2          8      65536

此时表T5段的数据已经删除,但所有 extent 依然健在,无法回收 T5段的所有区。

也可以要求一个预分配的所需空间(但要注意,所需要的空间一定是在表空间可达到的空间范围内)

cpp 复制代码
 alter table u1.t5 allocate extent(datafile '/opt/oracle/oradata/ORCLCDB/ORCLPDB1/users01.dbf' size 8m);


col SEGMENT_NAME for a30

select segment_name,file_id,extent_id,bytes from dba_extents where segment_name='T5';

SEGMENT_NAME                      FILE_ID  EXTENT_ID      BYTES
------------------------------ ---------- ---------- ----------
T5                                     12          0      65536
T5                                     12          1      65536
T5                                     12          2      65536
T5                                     12          3    1048576
T5                                     12          4    1048576
T5                                     12          5    1048576
T5                                     12          6    1048576
T5                                     12          7    1048576
T5                                     12          8    1048576
T5                                     12          9    1048576
T5                                     12         10    1048576

11 rows selected.

回收未使用的分区用 deallocate, 注意:只能收回从未使用的 extent

cpp 复制代码
SYS@pdb> alter table u1.t5 deallocate unused;

Table altered.

SYS@pdb>  select segment_name,file_id,extent_id,bytes from dba_extents where segment_name='T5';

SEGMENT_NAME                      FILE_ID  EXTENT_ID      BYTES
------------------------------ ---------- ---------- ----------
T5                                     12          0      65536
T5                                     12          1      65536
T5                                     12          2      65536

**思考:**为什么delete无法释放已经使用的区?如何释放这些实际上未使用的区?

**提示:**高水位线和shrink,关于这两个知识点,后面我们再详细介绍。

四、数据块 block

block是数据库内 I/O 最小单位,数据库需要读取数据时总是无法只读取一条记录,而是必须读整个数据块,只有在创建表空间时就能指定数据块容量,无法修改数据块容量。

BLOCK 的管理方法是区的管理和段管理的具体体现

自动管理方式(ASSM) :如创建表空间时区(extent)为本地管理方式,并且将段(segment)的存储空间方式设置为 AUTO(即 ASSM),该表空间的所有块均采用位图自动管理方式,这是系统默认的。

空闲列表方式(MSSM):引入 FREELIST 概念,以及 PCTFREE 和 PCTUSED 两个参数控制可用存储区的大小,避免行迁移现象的发生。这两个参数可在创建表空间时设置,也可在建立数据库的模式对象(表,索引)中设置。模式对象中设置的优先级比表空间的要高。就是说;如表和索引中没有设置,则按表空间的设置,如表空间也没设置,则按自动管理方式管理块。

引用一下ORACLE官方文档中块的格式

4.1 数据块的管理方式

freelist:空闲列表中登记了可以插入数据的可用块,位置在段头,插入表行数据时首先查找该列表。

pctfree:用来为一个块保留的空间百分比,以防止在今后的更新操作中增加一列或多列值的长度。达到该值,从 freelist 清除该块信息。

pctused:一个块的使用水位的百分比,这个水位将使该块返回到可用列表中去等待更多的插入操作。达到该值,该块信息加入 freelist,这个参数在 ASSM 下不使用。

查看表的默认定义

cpp 复制代码
-- Create table
create table ROW_MIG_DEMO
(
  x INTEGER not null,
  a CHAR(1000),
  b CHAR(1000),
  c CHAR(1000),
  d CHAR(1000),
  e CHAR(1000)
)
tablespace USERS
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64K
    next 1M
    minextents 1
    maxextents unlimited
  );

4.2 数据块的pctfree 和 pctused

接下来我们更加详细的了解下 pctfree 和 pctused

pctfree 这个参数定义了一个块保留空间的百分比,保留空间是为了将来可能发生的更新操作,因为更新可能增大被更新行占用的空间,如果此时该块没有可利用空间,那么只有发生 row migrate 了,从而会降低 I/O 性能。

换句话说,就是当一个块的利用率达到pctfree 的时候, oracle 就将该块从 freelist 中移除,不再向该块插入数据。所以说 pctfree是控制什么时候将块从 freelist 中移除的。

示例:

cpp 复制代码
CREATE TABLE test_table(n NUMBER) PCTFREE 20;

pctused 这个参数控制一个块什么时候被重新启用来插入数据,例如当一个块达到 pctfree 利用率的时候, oracle 停止向该块插入数据,同时从 freelist 移除该块,但是后来发生一些删除操作,使得该块的利用率下降,当该块的利用率降到 pctused 以下的时候该块就被重新启用来插入数据了,也即是将该块重新加入到 freelist 列表中,所以说 pctused是控制什么时候将一个块重新加入 freelist 的。

**举个例子:**比如一个块的 pctfree 定为 10%, pctused 定为 40%(oracle 默认设置)。那么一个块的使用率达到 90%(即 1-10%)的时候, oracle 将该块从 freelist 中移除,停止使用该块来插入数据(但可更新)。

后来该块上发生了一些删除操作,使得该块的利用率下降,当使用率下降到 40%以下的时候, oracle 重新将该块加入 freelist,可用于新的插入。

在 Oracle11g 中,表空间默认使用本地位图自动管理, PCTFREE 的默认值是 10,且无法自定义管理 PCTUSED 属性。

结语

通过本文的探讨,我们深入了解了 Oracle 数据库中段和区这两个核心逻辑存储单元的角色及其管理方式。从早期依赖手动参数的复杂时代,到如今广泛采用的自动化段空间管理(ASSM)和本地管理区(LME),Oracle 的存储管理机制经历了显著的演进,旨在提供更高效、更简便、更少出错的管理体验。

特别是 ASSM 基于位图的精细化空间管理和 LME 避免数据字典争用的优势,使其成为现代 Oracle 数据库存储的最佳实践。正确地配置和利用 ASSM 和 LME(尤其是 AUTOALLOCATE 模式),不仅能极大地简化数据库管理员的工作,更能有效提升数据库的整体性能、优化空间利用率、减少碎片,从而确保数据库系统的稳定运行和持续高效。

掌握这些知识,是每一位 Oracle 数据库从业者不可或缺的技能。

#数据库 #oracle #段区块 #逻辑结构

相关推荐
TDengine (老段)5 分钟前
TDengine 时间函数 TODAY() 用户手册
大数据·数据库·物联网·oracle·时序数据库·tdengine·涛思数据
码界奇点15 分钟前
KingbaseES一体化架构与多层防护体系如何保障企业级数据库的持续稳定与弹性扩展
数据库·架构·可用性测试
悟乙己37 分钟前
数据科学家如何更好地展示自己的能力
大数据·数据库·数据科学家
皆过客,揽星河1 小时前
mysql进阶语法(视图)
数据库·sql·mysql·mysql基础语法·mysql进阶语法·视图创建修改删除
tuokuac2 小时前
Redis 的相关文件作用
数据库·redis·缓存
鹧鸪云光伏与储能软件开发3 小时前
投资储能项目能赚多少钱?小程序帮你测算
运维·数据库·小程序·光伏·光伏设计软件·光伏设计
2301_779503764 小时前
MySQL主从同步--主从复制进阶
数据库·mysql
beijingliushao4 小时前
58-正则表达式
数据库·python·mysql·正则表达式
诗句藏于尽头5 小时前
DJANGO后端服务启动报错及解决
数据库·笔记·django
手握风云-5 小时前
MySQL数据库精研之旅第十五期:索引的 “潜规则”(下)
数据库