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 #段区块 #逻辑结构

相关推荐
敖行客 Allthinker21 分钟前
Redis 重回开源怀抱:开源精神的回归与未来展望
数据库·redis·开源
忘梓.1 小时前
从父类到子类:C++ 继承的奇妙旅程(1)
java·开发语言·数据库·c++
陆少枫3 小时前
MySQL基础关键_004_DQL(三)
数据库·mysql
焱焱枫5 小时前
深入理解 Oracle 数据块:行迁移与行链接的性能影响
数据库·oracle
长流小哥5 小时前
MySQL零基础入门:Ubuntu环境安装与操作精解
数据库·sql
临界点oc6 小时前
Redis从入门到实战 - 高级篇(上)
数据库·redis·缓存
电商数据girl9 小时前
【Python爬虫电商数据采集+数据分析】采集电商平台数据信息,并做可视化演示
java·开发语言·数据库·爬虫·python·数据分析
杨云龙UP10 小时前
SQL 中的中括号 [ ]、双引号 “ “、反引号 ` `:SQL Server、Oracle、MySQL三大数据库标识符 定界符 详解
数据库·sql·mysql·postgresql·oracle·sqlserver
qqxhb10 小时前
零基础学Java——第九章:数据库编程(三)
java·数据库·spring·mybatis