高水位线(High Water Mark,HWM)是Oracle数据库中一个重要但常被误解的概念。它不仅影响全表扫描的性能,还与空间管理、直接路径加载等操作密切相关。本文将深入解析MSSM和ASSM两种段空间管理模式下的HWM机制,特别是ASSM独有的Low HWM与High HWM双水位线设计。
一、什么是高水位线(HWM)?
1.1 基本概念
高水位线是Oracle用来标记数据段中已使用过的最高位置的指针。可以把它想象成水库的水位线:
- HWM以下:曾经存储过数据的区域(即使数据已被DELETE)
- HWM以上:从未使用过的"处女地"
关键点:DELETE操作不会降低HWM,只有TRUNCATE、SHRINK或MOVE操作才能重置HWM。
1.2 HWM对性能的影响
全表扫描(Full Table Scan)会读取HWM以下的所有数据块,即使这些块中的数据已被删除。这就是为什么删除大量数据后全表扫描仍然很慢的原因。
┌─────────────────────────────────────────────────────────────┐
│ 数据段 │
├──────────────────────────────┬──────────────────────────────┤
│ HWM以下(全表扫描范围) │ HWM以上(未使用) │
│ ┌───┬───┬───┬───┬───┬───┐ │ ┌───┬───┬───┬───┬───┬───┐ │
│ │数据│空 │数据│空 │数据│空 │ │ │ │ │ │ │ │ │ │
│ └───┴───┴───┴───┴───┴───┘ │ └───┴───┴───┴───┴───┴───┘ │
│ ↑ DELETE后产生的空块 │ │
└──────────────────────────────┴──────────────────────────────┘
▲
HWM
二、MSSM vs ASSM:两种段空间管理模式
2.1 MSSM(Manual Segment Space Management)
手动段空间管理,使用FREELIST来管理空闲空间:
- 只有一个HWM
- HWM以下的所有块都是已格式化的(formatted)
- HWM以上的块都是未格式化的(unformatted)
- 当FREELIST中没有可用块时,HWM默认每次上升5个数据块
-- 创建MSSM表空间
CREATE TABLESPACE mssm_ts
DATAFILE '/u01/oradata/mssm01.dbf' SIZE 100M
EXTENT MANAGEMENT LOCAL UNIFORM SIZE 1M
SEGMENT SPACE MANAGEMENT MANUAL;
2.2 ASSM(Automatic Segment Space Management)
自动段空间管理,使用位图(Bitmap)来管理空闲空间:
- 有两个HWM:Low HWM和High HWM
- 允许在段中间存在未格式化的块
- 更高效的并发插入性能
- Oracle 9i开始引入,10g后成为默认方式
-- 创建ASSM表空间(默认)
CREATE TABLESPACE assm_ts
DATAFILE '/u01/oradata/assm01.dbf' SIZE 100M
EXTENT MANAGEMENT LOCAL UNIFORM SIZE 1M
SEGMENT SPACE MANAGEMENT AUTO;
2.3 对比总结
| 特性 | MSSM | ASSM |
|---|---|---|
| 空间管理机制 | FREELIST | Bitmap |
| 高水位线数量 | 1个(HWM) | 2个(Low HWM + High HWM) |
| HWM以下块状态 | 全部已格式化 | 可能存在未格式化块 |
| 并发性能 | 较低(FREELIST争用) | 较高 |
| 适用场景 | 旧版本兼容 | 现代应用(推荐) |
三、ASSM的双水位线机制详解
3.1 为什么需要两个HWM?
在ASSM出现之前,Oracle必须保证HWM以下的所有块都是formatted(已格式化)的。但维护这一特性的代价是昂贵的:
- HW Enqueue锁争用:上涨HWM需要持有HW队列锁,长时间持有会抑制并发
- 格式化开销:格式化数据块涉及I/O操作,是相对较慢的操作
- 直接路径加载问题:Direct Load后可能在FREELIST上留下大量未使用的空间空洞
ASSM引入双水位线来解决这些问题:
- High HWM:标记段的最高使用位置,以上全是unformatted块
- Low HWM:标记已确认格式化的最高位置,以下全是formatted块
- 两者之间:可能存在formatted和unformatted块的混合区域
3.2 双水位线示意图
┌────────────────────────────────────────────────────────────────────────┐
│ ASSM 数据段 │
├───────────────────┬─────────────────────────┬──────────────────────────┤
│ Low HWM以下 │ Low HWM ~ High HWM之间 │ High HWM以上 │
│ (全部formatted) │ (混合:部分formatted) │ (全部unformatted) │
│ ┌──┬──┬──┬──┐ │ ┌──┬──┬──┬──┬──┬──┐ │ ┌──┬──┬──┬──┬──┬──┐ │
│ │F │F │F │F │ │ │F │U │F │U │F │U │ │ │U │U │U │U │U │U │ │
│ └──┴──┴──┴──┘ │ └──┴──┴──┴──┴──┴──┘ │ └──┴──┴──┴──┴──┴──┘ │
└───────────────────┴─────────────────────────┴──────────────────────────┘
▲ ▲
Low HWM High HWM
F = Formatted(已格式化)
U = Unformatted(未格式化)
3.3 双水位线在全表扫描中的作用
- Low HWM以下:扫描时不需要检查Bitmap,直接读取所有块
- Low HWM ~ High HWM之间:需要参考Bitmap来判断哪些块是formatted的,跳过unformatted块
- High HWM:作为全表扫描的停止位置
四、HWM的更新机制
4.1 High HWM的更新
当High HWM以下的空间不足以满足插入需求时,Oracle会提升High HWM:
- 需要获取HW Enqueue队列锁
- 移动的块数取决于一级位图块(L1 BMB)控制的块数
- High HWM以下包含所有的L1 Bitmap Block
4.2 Low HWM的更新条件
Low HWM的更新比High HWM更加严格,只有在以下条件满足时才会更新:
条件1:HEno == MyEno && MyBno == HBno + 1
→ 在同一个extent中格式化下一个范围的block
条件2:HEno + 1 == MyEno && HBno == HExtentSize && MyBno == 0
→ 前一个extent中所有块均已format,开始格式化下一个extent
其中:
HEno = 高水位Extent号
HBno = 高水位Block号
MyEno = 当前Extent号
MyBno = 当前Block号
简单来说:只有当前extent及之前所有extent的块都被格式化后,Low HWM才会更新。
4.3 数据块格式化时机
数据块的格式化通常发生在搜索可用空间时:
- 如果发现某个块是unformatted,则会格式化一批块(至少16个)
- 格式化数据块不一定会更新Low HWM
五、直接路径加载与HWM
5.1 Direct Path Load的特殊行为
直接路径加载(如INSERT /*+ APPEND */或SQL*Loader direct path)有特殊的空间分配行为:
- 数据直接写入High HWM以上的新区域
- 不使用FREELIST或Bitmap中的现有空闲块
- 最后一个extent中的块会被全部format
5.2 空间空洞问题
如果一个表主要通过直接路径加载数据,可能产生空间浪费:
┌────────────────────────────────────────────────────────────────┐
│ 第1次Direct Load → 第2次Direct Load → 第3次Direct Load │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 数据 │ 空洞 │ │ 数据 │ 空洞 │ │ 数据 │ 空洞 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ↑ ↑ ↑ │
│ extent末尾的空闲块从不被使用,造成空间浪费 │
└────────────────────────────────────────────────────────────────┘
ASSM通过保留这些块为unformatted状态,使后续的常规INSERT可以利用这些空间空洞。
六、实战:查看段的HWM信息
6.1 通过段头Dump查看
-- 1. 找到段头块位置
SELECT header_file, header_block
FROM dba_segments
WHERE segment_name = 'YOUR_TABLE' AND owner = 'YOUR_SCHEMA';
-- 2. Dump段头块
ALTER SYSTEM DUMP DATAFILE &header_file BLOCK &header_block;
-- 3. 查看跟踪文件
-- 在user_dump_dest目录下找到最新的trace文件
6.2 MSSM段头Dump示例
Extent Header:: spare1: 0 spare2: 0 #extents: 5 #blocks: 639
last map 0x00000000 #maps: 0 offset: 4128
Highwater:: 0x03c0028d ext#: 4 blk#: 13 ext size: 128
#blocks in seg. hdr's freelists: 0
#blocks below: 524
关键信息解读:
#extents: 5- 段包含5个extentHighwater:: 0x03c0028d- HWM的DBA地址ext#: 4 blk#: 13- HWM在第4个extent的第13个块#blocks below: 524- HWM以下有524个块
6.3 ASSM段头Dump示例
Extent Header:: spare1: 0 spare2: 0 #extents: 18413 #blocks: 147304
last map 0xabc23541 #maps: 36 offset: 2716
Highwater:: 0xabc23f6c ext#: 18412 blk#: 3 ext size: 8
#blocks in seg. hdr's freelists: 0
#blocks below: 147299
mapblk 0xabc23541 offset: 325
Unlocked
--------------------------------------------------------
Low HighWater Mark :
Highwater:: 0xabc23f6c ext#: 18412 blk#: 3 ext size: 8
#blocks in seg. hdr's freelists: 0
#blocks below: 147299
mapblk 0xabc23541 offset: 325
Level 1 BMB for High HWM block: 0xabc23ef9
Level 1 BMB for Low HWM block: 0xabc23ef9
--------------------------------------------------------
Segment Type: 1 nl2: 2 blksz: 8192 fbsz: 0
L2 Array start offset: 0x00001434
First Level 3 BMB: 0x00000000
L2 Hint for inserts: 0xabc0000a
Last Level 1 BMB: 0xabc23ef9
Last Level II BMB: 0xabc1d5fa
Last Level III BMB: 0x00000000
ASSM特有信息:
Low HighWater Mark- 显示Low HWM的位置Level 1 BMB for High HWM block- High HWM对应的L1位图块Level 1 BMB for Low HWM block- Low HWM对应的L1位图块
6.4 通过数据字典查询(近似值)
-- 查看表的块使用情况
SELECT table_name,
blocks, -- HWM以下的块数
empty_blocks, -- HWM以上的空块数
num_rows,
avg_row_len
FROM dba_tables
WHERE table_name = 'YOUR_TABLE' AND owner = 'YOUR_SCHEMA';
-- 注意:需要先收集统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS('YOUR_SCHEMA', 'YOUR_TABLE');
七、降低HWM的方法
7.1 TRUNCATE TABLE
-- 最快的方式,但会删除所有数据
TRUNCATE TABLE table_name;
-- 保留存储空间
TRUNCATE TABLE table_name REUSE STORAGE;
7.2 Segment Shrink(10g+,仅ASSM)
-- 启用行移动
ALTER TABLE table_name ENABLE ROW MOVEMENT;
-- 压缩段并降低HWM
ALTER TABLE table_name SHRINK SPACE;
-- 仅压缩数据,不降低HWM(减少对应用影响)
ALTER TABLE table_name SHRINK SPACE COMPACT;
-- 级联压缩索引
ALTER TABLE table_name SHRINK SPACE CASCADE;
7.3 Table Move(重建表)
-- 移动表到新位置(会重建表,降低HWM)
ALTER TABLE table_name MOVE;
-- 移动到指定表空间
ALTER TABLE table_name MOVE TABLESPACE new_tablespace;
-- 注意:移动后需要重建索引
ALTER INDEX index_name REBUILD;
7.4 导出/清空/导入
-- 传统方法
-- 1. 导出数据
-- 2. TRUNCATE TABLE
-- 3. 导入数据
7.5 方法对比
| 方法 | 保留数据 | 在线操作 | 索引影响 | 适用场景 |
|---|---|---|---|---|
| TRUNCATE | 否 | 是 | 无 | 清空表 |
| SHRINK SPACE | 是 | 是 | 无 | ASSM表空间 |
| MOVE | 是 | 否 | 需重建 | 任何表空间 |
| 导出/导入 | 是 | 否 | 需重建 | 大量数据删除后 |
八、隐藏参数:_bump_highwater_mark_count
这是一个控制HWM上升行为的隐藏参数:
-- 查看参数值
SELECT a.ksppinm AS parameter,
b.ksppstvl AS session_value,
c.ksppstvl AS instance_value,
a.ksppdesc AS description
FROM x$ksppi a, x$ksppcv b, x$ksppsv c
WHERE a.indx = b.indx AND a.indx = c.indx
AND a.ksppinm = '_bump_highwater_mark_count';
参数说明:控制FREELIST模式下每次HWM上升时分配的块数(默认为5)。
九、常见问题FAQ
Q1: 为什么DELETE大量数据后全表扫描还是很慢?
因为DELETE不会降低HWM,全表扫描仍会读取HWM以下的所有块(包括空块)。解决方案:使用SHRINK SPACE或重建表。
Q2: Low HWM和High HWM何时相等?
当两者之间没有unformatted块时,即所有块都已格式化。例如在段刚创建后,或者数据均匀填充的情况下。
Q3: Direct Path Insert后为什么不使用现有空闲空间?
这是设计行为。Direct Path Load直接写入HWM以上的新空间,绕过缓冲区缓存,目的是提高加载性能。
Q4: MSSM和ASSM可以在同一个数据库中共存吗?
可以。段空间管理是表空间级别的属性,同一数据库的不同表空间可以使用不同的管理方式。
Q5: 如何判断表是否有大量空块?
-- 分析表空间使用率
SELECT table_name,
ROUND((blocks * 8)/1024, 2) AS allocated_mb,
ROUND((num_rows * avg_row_len)/1024/1024, 2) AS actual_data_mb,
ROUND(((blocks * 8 * 1024) - (num_rows * avg_row_len)) / (blocks * 8 * 1024) * 100, 2) AS waste_pct
FROM dba_tables
WHERE owner = 'YOUR_SCHEMA' AND table_name = 'YOUR_TABLE';
总结
理解Oracle的高水位线机制对于性能调优和空间管理至关重要:
- MSSM使用单一HWM,结构简单但并发性能较低
- ASSM使用双水位线(Low HWM + High HWM),允许更灵活的空间管理
- 全表扫描会读取HWM以下的所有块,DELETE不会降低HWM
- 降低HWM的方法包括:TRUNCATE、SHRINK SPACE、MOVE
- Direct Path Load会绕过现有空闲空间,可能造成空间空洞