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、查看表与段的关系
--创建用户并授权
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 中采用了默认延迟段的创建方式。也就是说,在创建表的时候,暂时先不分配段,待插入第一条数据的时候再进行创建。
我们对表插入一条数据,再次查询段的情况:
U1@pdb> insert into t1 values(1);1 row created.U1@pdb> commitU1@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个区。
插入更多的数据
--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 选项,可以不使用默认延迟段的创建方式,例如:
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 修改,下次启动生效。
SYS@pdb> show parameter deferred_segment_creationNAME 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 3T2 TABLE ASSM
USERS 65536 8 1T3 TABLE ASSM
USERS 65536 8 1
创建带主键的表,观察段的分配情况:
U1@pdb> create table t3(id number primary key) segment creation immediate;Table created.U1@pdb> select * from user_segments;这里可以看到表和主键上的索引都会分配单独的段
创建带大对象字段的表,观察段的分配情况:
U1@pdb> create table t4(id number,name clob) segment creation immediate;Table created.U1@pdb> select * from user_segments;
这里可以看到上面的语句,产生了段类型为TABLE及段类型为LOBSEGMENT的对象
2.2 延迟段分配在索引上的使用
--创建索引时指定不分配空间
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 个或者多个)
select * from dba_extents t where t.owner='U1' and t.segment_name='T1';
可以手工 allocate extent 提前分配 extent(用于需大量插入数据的表)。
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 83、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 数据全部删除,观察区的情况:
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 calls0 db block gets23 consistent gets0 physical reads0 redo size550 bytes sent via SQL*Net to client388 bytes received via SQL*Net from client2 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)1 rows processedSYS@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 calls0 db block gets23 consistent gets0 physical reads0 redo size549 bytes sent via SQL*Net to client388 bytes received via SQL*Net from client2 SQL*Net roundtrips to/from client0 sorts (memory)0 sorts (disk)1 rows processed4、查看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段的所有区。
也可以要求一个预分配的所需空间(但要注意,所需要的空间一定是在表空间可达到的空间范围内)
alter table u1.t5 allocate extent(datafile '/opt/oracle/oradata/ORCLCDB/ORCLPDB1/users01.dbf' size 8m);col SEGMENT_NAME for a30select 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 104857611 rows selected.
回收未使用的分区用 deallocate, 注意:只能收回从未使用的 extent
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 下不使用。
查看表的默认定义
-- 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 USERSpctfree 10initrans 1maxtrans 255storage(initial 64Knext 1Mminextents 1maxextents unlimited);
4.2 数据块的pctfree 和 pctused
接下来我们更加详细的了解下 pctfree 和 pctused
pctfree 这个参数定义了一个块保留空间的百分比,保留空间是为了将来可能发生的更新操作,因为更新可能增大被更新行占用的空间,如果此时该块没有可利用空间,那么只有发生 row migrate 了,从而会降低 I/O 性能。
换句话说,就是当一个块的利用率达到pctfree 的时候, oracle 就将该块从 freelist 中移除,不再向该块插入数据。所以说 pctfree是控制什么时候将块从 freelist 中移除的。
示例:
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 #段区块 #逻辑结构