Oracle 分区表降低高水位(HWM)笔记

一、高水位线(HWM)基础

1. 定义

Oracle 中每个段(segment)都有容纳数据的上限即高水位线(HWM),HWM 表示段中未被使用的数据块数量,仅上涨不下跌(truncate 操作除外),即使删除表中全部数据,HWM 仍保持原值。

2. 影响

  • 全表扫描会读取 HWM 以下所有数据块,即使无数据,导致性能低下;
  • 插入数据使用 append 关键字时,会使用 HWM 以上数据块并自动抬高 HWM。

二、通用降低高水位线的方法

方法 语法 注意事项
表移动 alter table table_name move [tablespace tablespace_name]; 批量生成脚本: `select 'alter index
表收缩 alter table table_name enable row movement; alter table table_name shrink space; 需先开启行移动 对分区表可按分区收缩
表重建 create table t1 as select * from t; drop table t; alter table t1 rename to t; 需重新创建索引、约束等
emp/imp - 逻辑导出导入,重建表结构
释放未使用空间 alter table table_name deallocate unused; 仅释放 HWM 以上空间,无法释放以下空闲空间
truncate truncate table_name; 会清空表数据,重置 HWM,但数据不可恢复

三、分区表降低高水位线实操

场景1:使用 shrink space 收缩

1. 环境模拟
sql 复制代码
-- 创建分区表
create table scott.t_par
(id number,
inc_datetime varchar2(19),
random_id number,
random_string varchar2(60)
) 
partition by range (id) 
(
partition p_01 values less than(200000),
partition p_02 values less than(400000),
partition p_03 values less than(600000),
partition p_04 values less than(800000),
partition p_05 values less than(1000000),
partition p_max values less than(maxvalue)
);

-- 插入测试数据
insert into t_par
select rownum as id,
to_char(sysdate + rownum/24/3600, 'yyyy-mm-dd hh24:mi:ss') as inc_datetime,
trunc(dbms_random.value(0, 100)) as random_id,
dbms_random.string('x', 20) random_string
from dual
connect by level <= 1500000;
commit;

-- 查询数据分布
select count(*) from t_par partition(p_01);
select count(*) from t_par partition(p_02);
select count(*) from t_par partition(p_03);
select count(*) from t_par partition(p_04);
select count(*) from t_par partition(p_05);
select count(*) from t_par partition(p_max);

-- 收集统计信息
exec DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'T_PAR',ESTIMATE_PERCENT=>dbms_stats.auto_sample_size,method_opt=>'for all indexed columns',cascade=>true,force=>true,degree=>10);

-- 查看高水位
select table_name,partition_name,
ROUND ( (blocks * 8), 2) "高水位空间 k",
ROUND ( (num_rows * avg_row_len / 1024), 2) "真实使用空间 k",
ROUND ( (blocks * 10 / 100) * 8, 2) "预留空间(pctfree) k",
ROUND ( (blocks * 8 - (num_rows * avg_row_len / 1024) - blocks * 8 * 10 / 100),2) "浪费空间 k"
from dba_tab_partitions where lower(table_name)='t_par'
ORDER BY 5 DESC;
2. 模拟高水位(删除偶数ID数据)
sql 复制代码
delete from t_par where mod(id,2) = 0;
commit;

-- 再次收集统计信息并查看高水位
exec DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'T_PAR',ESTIMATE_PERCENT=>dbms_stats.auto_sample_size,method_opt=>'for all indexed columns',cascade=>true,force=>true,degree=>10);

select table_name,partition_name,
ROUND ( (blocks * 8), 2) "高水位空间 k",
ROUND ( (num_rows * avg_row_len / 1024), 2) "真实使用空间 k",
ROUND ( (blocks * 10 / 100) * 8, 2) "预留空间(pctfree) k",
ROUND ( (blocks * 8 - (num_rows * avg_row_len / 1024) - blocks * 8 * 10 / 100),2) "浪费空间 k"
from dba_tab_partitions where lower(table_name)='t_par'
ORDER BY 5 DESC;
3. 降低高水位
sql 复制代码
-- 开启行移动
alter table t_par enable row movement;

-- 按分区收缩
alter table t_par modify partition p_01 shrink space cascade; 
alter table t_par modify partition p_02 shrink space cascade;
alter table t_par modify partition p_03 shrink space cascade;
alter table t_par modify partition p_04 shrink space cascade;
alter table t_par modify partition p_05 shrink space cascade;
alter table t_par modify partition p_max shrink space cascade;

-- 收集统计信息后查看高水位
exec DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'T_PAR',ESTIMATE_PERCENT=>dbms_stats.auto_sample_size,method_opt=>'for all indexed columns',cascade=>true,force=>true,degree=>10);

select table_name,partition_name,
ROUND ( (blocks * 8), 2) "高水位空间 k",
ROUND ( (num_rows * avg_row_len / 1024), 2) "真实使用空间 k",
ROUND ( (blocks * 10 / 100) * 8, 2) "预留空间(pctfree) k",
ROUND ( (blocks * 8 - (num_rows * avg_row_len / 1024) - blocks * 8 * 10 / 100),2) "浪费空间 k"
from dba_tab_partitions where lower(table_name)='t_par'
ORDER BY 5 DESC;

场景2:使用 move tablespace 移动

1. 环境模拟(含索引创建)
sql 复制代码
-- 创建分区表(同场景1)
create table scott.t_par
(id number,
inc_datetime varchar2(19),
random_id number,
random_string varchar2(60)
) 
partition by range (id) 
(
partition p_01 values less than(200000),
partition p_02 values less than(400000),
partition p_03 values less than(600000),
partition p_04 values less than(800000),
partition p_05 values less than(1000000),
partition p_max values less than(maxvalue)
);

-- 插入测试数据(同场景1)
insert into t_par
select rownum as id,
to_char(sysdate + rownum/24/3600, 'yyyy-mm-dd hh24:mi:ss') as inc_datetime,
trunc(dbms_random.value(0, 100)) as random_id,
dbms_random.string('x', 20) random_string
from dual
connect by level <= 1500000;
commit;

-- 创建全局索引和本地索引
-- 全局索引
create index idx_t_par on t_par(random_id) global 
partition by range (random_id) 
(
partition p_01 values less than(200000),
partition p_02 values less than(400000),
partition p_03 values less than(600000),
partition p_04 values less than(800000),
partition p_05 values less than(1000000),
partition p_max values less than(maxvalue)
);

-- 本地索引
create index idx_t_par_2 on t_par(random_string) local (partition p01,partition p02,partition p03,partition p04,partition p05,partition p_max);

-- 查询数据分布、收集统计信息、查看索引/高水位(同场景1)
select count(*) from t_par partition(p_01);
select count(*) from t_par partition(p_02);
select count(*) from t_par partition(p_03);
select count(*) from t_par partition(p_04);
select count(*) from t_par partition(p_05);
select count(*) from t_par partition(p_max);

exec DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'T_PAR',ESTIMATE_PERCENT=>dbms_stats.auto_sample_size,method_opt=>'for all indexed columns',cascade=>true,force=>true,degree=>10);

-- 查看索引统计信息
select t2.table_name,
       t1.index_name,
       t1.partition_name,
       t1.last_analyzed,
       t1.blevel,
       t1.num_rows,
       t1.leaf_blocks,
       t1.status
  from dba_ind_partitions t1, user_indexes t2
 where t1.index_name = t2.index_name
   and t2.table_name = 'T_PAR';

-- 查看高水位
select table_name,partition_name,
ROUND ( (blocks * 8), 2) "高水位空间 k",
ROUND ( (num_rows * avg_row_len / 1024), 2) "真实使用空间 k",
ROUND ( (blocks * 10 / 100) * 8, 2) "预留空间(pctfree) k",
ROUND ( (blocks * 8 - (num_rows * avg_row_len / 1024) - blocks * 8 * 10 / 100),2) "浪费空间 k"
from dba_tab_partitions where lower(table_name)='t_par'
ORDER BY 5 DESC;
2. 模拟高水位(删除偶数ID数据)
sql 复制代码
delete from t_par where mod(id,2) = 0;
commit;

-- 收集统计信息并查看高水位
exec DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'T_PAR',ESTIMATE_PERCENT=>dbms_stats.auto_sample_size,method_opt=>'for all indexed columns',cascade=>true,force=>true,degree=>10);

select table_name,partition_name,
ROUND ( (blocks * 8), 2) "高水位空间 k",
ROUND ( (num_rows * avg_row_len / 1024), 2) "真实使用空间 k",
ROUND ( (blocks * 10 / 100) * 8, 2) "预留空间(pctfree) k",
ROUND ( (blocks * 8 - (num_rows * avg_row_len / 1024) - blocks * 8 * 10 / 100),2) "浪费空间 k"
from dba_tab_partitions where lower(table_name)='t_par'
ORDER BY 5 DESC;
3. 降低高水位(move + 重建索引)
sql 复制代码
-- 生成并执行分区move脚本
select 'alter table '||TABLE_OWNER||'.'||TABLE_NAME||' move partition '||PARTITION_NAME|| ' tablespace SCOTT; ' from DBA_tab_partitions WHERE TABLE_OWNER='SCOTT' and TABLE_NAME='T_PAR';

alter table SCOTT.T_PAR move partition P_MAX tablespace SCOTT; 
alter table SCOTT.T_PAR move partition P_05 tablespace SCOTT; 
alter table SCOTT.T_PAR move partition P_04 tablespace SCOTT; 
alter table SCOTT.T_PAR move partition P_03 tablespace SCOTT; 
alter table SCOTT.T_PAR move partition P_02 tablespace SCOTT; 
alter table SCOTT.T_PAR move partition P_01 tablespace SCOTT; 

-- 查看索引状态(已失效)
select t2.table_name,
       t1.index_name,
       t1.partition_name,
       t1.last_analyzed,
       t1.blevel,
       t1.num_rows,
       t1.leaf_blocks,
       t1.status
  from dba_ind_partitions t1, user_indexes t2
 where t1.index_name = t2.index_name
   and t2.table_name = 'T_PAR';

-- 重建索引(区分分区/非分区索引)
begin
  for c1 in (select t.index_name, t.partitioned from user_indexes t where table_name = 'T_PAR')
  loop
    if c1.partitioned='NO' then
      -- 重建非分区全局索引
      execute immediate 'alter index ' || c1.index_name || ' rebuild';
    else
      -- 重建分区索引的失效分区
      for c2 in (select partition_name from user_ind_partitions where index_name=c1.index_name and status = 'UNUSABLE')
      loop
        execute immediate 'alter index ' || c1.index_name || ' rebuild partition ' || c2.partition_name;
      end loop;
    end if;
  end loop;
end;
/

-- 如有LOB字段,单独move
-- alter table BDHI.TEST move lob(A) store as (tablespace IDWD_TBS002);

-- 收集统计信息
exec DBMS_STATS.GATHER_TABLE_STATS(ownname=>'SCOTT',tabname=>'T_PAR',ESTIMATE_PERCENT=>dbms_stats.auto_sample_size,method_opt=>'for all indexed columns',cascade=>true,force=>true,degree=>10);

-- 查看索引状态和高水位
select t2.table_name,
       t1.index_name,
       t1.partition_name,
       t1.last_analyzed,
       t1.blevel,
       t1.num_rows,
       t1.leaf_blocks,
       t1.status
  from dba_ind_partitions t1, user_indexes t2
 where t1.index_name = t2.index_name
   and t2.table_name = 'T_PAR';

select table_name,partition_name,
ROUND ( (blocks * 8), 2) "高水位空间 k",
ROUND ( (num_rows * avg_row_len / 1024), 2) "真实使用空间 k",
ROUND ( (blocks * 10 / 100) * 8, 2) "预留空间(pctfree) k",
ROUND ( (blocks * 8 - (num_rows * avg_row_len / 1024) - blocks * 8 * 10 / 100),2) "浪费空间 k"
from dba_tab_partitions where lower(table_name)='t_par'
ORDER BY 5 DESC;

四、关键注意事项

  1. shrink space :需开启行移动,对分区表可按分区操作,不影响索引(cascade 会同步收缩索引);
  2. move tablespace:移动后索引失效,需重建(分区索引需按分区重建);有 LOB 字段需单独移动;
  3. 统计信息:操作后需收集统计信息,保证执行计划准确性;
  4. truncate:重置 HWM 但清空数据,生产环境需谨慎使用;
  5. deallocate unused:仅释放 HWM 以上空间,无法解决 HWM 以下空闲问题。
相关推荐
小高不会迪斯科5 小时前
CMU 15445学习心得(二) 内存管理及数据移动--数据库系统如何玩转内存
数据库·oracle
时代的凡人5 小时前
0208晨间笔记
笔记
e***8905 小时前
MySQL 8.0版本JDBC驱动Jar包
数据库·mysql·jar
l1t5 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
今天只学一颗糖5 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
失忆爆表症7 小时前
03_数据库配置指南:PostgreSQL 17 + pgvector 向量存储
数据库·postgresql
AI_56787 小时前
Excel数据透视表提速:Power Query预处理百万数据
数据库·excel
SQL必知必会8 小时前
SQL 窗口帧:ROWS vs RANGE 深度解析
数据库·sql·性能优化
Gauss松鼠会8 小时前
【GaussDB】GaussDB数据库开发设计之JDBC高可用性
数据库·数据库开发·gaussdb
Vicky-Min8 小时前
NetSuite中保存Bill时遇到Overage的报错原因
oracle·erp