一、高水位线(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;
四、关键注意事项
- shrink space :需开启行移动,对分区表可按分区操作,不影响索引(
cascade会同步收缩索引); - move tablespace:移动后索引失效,需重建(分区索引需按分区重建);有 LOB 字段需单独移动;
- 统计信息:操作后需收集统计信息,保证执行计划准确性;
- truncate:重置 HWM 但清空数据,生产环境需谨慎使用;
- deallocate unused:仅释放 HWM 以上空间,无法解决 HWM 以下空闲问题。