Oracle Database frees only unused space above the high water mark (that is, the point beyond which database blocks have not yet been formatted to receive data). Oracle deallocates unused space beginning from the end of the object and moving toward the beginning of the object to the high water mark. If an extent is completely contained in the deallocation, then the whole extent is freed for reuse. If an extent is partially contained in the deallocation, then the used part up to the high water mark becomes the extent, and the remaining unused space is freed for reuse.In this Blog, we will make deallocation tests and look its effect in relation to low high-water mark (LHWM) and high high-water mark (HHWM). Then we dump LMT file space header and segment header to reveal extent management internals.
Note: Tested in Oracle 19.8
1. Deallocation Test
At first, we make a test:
-- drop tablespace test_ts including contents and datafiles;
-- Locally Managed, AUTOALLOCATE (system managed) Tablespace, Segment Space Management AUTO
SQL> create BIGFILE tablespace test_ts datafile '/oratestdb/oradata/testdb/test_ts.dbf' size 1000m online
extent management local autoallocate segment space management auto;
-- drop table test_tab purge;
SQL> create table test_tab tablespace test_ts as select level x, rpad('ABC', 1000, 'X') y from dual connect by level <= 2*1e4;
Table created.
SQL> select round(bytes/1024/1024) mb, blocks, extents, initial_extent/8192 initial_ext_blocks, next_extent/8192 next_ext_blocks
from dba_segments where segment_name = 'TEST_TAB';
MB BLOCKS EXTENTS INITIAL_EXT_BLOCKS NEXT_EXT_BLOCKS
---------- ---------- ---------- ------------------ ---------------
23 2944 38 8 128
SQL> select blocks, count(*) cnt, blocks*count(*) blocks#
,min(extent_id) min_ext_id, max(extent_id) max_ext_id, max(extent_id)-min(extent_id)+1 ext_id_span
,min(block_id) min_id_blk, max(block_id)+blocks-1 max_id_blk, max(block_id)+blocks-1-min(block_id)+1 blk_id_span
from dba_extents where segment_name = 'TEST_TAB' group by blocks order by 1;
BLOCKS CNT BLOCKS# MIN_EXT_ID MAX_EXT_ID EXT_ID_SPAN MIN_ID_BLK MAX_ID_BLK BLK_ID_SPAN
---------- ---------- ---------- ---------- ---------- ----------- ---------- ---------- -----------
8 16 128 0 15 16 8696 8823 128
128 22 2816 16 37 22 8832 11647 2816
We can see that 16 extents of size 8 blocks and 22 extents of size 128 blocks are allocated to the test table,
total 38 (16+22) extents, 2944 (128+2816) blocks.Now we deallocate unused space:
-- 'keep 0' has no effect since it is the default
SQL> alter table test_tab deallocate unused keep 0;
Table altered.
SQL> select blocks, count(*) cnt, blocks*count(*) blocks#
,min(extent_id) min_ext_id, max(extent_id) max_ext_id, max(extent_id)-min(extent_id)+1 ext_id_span
,min(block_id) min_id_blk, max(block_id)+blocks-1 max_id_blk, max(block_id)+blocks-1-min(block_id)+1 blk_id_span
from dba_extents where segment_name = 'TEST_TAB' group by blocks order by 1;
BLOCKS CNT BLOCKS# MIN_EXT_ID MAX_EXT_ID EXT_ID_SPAN MIN_ID_BLK MAX_ID_BLK BLK_ID_SPAN
---------- ---------- ---------- ---------- ---------- ----------- ---------- ---------- -----------
8 16 128 0 15 16 8696 8823 128
96 1 96 37 37 1 11520 11615 96
128 21 2688 16 36 21 8832 11519 2688
We can see that the last extent (extent_id: 37) is deallocated, its size is shrunk from 128 blocks to 96 blocks,
which means that 32 (128-96) unformatted blocks at the end of that extent are freed. MAX_ID_BLK is also reduced 32 (11647-11615).Oracle has two High-Water Marks:
-. Low High-Water Mark (LHWM): Like the old High Water Mark. All blocks below LHWM have been formatted for the table. -. High High-Water Mark (HHWM): blocks between LHWM and HHWM are formatted only when used, some blocks could be unformatted.The last extent is located between LHWM and HHWM, and it can contain blocks which have not yet been formatted, hence to be deallocated. In the above Oracle Docu excerpt, high water mark probably refers to Low High-Water Mark (LHWM).
After deallocation, total extents is still 38 (16+1+21), but blocks is reduced to 2912 (128+96+2688) blocks from previous 2944 blocks. In the later TEST_TAB segment header dump, we will see following text:
Low HighWater Mark :
Highwater:: 0x00002d60 ext#: 37 blk#: 96 ext size: 128
#blocks in seg. hdr's freelists: 0
#blocks below: 2912
Now we insert one row and then deallocate again:
SQL> insert into test_tab values (987654, rpad('ABC', 1000, 'X'));
1 row created.
SQL> commit;
Commit complete.
SQL> select blocks, count(*) cnt, blocks*count(*) blocks#
,min(extent_id) min_ext_id, max(extent_id) max_ext_id, max(extent_id)-min(extent_id)+1 ext_id_span
,min(block_id) min_id_blk, max(block_id)+blocks-1 max_id_blk, max(block_id)+blocks-1-min(block_id)+1 blk_id_span
from dba_extents where segment_name = 'TEST_TAB' group by blocks order by 1;
BLOCKS CNT BLOCKS# MIN_EXT_ID MAX_EXT_ID EXT_ID_SPAN MIN_ID_BLK MAX_ID_BLK BLK_ID_SPAN
---------- ---------- ---------- ---------- ---------- ----------- ---------- ---------- -----------
8 16 128 0 15 16 8696 8823 128
96 1 96 37 37 1 11520 11615 96
128 22 2816 16 38 23 8832 11775 2944
SQL> alter table test_tab deallocate unused;
Table altered.
SQL> select blocks, count(*) cnt, blocks*count(*) blocks#
,min(extent_id) min_ext_id, max(extent_id) max_ext_id, max(extent_id)-min(extent_id)+1 ext_id_span
,min(block_id) min_id_blk, max(block_id)+blocks-1 max_id_blk, max(block_id)+blocks-1-min(block_id)+1 blk_id_span
from dba_extents where segment_name = 'TEST_TAB' group by blocks order by 1;
BLOCKS CNT BLOCKS# MIN_EXT_ID MAX_EXT_ID EXT_ID_SPAN MIN_ID_BLK MAX_ID_BLK BLK_ID_SPAN
---------- ---------- ---------- ---------- ---------- ----------- ---------- ---------- -----------
8 16 128 0 15 16 8696 8823 128
96 1 96 37 37 1 11520 11615 96
128 22 2816 16 38 23 8832 11775 2944
We can see that a new extent (size 128 blocks) is allocated for this single row, and such size of extents is increased from 21 to 22.
The previous deallocated extent (size 96 blocks) is not expanded and re-used,
but this new produced irregular extent could put additional complexity to extent management.In fact, if we did not perform any deallocation, we could safely insert 200 more rows without adding any new extent:
insert into test_tab select level x, rpad('ABC', 1000, 'X') y from dual connect by level <= 200;
So the effect is that deallocated 32 blocks incurs a new extent of 128 blocks.
(Note: blocks of any deleted rows are not eligible to be deallocated because they have been formatted)The above test shows that deallocation frees the unformatted blocks of last extent in the first time, and no more further effect in the next execution.
2. File Header and Bitmap Dump
Now we create 3 tablesapces, one is BIGFILE, one is SMALLFILE, and one more BIGFILE with "_enable_12g_bft"=FALSE (default is TRUE). On each of them, we also create one table.
drop tablespace test_ts including contents and datafiles;
create BIGFILE tablespace test_ts datafile '/oratestdb/oradata/testdb/test_ts.dbf' size 1000m online
extent management local autoallocate segment space management auto;
drop table test_tab purge;
create table test_tab tablespace test_ts as select level x, rpad('ABC', 1000, 'X') y from dual connect by level <= 2*1e4;
drop tablespace test_ts_small including contents and datafiles;
create SMALLFILE tablespace test_ts_small datafile '/oratestdb/oradata/testdb/test_ts_small.dbf' size 1000m online
extent management local autoallocate segment space management auto;
drop table test_tab_small purge;
create table test_tab_small tablespace test_ts_small as select level x, rpad('ABC', 1000, 'X') y from dual connect by level <= 2*1e4;
--alter system reset "_enable_12g_bft" scope=memory; -- (enable 12g bigfile tablespace, default TRUE)
alter system set "_enable_12g_bft"=FALSE scope=memory;
drop tablespace test_ts_12g_bft_false including contents and datafiles;
create BIGFILE tablespace test_ts_12g_bft_false datafile '/oratestdb/oradata/testdb/test_ts_12g_bft_false.dbf' size 1000m online
extent management local autoallocate segment space management auto;
drop table test_tab_12g_bft_false purge;
create table test_tab_12g_bft_false tablespace test_ts_12g_bft_false as select level x, rpad('ABC', 1000, 'X') y from dual connect by level <= 2*1e4;
alter system reset "_enable_12g_bft" scope=memory;
Now we query FILE_ID for each tablespace and dump block 2 and 3:
SQL > select tablespace_name, file_id from dba_data_files where tablespace_name in ('TEST_TS', 'TEST_TS_SMALL', 'TEST_TS_12G_BFT_FALSE');
TABLESPACE_NAME FILE_ID
------------------------------ ----------
TEST_TS 1968
TEST_TS_SMALL 1969
TEST_TS_12G_BFT_FALSE 1970
alter session set tracefile_identifier = 'TEST_TS';
alter system dump datafile 1968 block min 2 block max 3;
alter session set tracefile_identifier = 'TEST_TS_SMALL';
alter system dump datafile 1969 block min 2 block max 3;
alter session set tracefile_identifier = 'TEST_TS_12G_BFT_FALSE';
alter system dump datafile 1970 block min 2 block max 3;
Here all 3 dumps (many detail lines removed):
--============ TEST_TS ============--
Start dump data blocks tsn: 2882 file#:1968 minblk 2 maxblk 3
BH (0x118fd2048) file#: 1968 rdba: 0x00000002 (1024/2) class: 13 ba: 0x118bbe000
frmt: 0x02 chkval: 0xff66 type: 0x4c=KTFBN File Space Header
File Space Header Block:
------------------------
Header Control:
---------------
RelFno:1024 Unit:8 Size:128000 Initial:8691 Tail:128000
AutoExtend:NO Increment:0 MaxSize:0
Flag:0x00000901 (BASIC/-/-/-/-/-/-/-/BIGF/-/AUTOALLOC)
Deallocation scn: 0x00000000fb5cd8ea
BigFile Header Control:
-----------------------
First_FSB:3 FSB_Batch:128 FSB_Chunk:1 FSB_FFBs:16384
First_FFB:131 FFB_Chunk:16 FFB_AUs:32768
-----------------
FSB instance map:
-----------------
[ 0] 0x00000003 (3)
METADATA Table:
---------------
[ 0] flag:0x7(I/S/P) first:0x00000093(147) len:96
BH (0x128fd8df8) file#: 1968 rdba: 0x00000003 (1024/3) class: 12 ba: 0x128c5a000
frmt: 0x02 chkval: 0xa038 type: 0x4d=KTFBN File Space Level 2 Bitmap
FSB BitMap Control:
-------------------
begin:0 unit:16384 flag:0x1
FFB Bitmap:
--============ TEST_TS_SMALL ============--
Start dump data blocks tsn: 2883 file#:1969 minblk 2 maxblk 3
BH (0x154f7dbb0) file#: 1969 rdba: 0xec800002 (946/2) class: 13 ba: 0x154440000
frmt: 0x02 chkval: 0xbc13 type: 0x1d=KTFB Bitmapped File Space Header
File Space Header Block:
Header Control:
RelFno: 946, Unit: 8, Size: 128000, Flag: 1(0x1)
AutoExtend: NO, Increment: 0, MaxSize: 0
Initial Area: 126, Tail: 127999, First: 368, Free: 15616
BH (0x124fd52e8) file#: 1969 rdba: 0xec800003 (946/3) class: 12 ba: 0x124c06000
frmt: 0x02 chkval: 0xac2b type: 0x1e=KTFB Bitmapped File Space Bitmap
File Space Bitmap Block:
BitMap Control:
RelFno: 946, BeginBlock: 128, Flag: 0, First: 368, Free: 63120
--============ TEST_TS_12G_BFT_FALSE ============--
Start dump data blocks tsn: 2884 file#:1970 minblk 2 maxblk 3
BH (0x162fc1248) file#: 1970 rdba: 0x00000002 (1024/2) class: 13 ba: 0x162a3e000
frmt: 0x02 chkval: 0xdbd1 type: 0x1d=KTFB Bitmapped File Space Header
File Space Header Block:
Header Control:
RelFno: 1024, Unit: 8, Size: 128000, Flag: 1(0x1)
AutoExtend: NO, Increment: 0, MaxSize: 0
Initial Area: 126, Tail: 127999, First: 368, Free: 15616
BH (0x118fb5fb0) file#: 1970 rdba: 0x00000003 (1024/3) class: 12 ba: 0x118940000
frmt: 0x02 chkval: 0x4719 type: 0x1e=KTFB Bitmapped File Space Bitmap
File Space Bitmap Block:
BitMap Control:
RelFno: 1024, BeginBlock: 128, Flag: 0, First: 368, Free: 63120
In the above dumps, block 2 is "class: 13" ('file header block'), and block 3 is "class: 12" ('bitmap index block')
(See
Oracle Internals - Block Classes).In TEST_TS dump, block 2 and 3 types are prefixed with "KTFBN", which are:
KTFBN File Space Header KTFBN File Space Level 2 Bitmapwhereas in TEST_TS_SMALL dump, block 2 and 3 types are prefixed with "KTFB", which are:
KTFB Bitmapped File Space Header KTFB Bitmapped File Space BitmapTEST_TS_12G_BFT_FALSE is a BIGFILE tablespace similar to TEST_TS, but with "_enable_12g_bft"=FALSE (default is TRUE). Then its header and bitmap are same as SMALLFILE.
The extents inside a datafile of a locally managed tablespace are tracked in KTFBN or KTFB Bitmap blocks. It seems that this special 12g hidden parameter "_enable_12g_bft" switched Bitmap type identification from KTFB to KTFBN.
About KTFB Bitmap blocks, Blog: Deciphering extent management internals inside locally managed tablespaces has a deep investigation.
3. Segment Header and Bitmap Dump
For 3 created tables, we first find their HEADER_BLOCK, and then dump HEADER_BLOCK and its two preceding blocks:
SQL> select segment_name, header_file, header_block from dba_segments where segment_name in ('TEST_TAB', 'TEST_TAB_SMALL', 'TEST_TAB_12G_BFT_FALSE');
SEGMENT_NAME HEADER_FILE HEADER_BLOCK
---------------------- ------- ----------- ------------
TEST_TAB 1968 8698
TEST_TAB_SMALL 1969 130
TEST_TAB_12G_BFT_FALSE 1970 130
alter session set tracefile_identifier = 'TEST_TAB';
alter system dump datafile 1968 block min 8696 block max 8698;
alter session set tracefile_identifier = 'TEST_TAB_SMALL';
alter system dump datafile 1969 block min 128 block max 130;
alter session set tracefile_identifier = 'TEST_TAB_12G_BFT_FALSE';
alter system dump datafile 1970 block min 128 block max 130;
The HEADER_BLOCK is "class: 4" ('segment header'), two preceding blocks are "class: 8" ('1st level bmb') and "class: 9" ('2nd level bmb'):
--============ TEST_TAB ============--
Start dump data blocks tsn: 2882 file#:1968 minblk 8696 maxblk 8698
BH (0x118f9aa58) file#: 1968 rdba: 0x000021f8 (1024/8696) class: 8 ba: 0x1186d2000
frmt: 0x02 chkval: 0xe677 type: 0x20=FIRST LEVEL BITMAP BLOCK
Dump of First Level Bitmap Block
--------------------------------
First free datablock : 16
--------------------------------------------------------
DBA Ranges :
--------------------------------------------------------
0x000021f8 Length: 8 Offset: 0
BH (0xfbfa0890) file#: 1968 rdba: 0x000021f9 (1024/8697) class: 9 ba: 0xfb758000
frmt: 0x02 chkval: 0x74b9 type: 0x21=SECOND LEVEL BITMAP BLOCK
Dump of Second Level Bitmap Block
number: 52 nfree: 1 ffree: 51 pdba: 0x000021fa
BH (0xfaf8d8d0) file#: 1968 rdba: 0x000021fa (1024/8698) class: 4 ba: 0xfa5a8000
frmt: 0x02 chkval: 0x7293 type: 0x23=PAGETABLE SEGMENT HEADER
Extent Control Header
-----------------------------------------------------------------
Extent Header:: spare1: 0 spare2: 0 #extents: 38 #blocks: 2944
last map 0x00000000 #maps: 0 offset: 2716
Highwater:: 0x00002d60 ext#: 37 blk#: 96 ext size: 128
#blocks in seg. hdr's freelists: 0
#blocks below: 2912
--------------------------------------------------------
Low HighWater Mark :
Highwater:: 0x00002d60 ext#: 37 blk#: 96 ext size: 128
#blocks in seg. hdr's freelists: 0
#blocks below: 2912
mapblk 0x00000000 offset: 37
Level 1 BMB for High HWM block: 0x00002d01
Level 1 BMB for Low HWM block: 0x00002d01
--------------------------------------------------------
Segment Type: 1 nl2: 1 blksz: 8192 fbsz: 0
Map Header:: next 0x00000000 #extents: 38 obj#: 3998654 flag: 0x10000000
Extent Map
-----------------------------------------------------------------
0x000021f8 length: 8
0x00002280 length: 128
Auxillary Map
--------------------------------------------------------
Extent 0 : L1 dba: 0x000021f8 Data dba: 0x000021fb
--------------------------------------------------------
Second Level Bitmap block DBAs
--------------------------------------------------------
DBA 1: 0x000021f9
In the above dump, we can see a special section starting with "Low HighWater Mark".
(Blog: Low High Water Mark, High High Water Mark, and Parallel Query
has some discussions on LHWM and HHWM).The dump of TEST_TAB_SMALL and TEST_TAB_12G_BFT_FALSE are very similar to TEST_TAB.