高水位线(High Water Mark,HWM)是Oracle数据库中一个重要但常被误解的概念。它不仅影响全表扫描的性能,还与空间管理、直接路径加载等操作密切相关。本文将深入解析MSSM和ASSM两种段空间管理模式下的HWM机制,特别是ASSM独有的Low HWMHigh 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(已格式化)的。但维护这一特性的代价是昂贵的:

  1. HW Enqueue锁争用:上涨HWM需要持有HW队列锁,长时间持有会抑制并发
  2. 格式化开销:格式化数据块涉及I/O操作,是相对较慢的操作
  3. 直接路径加载问题: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个extent
  • Highwater:: 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会绕过现有空闲空间,可能造成空间空洞