暂无图片
暂无图片
9
暂无图片
暂无图片
暂无图片

这是关于Oracle碎片的文章

原创 布衣 2025-07-03
645

碎片产生的原因

Oracle 对数据段的管理有一个高水位(HWM, High Water Mark)的概念。高水位是数据段中使用过和未使用过的数据块的分界线。高水位以下的数据块是曾使用过的,以上的是从未被使用或初始化过的。

当表或索引在表空间中频繁地进行DML操作时,表中空闲的数据块(高水位以下)无法被有效的利用,例如一些不连续地空间,无法被有效利用时。数据库的数据块就会产生碎片。

碎片的影响

碎片的产生会导致存储空间利用率低下,并且影响数据库的性能。

以全表扫描为例:

当 Oracle 进行全表扫描(FTS, Full table scan)的操作时,它会读高水位下的所有数据块。当高水位下还有很多空闲空间(碎片),读取这些空闲数据块会降低操作的性能。索引碎片同理(解决办法:重建索引+online)。

产生行链接和行迁移

  • 行链接 Row Chaining
    当插入数据量大的行的,如果一个Block不能存放一条记录,该记录的一部分会存储到同个Extent中的其他Block,这些block形成一个数据块链。
  • 行迁移 Row Migration
    当Update的时候导致记录长度增加了,存储的Block已经满了,就会发生行迁移。Oracle会迁移整行数据到一个能够存储下整行数据的Block中,迁移的原始指针指向新的存放行数据的Block,ROWID不变。

当数据行发生链接(chain)或迁移(migrate)时,对其访问将会造成 I/O 性能降低,因为Oracle为获取这些数据行的数据,必须访问更多的数据块(data block)。

  • 行链接&行迁移检测方案
-- 检测方案1: -- 执行脚本创建chained_rows表 @?/rdbms/admin/utlchain.sql -- ‌执行分析命令 ANALYZE TABLE 表名 LIST CHAINED ROWS INTO chained_rows; -- 确认存在行迁移/链接的记录 SELECT owner_name, table_name, head_rowid FROM chained_rows; -- 检测方案2: -- 分析表 exec dbms_stats.gather_table_stats(ownname=>'HR',tabname=> 'EMPLOYEES'); -- 查看 SELECT table_name, chain_cnt FROM user_tables WHERE table_name = 'EMPLOYEES'; TABLE_NAME CHAIN_CNT -------------- ---------- EMPLOYEES 891 --产生迁移
  • 解决‌行迁移‌&‌行链接‌思路
    ‌行迁移‌:由UPDATE导致记录超出块空间,需迁移数据(可优化PCTFREE解决)。
    ‌行链接‌:记录长度超块大小(如LOB字段),需调整块大小或拆分表结构

解决方案

1、支持在线操作-在线重定义(dbms_redefinition)

-- 1. 检查表是否可重定义 EXEC DBMS_REDEFINITION.CAN_REDEF_TABLE('SCOTT', 'EMPLOYEES'); -- 2. 创建中间表(结构需包含新列或修改后的定义) CREATE TABLE employees_interim AS SELECT * FROM employees WHERE 1=0; -- 3. 启动重定义 EXEC DBMS_REDEFINITION.START_REDEF_TABLE('SCOTT', 'EMPLOYEES', 'EMPLOYEES_INTERIM'); -- 4. 复制依赖对象(可选,不建议使用,结构手动创建更有把控性) -- 注意:复制的约束(如主键、唯一约束、外键)在中间表上默认处于 DISABLED 状态。这些约束会在 FINISH_REDEF_TABLE 切换完成后,在新表(原中间表)上自动启用; -- 索引会被创建,但可能需要在切换后重建以优化性能(尤其是改变了表结构或分区时) DECLARE l_errors NUMBER; BEGIN DBMS_REDEFINITION.COPY_TABLE_DEPENDENTS( uname => 'SCOTT', orig_table => 'EMPLOYEES', int_table => 'EMPLOYEES_INTERIM', copy_indexes => DBMS_REDEFINITION.cons_orig_params, copy_triggers => TRUE, ignore_errors => FALSE, num_errors => l_errors ); DBMS_OUTPUT.PUT_LINE('Errors: ' || l_errors); END; / -- 5. 同步数据 exec dbms_redefinition.sync_interim_table('SCOTT', 'EMPLOYEES', 'EMPLOYEES_INTERIM'); -- 6. 完成重定义 EXEC DBMS_REDEFINITION.FINISH_REDEF_TABLE('SCOTT', 'EMPLOYEES', 'EMPLOYEES_INTERIM');

2、不支持在线操作

1)表迁移

– 注:需手动重建索引和 LOB 字段的存储定义

-- 表迁移 ALTER TABLE table_name MOVE TABLESPACE new_tablespace; -- 索引重建 alter index index_name rebuild tablespace tablespace_name; -- 表中有LOB相关字段,使用如下命令迁移表。 alter table tb_name move tablespace tbs_name lob (col_lob1,col_lob2) store as(tablesapce tbs_name);
  • 导出导入操作
    用exp/imp(expdp/impdp)导出后,重新导入重建,在重新创建索引和重新收集统计信息。

2)Shrink 命令,需要表空间是基于自动段管理的。

  • 优化点:不需要索引重建
  • 半支持在线操作:整理数据不回收空间(可在线),回收空间会锁表(不支持在线)
-- 整理表,不影响DML操作 --启动行移动功能 alter table TABLE_NAME enable ROW MOVEMENT; --只整理碎片 不回收空间 alter table TABLE_NAME shrink space compact-- 重置高水位,此时不能有DML操作 -- 整理碎片并回收空间,并调整水位线。业务少时执行 alter table TABLE_NAME shrink space; --关闭行移动 alter table TABLE_NAME disable ROW MOVEMENT; -- 也可以一步到位:会锁表 alter table TABLE_NAME shrink space;

查看表碎片

set lines 199 pagesize 199 set COLSEP '|' SELECT table_name, ROUND ( (blocks * 8)/1024/1024, 2) "高水位空间 G", ROUND ( (num_rows * avg_row_len /1024)/1024/1024, 2) "真实使用空间 G", ROUND ( (blocks * 10 / 100) * 8/1024/1024, 2) "预留空间(pctfree) G", ROUND ( (blocks * 8 - (num_rows * avg_row_len / 1024) - blocks * 8 * 10 / 100)/1024/1024, 2) "浪费空间 G" FROM dba_tables WHERE temporary = 'N' and ROUND ( (blocks * 8)/1024/1024, 2)>10 ORDER BY 5 DESC;

查看表空间碎片

  • 1、查看fsfi值
-- 数字越小,表空间碎片较多,当小于30%的时候说明碎片程度很可观了。 set lines 199 pagesize 199 set COLSEP '|' select a.tablespace_name, trunc(sqrt(max(blocks)/sum(blocks))* (100/sqrt(sqrt(count(blocks)))),2) fsfi from dba_free_space a,dba_tablespaces b where a.tablespace_name=b.tablespace_name and b.contents not in('TEMPORARY','UNDO','SYSAUX') group by A.tablespace_name order by fsfi;
  • 2.查看dba_free_space
    dba_free_space 显示的是有free 空间的tablespace,如果一个tablespace的free 空间不连续,那每段free空间都会在dba_free_space中存在一条记录。如果一个tablespace 有好几条记录,说明表空间存在碎片,当采用字典管理的表空间碎片超过500就需要对表空间进行碎片整理。
set lines 199 pagesize 199 set COLSEP '|' SELECT a.tablespace_name, COUNT (1) 碎片量 FROM dba_free_space a, dba_tablespaces b WHERE a.tablespace_name = b.tablespace_name AND b.contents NOT IN ('TEMPORARY', 'UNDO', 'SYSAUX') GROUP BY a.tablespace_name HAVING COUNT (1) > 20 ORDER BY 2;

注:以上查询基于数据字典表,因为想要获取最新的数据需要对表或用户进行信息收集

收集统计信息

  • 收集表统计信息
SQL> exec dbms_stats.gather_table_stats(ownname=>'SCHEMA_NAME',tabname=> 'TABLE_NAME');
  • 整个 Schema 中对象的统计信息
SQL> exec dbms_stats.gather_schema_stats(ownname=>'SCHEMA_NAME');

测试Shrink命令整理表碎片的效果

  • 表及数据准备
SYS@phytest1:1411> create table t1 TABLESPACE TWO_DAT as select * from dba_objects; Table created. SYS@phytest1:1411> insert into t1 select * from t1; 76795 rows created. SYS@phytest1:1411> / 153590 rows created. ......(执行8次) SYS@phytest1:1411> commit; Commit complete. SYS@phytest1:1411> select count(*) from t1; COUNT(*) ---------- 9829760
  • 输出格式
SYS@phytest1:1411> set lines 199 pagesize 199 SYS@phytest1:1411> set COLSEP '|' SYS@phytest1:1411> set timing on
  • 收集T1表信息
SYS@phytest1:1411> exec dbms_stats.gather_table_stats(ownname=>'SYS',tabname=> 'T1'); PL/SQL procedure successfully completed. Elapsed: 00:00:13.20
  • 查看表碎片:113.88MB
    image.png
  • 删除数据
SYS@phytest1:1411> delete from T1 where object_type in('INDEX','TABLE','SYNONYM'); 4720384 rows deleted. Elapsed: 00:00:37.42 SYS@phytest1:1411> commit; Commit complete.
  • 再次分析表
SYS@phytest1:1411> exec dbms_stats.gather_table_stats(ownname=>'SYS',tabname=> 'T1'); PL/SQL procedure successfully completed. Elapsed: 00:00:13.20
  • 查看表碎片:555.04MB,增加了400+MB的碎片
    image.png
  • 查看执行计划
SYS@phytest1:1411> set autotrace trace SYS@phytest1:1411> select count(*) from t1 where object_type='TRIGGER';

image.png

  • 只整理数据不回空间
SYS@phytest1:1411> alter table t1 enable ROW MOVEMENT; Table altered. Elapsed: 00:00:00.01 SYS@phytest1:1411> alter table t1 shrink space compact; Table altered. Elapsed: 00:01:33.96
  • 再次分析表
SYS@phytest1:1411> exec dbms_stats.gather_table_stats(ownname=>'SYS',tabname=> 'T1'); PL/SQL procedure successfully completed. Elapsed: 00:00:13.20
  • 查看碎片情况
    image.png
  • 再次查询观察执行计划
    image.png
    以上结果证明,只整理数据不释放空间对查询没有影响,对于Insert会有提高,可以整理出连续空闲空间。
  • 回收空间
SYS@phytest1:1411> alter table t1 shrink space; Table altered. Elapsed: 00:00:00.84 SYS@phytest1:1411> alter table t1 disable ROW MOVEMENT; Table altered. Elapsed: 00:00:00.01
  • 再次收集
SYS@phytest1:1411> exec dbms_stats.gather_table_stats(ownname=>'SYS',tabname=> 'T1'); PL/SQL procedure successfully completed. Elapsed: 00:00:08.02
  • 查看碎片:仅剩:34.7MB
    image.png
  • 再次查看执行计划
    image.png
    以上执行计划对比,效率提升及逻辑读减少近1倍。

测试小结

  • 1、整理碎片不降低高水位释放空间的话,对于查询没有效果;
  • 2、仅整理数据,会将小碎片整理出连续的空间,理论上会对Insert 会有提升,未测试;
  • 3、Shrink 操作开启行移动,相当于对数据行进行移动整理,大表操作需要关注UNDO空间变化;
  • 4、通过上以操作测试对于碎片的整理个人推荐使用dbms_redefinition操作;

欢迎赞赏支持或留言指正
image.png

最后修改时间:2025-07-11 08:45:27
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论