oracle高水位问题
转自:https://blog.csdn.net/cnham/article/details/5987999
说到HWM,我们首先要简要的谈谈ORACLE的逻辑存储管理.我们知道,ORACLE在逻辑存储上分4个粒度:表空间,段,区和块.
举 个例子来说,当我们创建一个表:PT_SCHE_DETAIL时,ORACLE就会为这个对象分配一个段.在这个段中,即使我们未插入任何记录,也至少有 一个区被分配,第一个区的第一个块就称为段头(SEGMENT HEADE),段头中就储存了一些信息,基中HWM的信息就存储在此.此时,因为第一个区的第一块用于存储段头的一些信息,虽然没有存储任何实际的记录, 但也算是被使用,此时HWM是位于第2个块.当我们不断插入数据到PM_USER后,第1个块已经放不下后面新插入的数据,此时,ORACLE将高水位之 上的块用于存储新增数据,同时,HWM本身也向上移.也就是说,当我们不断插入数据时,HWM会往不断上移,这样,在HWM之下的,就表示使用过的 块,HWM之上的就表示已分配但从未使用过的块.
考虑让我们看一个段,如一张表,其中填满了块,如图 1 所示。在正常操作过程中,删除了一些行,如图 2 所示。现有就有了许多浪费的空间:(I) 在表的上一个末端和现有的块之间,以及 (II) 在块内部,其中还有一些没有删除的行。

图2:行后面的块已经删除了;HWM 仍保持不变
HWM本身的信息是储存在段头.在段空间是手工管理方式时,ORACLE是通过FREELIST(一个单向链表)来管理段内的空间分配.在段空间是自动管理方式时(ASSM),ORACLE是通过BITMAP来管理段内的空间分配.
LOGGING
DATAFILE 'D:ORACLE_HOMEORADATARAINNYRAINNY.ORA' SIZE 5M
AUTOEXTEND
ON NEXT 10M MAXSIZE UNLIMITED EXTENT MANAGEMENT LOCAL
SEGMENT SPACE MANAGEMENT MANUAL;
I NUMBER(10);
FOR I IN 1..10000000 LOOP
INSERT INTO TEST_TAB VALUES(I,'TESTSTRING');
END LOOP;
COMMIT;
(C)我们来查询一下,看在插入一千万条记录后所访问的块数和查询所用时间:
SQL> SET TIMING ON
我们来看上面的执行计划,这句SQL总供耗时是:1分3秒.访问方式是采用全表扫描方式(FTS),逻辑读了156310个BLOCK,物理读了154239个BLOCK.
我们来分析一下这个表:
DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=> 'TEST',
TABNAME=> 'TEST_TAB',
PARTNAME=> NULL);END;
发现这个表目前使用的BLOCK有: 156532,未使用的BLOCK(EMPTY_BLOCKS)为:0,总行数为(NUM_ROWS):1000 0000
COMMIT;
----------------------------------------------------------
0 SELECT STATEMENT OPTIMIZER=CHOOSE (COST=15056 CARD=1)
2 1 TABLE ACCESS (FULL) OF 'TEST_TAB' (COST=15056 CARD=1)
----------------------------------------------------------
0 RECURSIVE CALLS
0 DB BLOCK GETS
156310 CONSISTENT GETS
155565 PHYSICAL READS
0 REDO SIZE
378 BYTES SENT VIA SQL*NET TO CLIENT
503 BYTES RECEIVED VIA SQL*NET FROM CLIENT
2 SQL*NET ROUNDTRIPS TO/FROM CLIENT
0 SORTS (MEMORY)
0 SORTS (DISK)
1 ROWS PROCESSED
我们在DELETE表后再次分析表,看看有什么变化:
这时, TEST_TAB表目前使用的BLOCK是: 156532,未使用的BLOCK(EMPTY_BLOCKS)为:0,总行数为(NUM_ROWS)已变成:0
问 题的根源就在于ORACLE的HWM.也就是说,在新增记录时,HWM会慢慢往上移,但是在删除记录后,HWM却不会往下移,也就是说,DELETE一千 万条记录后,此表的HWM根本没移动,还在原来的那个位置,所以,HWM以下的块数同样也是一样的.ORACLE的全表扫描是读取ORACLE高水位标记 下的所有BLOCK,也就是说,不管HWM下的BLOCK现在实际有没有存放数据,ORACLE都会一一读取,这样,大家可想而知,在我们DELETE表 后,ORACLE读了大量的空块,耗去了大量的时间.
我们再来看DELETE表后段空间实际使用的状况:
TOTAL BYTES.............................1346371584
UNUSED BLOCKS...........................7168 --有7168块没有用过,也就是在HWM上面的块数
UNUSED BYTES............................58720256
LAST USED EXT FILEID....................9
LAST USED EXT BLOCKID...................158856-- BLOCK ID 是针对数据文件来编号的,表示最后使用的一个EXTENT的第一个BLOCK的编号
LAST USED BLOCK.........................1024 -- 在最后使用的一个EXTENT 中一共用了1024块
LAST USED EXT BLOCK ID + LAST USED BLOCK -1 = HWM 所在的数据文件的BLOCK编号
TOTAL BYTES.............................1287651328
UNUSED BLOCKS...........................0
UNUSED BYTES............................0
LAST USED EXT FILEID....................9
LAST USED EXT BLOCKID...................158856
LAST USED BLOCK.........................1024
此时,总共用到的块数已变为8, 我们再代入上面的公式,算出HWM的位置: 8-5=3 HWM所在的BLOCK ID是2632+3-1=2634,
-Recorded in the segment header block
-Set to the beginning of the segment on the creation
-Incremented in five-block increments as rows are inserted
-Reset by the truncate command
-Never reset by the delete command
-Space above the high-water-mark can be reclaimed at the table level by using the following command:
ALTER TABLE DEALLOCATE UNUSED…
----------------------------------------------------------
1 0 SORT (AGGREGATE)
2 1 TABLE ACCESS (FULL) OF 'TEST_TAB'
----------------------------------------------------------
0 RECURSIVE CALLS
0 DB BLOCK GETS
0 PHYSICAL READS
0 REDO SIZE
378 BYTES SENT VIA SQL*NET TO CLIENT
503 BYTES RECEIVED VIA SQL*NET FROM CLIENT
2 SQL*NET ROUNDTRIPS TO/FROM CLIENT
0 SORTS (MEMORY)
0 SORTS (DISK)
1 ROWS PROCESSED
从 中我们也可以发现,分析表和SHOW_SPACE显示的数据有点不一致.那么哪个是准的呢?其实这两个都是准的,只不过计算的方法有点不同.事实上,当你 创建了一个对象如表以后,不管你有没有插入数据,它都会占用一些块,ORACLE也会给它分配必要的空间.同样,用ALTER TABLE MOVE释放自由空间后,还是保留了一些空间给这个表.
最后,我们再来执行TRUNCATE命令,截断这个表,看看段空间的使用状况:
TOTAL BYTES.............................65536
UNUSED BLOCKS...........................5
UNUSED BYTES............................40960
LAST USED EXT FILEID....................9
LAST USED EXT BLOCKID...................2632
LAST USED BLOCK.........................3
TOTAL BYTES.............................65536
UNUSED BLOCKS...........................5
UNUSED BYTES............................40960
LAST USED EXT FILEID....................9
LAST USED EXT BLOCKID...................2112
LAST USED BLOCK.........................3
(
INITIAL 64K
MINEXTENTS 1
MAXEXTENTS UNLIMITED
);
(2)如果MINEXTENT >HWM 则释放MINEXTENTS 以上的空间。如果要释放HWM以上的空间则使用KEEP 0。
ALTER TABLE TABLESNAME DEALLOCATE UNUSED KEEP 0;
(3) TRUNCATE TABLE DROP STORAGE(缺省值)命令可以将MINEXTENT 之上的空间完全释放(交还给操作系统),并且重置HWM。
(4)如果仅是要移动HWM,而不想让表长时间锁住,可以用TRUNCATE TABLE REUSE STORAGE,仅将HWM重置。
(5)ALTER TABLE MOVE会将HWM移动,但在MOVE时需要双倍的表空间,而且如果表上有索引的话,需要重构索引
(6)DELETE表不会重置HWM,也不会释放自由的空间(也就是说DELETE空出来的空间只能给对象本身将来的INSERT/UPDATE使用,不能给其它的对象使用)
如果要同时压缩表的索引,可以发布:ALTER TABLE TEST_TAB SHRINK SPACE CASCADE
注意:在使用此命令时需要先使行可迁移row movement(具体见例子)。
与使用ALTER TABLE MOVE 不同的是执行此命令后并不需要重构索引。
You use online segment shrink to reclaim fragmented free space below the high water mark in an Oracle Database segment. The benefits of segment shrink are these:
* Compaction of data leads to better cache utilization, which in turn leads to better online transaction processing (OLTP) performance.
* The compacted data requires fewer blocks to be scanned in full table scans, which in turns leads to better decision support system (DSS) performance.
Segment shrink is an online, in-place operation. DML operations and queries can be issued during the data movement phase of segment shrink. Concurrent DML operation are blocked for a short time at the end of the shrink operation, when the space is deallocated. Indexes are maintained during the shrink operation and remain usable after the operation is complete. Segment shrink does not require extra disk space to be allocated.
Segment shrink reclaims unused space both above and below the high water mark. In contrast, space deallocation reclaims unused space only above the high water mark. In shrink operations, by default, the database compacts the segment, adjusts the high water mark, and releases the reclaimed space.
Segment shrink requires that rows be moved to new locations. Therefore, you must first enable row movement in the object you want to shrink and disable any rowid-based triggers defined on the object.
Shrink operations can be performed only on segments in locally managed tablespaces with automatic segment space management (ASSM). Within an ASSM tablespace, all segment types are eligible for online segment shrink except these:
* IOT mapping tables
* Tables with rowid based materialized views
* Tables with function-based indexes
Elapsed: 00:00:05.83
----------
210992
Elapsed: 00:00:01.06
210992 rows created.
Elapsed: 00:00:59.83
Commit complete.
Elapsed: 00:00:00.07
Total Bytes.............................75497472
Unused Blocks...........................768
Unused Bytes............................6291456
Last Used Ext FileId....................4
Last Used Ext BlockId...................8328
Last Used Block.........................256
也可以通过查看extents得到HWM=8*16+128*63+256=8192+256=8448
Elapsed: 00:00:00.01
Elapsed: 00:00:40.99
Elapsed: 00:00:00.01
Total Bytes.............................75497472
Unused Blocks...........................768
Unused Bytes............................6291456
Last Used Ext FileId....................4
Last Used Ext BlockId...................8328
Last Used Block.........................256
Elapsed: 00:00:00.00
alter table demo shrink space
*
ERROR at line 1:
ORA-10636: ROW MOVEMENT is not enabled
Elapsed: 00:00:00.09
Elapsed: 00:00:00.10
Elapsed: 00:01:35.51
Total Bytes.............................29949952
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................4
Last Used Ext BlockId...................3720
Last Used Block.........................72
Elapsed: 00:00:00.02
高水位线实验:
-- 创建test3表
SQL> create table test3 as
2 select * from dba_objects where 1 = 2;
Table created
-- 查看表中分配块,区大小
SQL> SELECT segment_name, segment_type, blocks -- 分配数据块数, extents -- 分配区块数
2 FROM dba_segments
3 WHERE segment_name = 'TEST3'
4 ;
SEGMENT_NAME SEGMENT_TYPE BLOCKS EXTENTS
-------------------------------------------------------------------------------- ------------------ ---------- ----------
TEST3 TABLE 8 1
TEST3 TABLE 8 1
-- 分析表TEST3表
SQL> ANALYZE TABLE TEST3 ESTIMATE STATISTICS;
Table analyzed
-- 查询TEST3表高水位线
SQL> SELECT blocks -- 高水位线(占用TEST3表数据块数), empty_blocks -- TEST3表空闲块数, num_rows
2 FROM user_tables
3 WHERE table_name = 'TEST3';
BLOCKS EMPTY_BLOCKS NUM_ROWS
---------- ------------ ----------
0 7 0
-- 因为未向TEST3表中插入任何数据,因此此表的高水位线为0,现向TEST3表中插入数据再观察
SQL> insert into test3
2 select * from dba_objects;
50361 rows inserted
SQL> commit;
Commit complete
-- 重新分析表
SQL> ANALYZE TABLE TEST3 ESTIMATE STATISTICS;
Table analyzed
-- 再次查看表中分配块,区大小
SQL> SELECT segment_name, segment_type, blocks, extents
2 FROM dba_segments
3 WHERE segment_name = 'TEST3'
4 ;
SEGMENT_NAME SEGMENT_TYPE BLOCKS EXTENTS
-------------------------------------------------------------------------------- ------------------ ---------- ----------
TEST3 TABLE 8 1
TEST3 TABLE 768 21
此时看到BLOCKS数已增长到768, 也就是Oracle分配给TEST3表768个数据块,21个区
-- 再次查看TEST3表高水位线
SQL> SELECT blocks, empty_blocks, num_rows
2 FROM user_tables
3 WHERE table_name = 'TEST3';
BLOCKS EMPTY_BLOCKS NUM_ROWS
---------- ------------ ----------
689 78 50361
已增长到689个块, 还有78个空闲块,689 + 78 = 767, 比分配的少1个数据块,是因为这一个数据块是用作segment header
-- 现将TEST3表delete,在查看高水位线
SQL> delete from test3;
50361 rows deleted
SQL> commit;
Commit complete
SQL> ANALYZE TABLE TEST3 ESTIMATE STATISTICS;
Table analyzed
SQL>
SQL> SELECT blocks, empty_blocks, num_rows
2 FROM user_tables
3 WHERE table_name = 'TEST3';
BLOCKS EMPTY_BLOCKS NUM_ROWS
---------- ------------ ----------
689 78 0
发现此表高水位线并未减少,证明delete只是删除表中数据块的记录,但并不会使表中的高水位线下降, 在进行全表扫描时会Oracle会扫描表中高水位线下的所有数据块,
因此数据虽然被删除了,但查询时有可能还是很慢。所以在进行大表删除时应使用truncate语句,看下面实验:
SQL> truncate table test3;
Table truncated
SQL> ANALYZE TABLE TEST3 ESTIMATE STATISTICS;
Table analyzed
SQL>
SQL> SELECT blocks, empty_blocks, num_rows
2 FROM user_tables
3 WHERE table_name = 'TEST3';
BLOCKS EMPTY_BLOCKS NUM_ROWS
---------- ------------ ----------
0 7 0
现在表中高水位下降到0了, 一点心得, 记录下来。
4. 修正ORACLE 表的高水位线
在ORACLE 中,执行对表的删除操作不会降低该表的高水位线。而全表扫描将始终读取一个段 (extent) 中所有低于高水位线标记的块。如果在执行删除操作后不降低高水位线标记,则将导致查询语句的性能低下。
下面的方法都可以 降低高水位线标记 。
1. 执行表重建指令 alter table table_name move;
在线转移表空间ALTER TABLE ... MOVE TABLESPACE ..
当你创建了一个对象如表以后, 不管你有没有插入数据 , 它都会占用一些块 ,ORACLE 也会给它分配必要的空间 . 同样 ,用 ALTER TABLE MOVE 释放自由空间后 , 还是保留了一些空间给这个表 .
ALTER TABLE ... MOVE 后面不跟参数也行,不跟参数表还是在原来的表空间, M ove后记住重建索引 . 如果以后还要继续向这个表增加数据,没有必要move , 只是释放出来的空间,只能这个表用,其他的表或者 segment 无法使用该空间 。
2. 执行alter table table_name shrink space;
注意, 此命令为Oracle 10g 新增功能 ,再执行该指令之前必须允许行移动 alter table table_name enable row movement;
3. 复制要保留的数据到临时表t , drop 原表,然后 rename 临时表 t 为原表
4. 用逻辑导入导出: E mp/ I mp
5. A lter table table_name deallocate unused
注: 这证明,DEALLOCATE UNUSED 为释放 HWM 上面的未使用空间 , 但是并不会释放 HWM 下面的自由空间 , 也不会移动 HWM 的位置 .
6. 尽量使用 truncate .
注意:
在9I 中 :
1. 如果是 INEXTENT , 可以使ALTER TABLE TABLENAME DEALLOCATE UNUSED 将 HWM 以上所有没使用的空间释放
2. 如果MINEXTENT >HWM 则释放 MINEXTENTS 以上的空间。如果要释放 HWM 以上的空间则使用 KEEP 0 。
ALTER TABLE TABLESNAME DEALLOCATE UNUSED KEEP 0;
3. TRUNCATE TABLE DROP STORAGE (缺省值 ) 命令可以将MINEXTENT 之上的空间完全释放 ( 交还给操作系统 ), 并且重置 HWM 。
4. 如果仅是要移动HWM, 而不想让表长时间锁住 , 可以用 TRUNCATE TABLE REUSE STORAGE, 仅将 HWM 重置。
5. ALTER TABLE MOVE会将 HWM 移动 , 但在 MOVE 时需要双倍的表空间 , 而且如果表上有索引的话 , 需要重构索引
6. DELETE表不会重置 HWM, 也不会释放自由的空间 ( 也就是说 DELETE 空出来的空间只能给对象本身将来的 INSERT/UPDATE 使用 , 不能给其它的对象使用 )
在ORACLE 10G:
1. 可以使用ALTER TABLE TEST_TAB SHRINK SPACE 命令来联机移动 HWM,
2. 如果要同时压缩表的索引, 可以发布 :ALTER TABLE TEST_TAB SHRINK SPACE CASCADE
5. HWM 特点:
1. ORACLE用 HWM 来界定一个段中使用的块和未使用的块 .
举个例子来说, 当我们创建一个表时 ,ORACLE 就会为这个对象分配一个段 . 在这个段中 , 即使我们未插入任何记录 , 也至少有一个区被分配 , 第一个区的第一个块就称为段头 (SEGMENT HEADE), 段头中就储存了一些信息 , 基中 HWM 的信息就存储在此 . 此时 , 因为第一个区的第一块用于存储段头的一些信息 , 虽然没有存储任何实际的记录 , 但也算是被使用, 此时 HWM 是位于第 2 个块 . 当我们不断插入数据到 表 后, 第 1 个块已经放不下后面新插入的数据 , 此时 ,ORACLE 将高水位之上的块用于存储新增数据 , 同时 ,HWM 本身也向上移 . 也就是说 , 当我们不断插入数据时 ,HWM 会往不断上移 , 这样 , 在 HWM 之下的 , 就表示使用过的块 ,HWM 之上的就表示已分配但从未使用过的块 .
2. HWM在插入数据时 , 当现有空间不足而进行空间的扩展时会向上移 , 但删除数据时不会往下移 .
这就好比是水库的水位, 当涨水时 , 水位往上移 , 当水退出后 , 最高水位的痕迹还是清淅可见 .
ORACLE 不会释放空间以供其他对象使用,有一条简单的理由:由于空间是为新插入的行保留的,并且要适应现有行的增长。被占用的最高空间称为最高使用标记 (HWM) ,
3. HWM的信息存储在段头当中 .
HWM本身的信息是储存在段头 . 在段空间是手工管理方式时 ,ORACLE 是通过 FREELIST( 一个单向链表 ) 来管理段内的空间分配 . 在段空间是自动管理方式时 (ASSM),ORACLE 是通过 BITMAP 来管理段内的空间分配 .
4. ORACLE的全表扫描是读取高水位标记 (HWM) 以下的所有块 .
所以问题就产生了. 当用户发出一个全表扫描时, ORACLE 始终必须从段一直扫描到 HWM ,即使它什么也没有发现。该任务延长了全表扫描的时间。
5. 当用直接路径插入行时 , 即使HWM 以下有空闲的数据库块,键入在插入数据时使用了 append 关键字,则在插入时使用 HWM 以上的数据块,此时 HWM 会自动增大。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南