Oracle TABLE
CREATE
TODO
CTAS
To duplicate an existing table you can use CTAS (Create Table As Select)...
CREATE mytable2 AS SELECT * FROM mytable1;
The SELECT can be pretty much any valid SELECT statement. Any ambiguity will cause problems.. i.e. you should define the columns you are going to populate if the query isn't going to be able to derive them.
SAMPLE
If you only want a subset of the data, consider the SAMPLE clause...
CREATE mytable2 AS SELECT * FROM mytable1 SAMPLE(20);
Will create mytable2 based on 20% of the rows in mytable1CREATE mytable2 AS SELECT * FROM mytable1 SAMPLE BLOCK(20);
Will create mytable2 based on 20% of the block in mytable1CREATE_TABLE_COST
"This procedure is used in capacity planning to determine the size of the table given various attributes."
https://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_space.htm#i1003180
MODIFY
For example, to change the size or nullability of a column...
ALTER TABLE myTable MODIFY myColumn VARCHAR2(30) NOT NULL;
NOTE: If there are NULL values in this column in any existing rows you will get ORA-02296Also see: MOVE, later on this page.DROP
To DROP a table...
DROP TABLE '&TABLE';
To the DROP the table and PURGE the table from the RECYCLEBIN in a single step...
DROP TABLE '&TABLE' PURGE;
To DROP the table and all referential integrity constraints that refer to primary and unique keys in the dropped table...
DROP TABLE '&TABLE' CASCADE CONSTRAINTS;
You could use this to address: ORA-02449: unique/primary keys in table referenced by foreign keysTo "un-drop" a table...
FLASHBACK TABLE '%TABLE' TO BEFORE DROP;
Note: this is NOT possible if you used the PURGE option for the DROP TABLE or if the table has been otherwise purged from the recycle bin.TRUNCATE
A TRUNCATE statement does not generate any Undo information and it Commits immediately.
It is a DDL statement and cannot be rolled back.
Constraints and Triggers are retained (but Triggers do not fire during TRUNCATE)
A single Audit record is generated for the TRUNCATE statement being issued (if auditing is turned on)
When a table or cluster is truncated, all associated indexes are also truncated.
TRUNCATE TABLE '&TABLE' REUSE STORAGE; # Table retains all extents (but they are all empty)
TRUNCATE TABLE '&TABLE' DROP STORAGE; # Table retains only number of extents equal to MINEXTENTS (all are empty) (DEFAULT)
TRUNCATE TABLE '&TABLE' DROP ALL STORAGE; # Table retains only one extent (which is empty) 11.2.0.2+
Row Movement
Row Movement gives Oracle permission to change the rowid of a row so that you can perform operations like shrink space or an update/set that moves rows across table partitions.Check
SELECT row_movement
FROM dba_tables
WHERE owner = '&OWNER'
AND table_name = '&TABLE';
Enable
ALTER TABLE &TABLE ENABLE ROW MOVEMENT;
Table Size
To see the table size (not including indexes and LOB segments)...
SELECT SUM(e.blocks) AS blocks,
COUNT(*) AS extents,
SUM(e.blocks*p.value) AS bytes,
SUM(e.blocks*p.value)/1024 AS KB,
SUM(e.blocks*p.value)/1024/1024 AS MB,
SUM(e.blocks*p.value)/1024/1024/1024 AS GB
FROM dba_extents e,
v$parameter p
WHERE e.owner = '&table_owner'
AND e.segment_name = '&table_name'
AND p.name = 'db_block_size';
An alternate approach (not including indexes and LOB segments)...
SET TRIMSPOOL ON
SET LINESIZE 132 PAGESIZE 80
COLUMN owner FORMAT a20
COLUMN segment_name FORMAT a30
COLUMN segment_type FORMAT a10
COLUMN bytes FORMAT 999,999,999,999,999
SELECT owner, segment_name, segment_type, bytes
FROM dba_segments
WHERE segment_type = 'TABLE'
AND owner = '&table_owner'
AND segment_name = '&table_name'
ORDER BY bytes, owner, segment_name, segment_type
/
Including LOB segments and Indexes...
SELECT owner, segment_name, segment_type, bytes
FROM dba_segments
WHERE segment_type = 'TABLE'
AND owner = '&&table_owner'
AND segment_name = '&&table_name'
UNION
SELECT owner, segment_name, segment_type, bytes
FROM dba_segments
WHERE segment_type = 'LOBSEGMENT'
AND owner = '&&table_owner'
AND segment_name IN (SELECT segment_name
FROM dba_lobs
WHERE owner = '&&table_owner'
AND table_name = '&&table_name')
UNION
SELECT owner, segment_name, segment_type, bytes
FROM dba_segments
WHERE segment_type = 'INDEX'
AND owner = '&&table_owner'
AND segment_name IN (SELECT index_name
FROM dba_indexes
WHERE owner = '&&table_owner'
AND table_name = '&&table_name')
ORDER BY bytes, owner, segment_name, segment_type
/
TODO: Include partitions (table and index)
In versions prior to 12.1 the query below will only be accurate if table statistics have just been gathered....
SELECT num_rows, blocks
FROM dba_tables
WHERE table_name = '&TABLE';
The queries below will give you accurate results... but, are you really sure you need accurate results?
https://asktom.oracle.com/ords/f?p=100:11:0::NO::P11_QUESTION_ID:1660875645686SPOOL /tmp/tmp.sql
SET HEADING OFF
SET PAGESIZE 0
SET FEEDBACK OFF
SELECT 'SELECT '''||table_name||''' AS TABLE_NAME, COUNT(*) AS ROW_COUNT FROM '||owner||'."'||table_name||'";'
FROM dba_tables
WHERE owner = UPPER('&table_owner')
/
SPOOL OFF
COLUMN TABLE_NAME FORMAT a30
@/tmp/tmp.sql
Save this to a script (e.g. /tmp/my.sql) and run it. Cut&Paste direct to SQL*Plus won't work.This script assumes you are a DBA needing to query from another schema. If you are logged in to the owning schema, make your life easier and use USER_TABLES.ALTER SESSION SET CURRENT_SCHEMA=&table_owner;
SET PAGESIZE 20
COLUMN TABLE_NAME FORMAT a30
WITH
FUNCTION rcount(tname IN VARCHAR2) RETURN INT IS
c INT;
BEGIN
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM "'||tname||'"'
INTO c;
RETURN c;
END;
SELECT table_name, rcount(table_name) AS ROW_COUNT
FROM dba_tables
WHERE owner = UPPER('&&table_owner');
/
Save this to a script (e.g. /tmp/my.sql) and run it. Cut&Paste direct to SQL*Plus won't work.This script assumes you are a DBA needing to query from another schema. If you are logged in to the owning schema, make your life easier and use USER_TABLES.Space Usage
DBMS_SPACE.SPACE_USAGE
@/u01/dba/blkspc.sql
DBMS_OUTPUT.PUT_LINE( 'Unformatted Blocks '|| l_unformatted_blocks ); DBMS_OUTPUT.PUT_LINE( 'Full Blocks '|| l_full_blocks ); DBMS_OUTPUT.PUT_LINE( 'FS1 Blocks (0-25) '|| l_fs1_blocks ); DBMS_OUTPUT.PUT_LINE( 'FS2 Blocks (25-50) '|| l_fs2_blocks ); DBMS_OUTPUT.PUT_LINE( 'FS3 Blocks (50-75) '|| l_fs3_blocks ); DBMS_OUTPUT.PUT_LINE( 'FS4 Blocks (75-100)'|| l_fs4_blocks ); DBMS_OUTPUT.PUT_LINE( 'total '|| (l_unformatted_blocks+ l_fs1_blocks+ l_fs2_blocks+ l_fs3_blocks+ l_fs4_blocks+ l_full_blocks) );END;/SET SERVEROUTPUT OFF
DBMS_SPACE.UNUSED_SPACE
@/u01/dba/blkunused.sql
Blocks
Not-Empty Blocks
SELECT COUNT(*)
FROM ( SELECT DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
FROM &mytable
GROUP BY DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID));
All Blocks
SELECT SUM(BLOCKS)
FROM dba_segments
WHERE owner = '&myschema'
AND segment_name = '&mytable';
Generate DDL
Shrink
NOTE: Row Movement must be Enabled in order for Shrink operations to work.To recover space and amend the high water mark...
ALTER TABLE &TABLE SHRINK SPACE;
To recover space for the table and all dependant objects...
ALTER TABLE &TABLE SHRINK SPACE CASCADE;
To recover space then amend the high water mark as a seperate step...
ALTER TABLE &TABLE SHRINK SPACE COMPACT;
ALTER TABLE &TABLE SHRINK SPACE;
Move
NOTE: Row Movement must be Enabled in order for Move operations to work.ALTER TABLE &TABLE MOVE;
ALTER TABLE &TABLE MOVE TABLESPACE &TSPACE;
You will need to rebuild indexes to make them usable againALTER TABLE &TABLE MOVE TABLESPACE &TSPACE UPDATE INDEXES
ALTER TABLE &TABLE MOVE ONLINE;
It is recommended to always re-gather statistics at the end of the moveRebuild in same tablespace
Move to new tablespace
Move to new tablespace keeping indexes valid
ONLINE introduced in 12.2 (update indexes not required)
Note that a Move operation will lead to two copies of the table being in existence for the duration of the move. Be aware of the space implications of this.
ALTER TABLE &TABLE MOVE ONLINE TABLESPACE &TSPACE;
ALTER TABLE &TABLE MOVE ONLINE TABLESPACE &TSPACE COMPRESS;
ALTER TABLE &TABLE MOVE ONLINE TABLESPACE &TSPACE NOCOMPRESS;
ALTER TABLE &TABLE MOVE ONLINE STORAGE (PCTINCREASE 0)'
Estimating Benefit of Shrink or Move
Assuming a block overhead of 8% (1), and that table stats are current, we can estimate how many blocks the table should theoretically use vs what it is actually using...
SELECT blocks,
num_rows*avg_row_len/8192*100/(100-pct_free)*1.08 est_blocks
FROM user_tables
WHERE table_name = '&TABLE';
Test Data
Collected examples of tables containining randomly generated data for tesing purposesCREATE TABLE &mytab AS
SELECT level AS id,
DBMS_RANDOM.string('x',10) AS code,
DBMS_RANDOM.string('a',50) AS short_description,
DBMS_RANDOM.string('a',200) AS long_description
FROM dual
CONNECT BY level <= 10000;
Generates a table called mytab with 10,000 rows, an auto-generated unique numeric id, a random 10 character uppercase code, a random mixed case short description and a random mixed case long description.
Potential issues with this dataset....
All values are fixed length rather than variable length
Table Blocks Deep Dive
CREATE USER &&user IDENTIFIED BY "&password"
DEFAULT TABLESPACE &&tspace
TEMPORARY TABLESPACE &temp
QUOTA UNLIMITED ON &&tspace
PROFILE &profile;
GRANT resource TO &&user;
Create an empty schema for this test.
CREATE TABLE &mytab AS
SELECT level AS id,
DBMS_RANDOM.string('x',10) AS code,
DBMS_RANDOM.string('a',50) AS short_description,
DBMS_RANDOM.string('a',200) AS long_description
FROM dual
CONNECT BY level <= 10000;
Run this 3 times to create:
MYTAB1, MYTAB2 and MYTAB3.
SELECT table_name,
num_rows,
avg_row_len,
blocks,
pct_free,
ROUND(num_rows*avg_row_len/8192*100/(100-pct_free)*1.08) AS est_blocks
FROM user_tables;
Let's see how many blocks our tables are using and let's compare that to what we estimate it should be using (est_blocks).
Let's save this script as:
/tmp/blkchk.sql
Our estimate calculation is close enough for our purposes (it is within 10 blocks of the number of blocks reported in USER_TABLES).
Note that the number of blocks reported in USER_TABLES will differ from other sources of what, at first glance, would seem to be the same information. Consider this alternate way of counting blocks...
SELECT COUNT(1)
FROM( SELECT DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid)
FROM mytab1
GROUP BY DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID))
To further muddy the waters, USER_SEGMENTS returns a different figure...
SELECT blocks
FROM user_segments
WHERE segment_name = 'MYTAB1'
AND segment_type='TABLE'
Let's dig into that a bit further...
SELECT COUNT(extent_id) AS Extents,
blocks AS Blocks_per_Extent
FROM user_extents
WHERE segment_name = 'MYTAB1'
AND segment_type='TABLE'
GROUP BY blocks;
Let's reverse engineer one of our tables and create a new copy of it without any data...
SET LONG 20000
SET PAGESIZE 0
SELECT DBMS_METADATA.GET_DDL(object_type,object_name)
FROM user_objects
WHERE object_name = '&TABLE';
Let's create some new scripts to make our experiment easier to run.
This script will provide us with a breakdown of what the number of blocks looks like from the various different sources of this information just for our new table (mytab4).
Let's call this script...
/tmp/blkchk4.sql
SELECT table_name, est_blocks, rowid_blocks, table_blocks, extent_blocks, segment_blocks, empty_blocks, table_rows FROM ( SELECT blocks AS segment_blocks FROM user_segments WHERE segment_name = 'MYTAB4' AND segment_type='TABLE' ), ( SELECT table_name, num_rows AS table_rows, blocks AS table_blocks, empty_blocks, ROUND(num_rows*avg_row_len/8192*100/(100-pct_free)*1.08) AS est_blocks FROM user_tables WHERE table_name = 'MYTAB4' ), ( SELECT COUNT(1) AS rowid_blocks FROM ( SELECT DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid) FROM mytab4 GROUP BY DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) ) ), ( SELECT SUM(blocks) AS extent_blocks FROM user_extents WHERE segment_name = 'MYTAB4' AND segment_type = 'TABLE')/
This script will prompt for a number of rows to insert, insert them, gather stats and provide us with a breakdown of what the number of blocks looks like from the various different sources of this information.
Let's call this script...
/tmp/addrow4.sql
EXEC DBMS_STATS.gather_table_stats(NULL,'MYTAB4');
@@/tmp/blkchk4.sql
@/tmp/blkchk4
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 0 8 8
EXEC DBMS_STATS.gather_table_stats(NULL,'MYTAB4');
@/tmp/blkchk4
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 0 0 0 8 8 0 0
The existence of the initial extent is not guaranteed. It exists for us because the "CREATE TABLE" statement we used earlier contained a "SEGMENT CREATION IMMEDIATE" clause. Let's recreate the table using "SEGMENT CREATION DEFERRED" instead...
DROP TABLE MYTAB4;
CREATE TABLE "MYTAB4"
( "ID" NUMBER,
"CODE" VARCHAR2(4000),
"SHORT_DESCRIPTION" VARCHAR2(4000),
"LONG_DESCRIPTION" VARCHAR2(4000)
) SEGMENT CREATION DEFERRED
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS";
@/tmp/blkchk4
no rows selected
We also get the same result after gathering stats
We have a couple of ways to materialize a deferred segment. We can add a row, or we can do this...
Note that you need to run this as a privileged user.
BEGIN
DBMS_SPACE_ADMIN.materialize_deferred_segments (
schema_name => 'MYSCHEMA',
table_name => 'MYTAB4',
partition_name => NULL);
END;
/
@/tmp/blkchk4
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 0 0 0 8 8 0 0
We can also "dematerialize" tables with no rows...
Note that you need to run this as a privileged user.
BEGIN
DBMS_SPACE_ADMIN.drop_empty_segments (
schema_name => 'MYSCHEMA',
table_name => 'MYTAB4',
partition_name => NULL);
END;
/
@/tmp/blkchk4
no rows selected
We also get the same result after gathering stats
@/tmp/addrow4
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 0 1 5 8 8 0 1
@/u01/dba/blkspc.sql
SELECT DBMS_ROWID.ROWID_BLOCK_NUMBER(mytab4.rowid) block_id, rowid
FROM mytab4;
Save as:/tmp/extchk.sql
COLUMN owner FORMAT a10
COLUMN segment_name FORMAT a15
COLUMN tablespace_name FORMAT a20
SELECT owner,
segment_name,
tablespace_name,
extent_id,
file_id,
block_id AS start_block,
blocks,
block_id+blocks-1 AS end_block
FROM dba_extents
WHERE segment_name = 'MYTAB4';
ALTER SESSION SET TRACEFILE_IDENTIFIER='BLOCK_DUMP';
ALTER SYSTEM DUMP DATAFILE 4 BLOCK MIN 2560521 BLOCK MAX 2560528;
SELECT * FROM mytab4;
SELECT value FROM v$diag_info WHERE name = 'Diag Trace';
ls | grep BLOCK_DUMP
0:Metadata 1:Metadata 2:Metadata 3:100% free 4:100% free 5:100% free 6:100% free 7:100% free --------------------------------------------------------...
--------------------------------------------------------
Auxillary Map -------------------------------------------------------- Extent 0 : L1 dba: 0x01271209 Data dba: 0x0127120c --------------------------------------------------------
Second Level Bitmap block DBAs -------------------------------------------------------- DBA 1: 0x0127120a
Auxillary Map -------------------------------------------------------- Extent 0 : L1 dba: 0x01271209 Data dba: 0x0127120c --------------------------------------------------------
Second Level Bitmap block DBAs -------------------------------------------------------- DBA 1: 0x0127120a
Itl Xid Uba Flag Lck Scn/Fsc0x01 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.000000000x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000bdba: 0x0127120cdata_block_dump,data header at 0x700010022db2064===============tsiz: 0x1f98hsiz: 0xepbl: 0x700010022db2064 76543210flag=--------ntab=0nrow=0frre=-1fsbo=0xefseo=0x1f98avsp=0x1f8atosp=0x1f8ablock_row_dump:end_of_block_dump
Itl Xid Uba Flag Lck Scn/Fsc0x01 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.000000000x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000bdba: 0x0127120cdata_block_dump,data header at 0x1210ac064===============tsiz: 0x1f98hsiz: 0xepbl: 0x1210ac064 76543210flag=--------ntab=0nrow=0frre=-1fsbo=0xefseo=0x1f98avsp=0x1f8atosp=0x1f8ablock_row_dump:end_of_block_dump
Itl Xid Uba Flag Lck Scn/Fsc0x01 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.000000000x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000bdba: 0x0127120ddata_block_dump,data header at 0x7000100216be064===============tsiz: 0x1f98hsiz: 0xepbl: 0x7000100216be064 76543210flag=--------ntab=0nrow=0frre=-1fsbo=0xefseo=0x1f98avsp=0x1f8atosp=0x1f8ablock_row_dump:end_of_block_dump
Itl Xid Uba Flag Lck Scn/Fsc0x01 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.000000000x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000bdba: 0x0127120ddata_block_dump,data header at 0x1210ac064===============tsiz: 0x1f98hsiz: 0xepbl: 0x1210ac064 76543210flag=--------ntab=0nrow=0frre=-1fsbo=0xefseo=0x1f98avsp=0x1f8atosp=0x1f8ablock_row_dump:end_of_block_dump
Itl Xid Uba Flag Lck Scn/Fsc0x01 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.000000000x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000bdba: 0x0127120edata_block_dump,data header at 0x700010077874064===============tsiz: 0x1f98hsiz: 0xepbl: 0x700010077874064 76543210flag=--------ntab=0nrow=0frre=-1fsbo=0xefseo=0x1f98avsp=0x1f8atosp=0x1f8ablock_row_dump:end_of_block_dump
Itl Xid Uba Flag Lck Scn/Fsc0x01 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.000000000x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000bdba: 0x0127120edata_block_dump,data header at 0x1210ac064===============tsiz: 0x1f98hsiz: 0xepbl: 0x1210ac064 76543210flag=--------ntab=0nrow=0frre=-1fsbo=0xefseo=0x1f98avsp=0x1f8atosp=0x1f8ablock_row_dump:end_of_block_dump
Itl Xid Uba Flag Lck Scn/Fsc0x01 0x000a.00e.012ae506 0x0140082e.1ef2.06 --U- 1 fsc 0x0000.413e56fe0x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000bdba: 0x0127120fdata_block_dump,data header at 0x70001007e484064===============tsiz: 0x1f98hsiz: 0x14pbl: 0x70001007e484064 76543210flag=--------ntab=1nrow=1frre=-1fsbo=0x14fseo=0x1e8aavsp=0x1e76tosp=0x1e760xe:pti[0] nrow=1 offs=00x12:pri[0] offs=0x1e8ablock_row_dump:tab 0, row 0, @0x1e8atl: 270 fb: --H-FL-- lb: 0x1 cc: 4col 0: [ 3] c2 33 02col 1: [10] 34 37 55 51 56 35 4e 49 59 38col 2: [50] 68 43 4a 75 56 61 49 57 78 63 46 67 53 53 55 56 6c 4e 43 48 4f 6d 46 6c 41 49 50 56 70 50 46 78 67 7a 52 72 58 42 7a 6a 6a 71 43 4b 49 61 67 64 41 77col 3: [200] 51 77 4e 44 78 69 4c 59 51 4c 70 4e 55 64 63 58 59 71 55 55 45 56 45 45 53 58 4d 6d 4f 41 45 66 53 43 53 43 66 72 59 69 44 4e 50 54 4d 4d 4e 61 41 63 76 6c 7a 45 41 69 55 5a 70 4d 69 51 45 69 4f 49 50 7a 56 41 46 62 44 6a 6f 52 69 52 66 54 41 48 58 4e 46 46 4e 6d 63 49 4f 4f 49 63 51 52 57 66 54 69 4d 53 43 45 4d 6a 43 68 57 67 42 55 68 4d 61 6f 57 53 56 72 51 65 4f 64 64 57 74 61 4f 49 7a 76 6b 68 59 4d 72 61 76 63 6d 50 47 4b 56 6f 43 65 75 6d 4a 6e 4a 68 4c 71 6f 76 65 61 72 43 63 48 4f 5a 67 5a 6e 56 51 49 68 62 42 6c 53 52 48 59 49 43 4e 58 71 4c 6c 5a 64 44 6e 6a 63 58 6a 41 70 6e 6a 49end_of_block_dump
Itl Xid Uba Flag Lck Scn/Fsc0x01 0x000a.00e.012ae506 0x0140082e.1ef2.06 --U- 1 fsc 0x0000.413e56fe0x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000bdba: 0x0127120fdata_block_dump,data header at 0x1210ac064===============tsiz: 0x1f98hsiz: 0x14pbl: 0x1210ac064 76543210flag=--------ntab=1nrow=1frre=-1fsbo=0x14fseo=0x1e8aavsp=0x1e76tosp=0x1e760xe:pti[0] nrow=1 offs=00x12:pri[0] offs=0x1e8ablock_row_dump:tab 0, row 0, @0x1e8atl: 270 fb: --H-FL-- lb: 0x1 cc: 4col 0: [ 3] c2 33 02col 1: [10] 34 37 55 51 56 35 4e 49 59 38col 2: [50] 68 43 4a 75 56 61 49 57 78 63 46 67 53 53 55 56 6c 4e 43 48 4f 6d 46 6c 41 49 50 56 70 50 46 78 67 7a 52 72 58 42 7a 6a 6a 71 43 4b 49 61 67 64 41 77col 3: [200] 51 77 4e 44 78 69 4c 59 51 4c 70 4e 55 64 63 58 59 71 55 55 45 56 45 45 53 58 4d 6d 4f 41 45 66 53 43 53 43 66 72 59 69 44 4e 50 54 4d 4d 4e 61 41 63 76 6c 7a 45 41 69 55 5a 70 4d 69 51 45 69 4f 49 50 7a 56 41 46 62 44 6a 6f 52 69 52 66 54 41 48 58 4e 46 46 4e 6d 63 49 4f 4f 49 63 51 52 57 66 54 69 4d 53 43 45 4d 6a 43 68 57 67 42 55 68 4d 61 6f 57 53 56 72 51 65 4f 64 64 57 74 61 4f 49 7a 76 6b 68 59 4d 72 61 76 63 6d 50 47 4b 56 6f 43 65 75 6d 4a 6e 4a 68 4c 71 6f 76 65 61 72 43 63 48 4f 5a 67 5a 6e 56 51 49 68 62 42 6c 53 52 48 59 49 43 4e 58 71 4c 6c 5a 64 44 6e 6a 63 58 6a 41 70 6e 6a 49end_of_block_dump
Itl Xid Uba Flag Lck Scn/Fsc0x01 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.000000000x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000bdba: 0x01271210data_block_dump,data header at 0x700010082b96064===============tsiz: 0x1f98hsiz: 0xepbl: 0x700010082b96064 76543210flag=--------ntab=0nrow=0frre=-1fsbo=0xefseo=0x1f98avsp=0x1f8atosp=0x1f8ablock_row_dump:end_of_block_dump
Itl Xid Uba Flag Lck Scn/Fsc0x01 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.000000000x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000bdba: 0x01271210data_block_dump,data header at 0x1210ac064===============tsiz: 0x1f98hsiz: 0xepbl: 0x1210ac064 76543210flag=--------ntab=0nrow=0frre=-1fsbo=0xefseo=0x1f98avsp=0x1f8atosp=0x1f8ablock_row_dump:end_of_block_dumpEnd dump data blocks tsn: 4 file#: 4 minblk 2560521 maxblk 2560528
So, the 5 blocks shown in USER_TABLES reflects the number of "trans data" blocks in the initial 8 block extent.
We can get a breakdown of block types from our tracefile...
grep ^frmt tracefile.trc
Let's add rows and watch. Remember we estimate 25 rows per block. I'll only show the interesting output below...
@/tmp/addrow4
Apparently our estimate of 25 rows per block was incorrect...
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 1 1 5 8 8 0 25
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 1 2 5 8 8 0 27
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 2 3 5 8 8 0 53
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 3 4 5 8 8 0 79
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 4 5 5 8 8 0 105
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 5 6 13 16 16 0 131
@/tmp/extchk.sql
ALTER SESSION SET TRACEFILE_IDENTIFIER='BLOCK_DUMP';
ALTER SYSTEM DUMP DATAFILE 4 BLOCK MIN 2560521 BLOCK MAX 2560536;
You should also be able to see the contents of your table in the extents.
0:Metadata 1:Metadata 2:Metadata 3:100% free 4:100% free 5:100% free 6:100% free 7:100% free --------------------------------------------------------
0:Metadata 1:Metadata 2:Metadata 3:FULL 4:FULL 5:FULL 6:FULL 7:FULL 8:100% free 9:100% free 10:100% free 11:100% free 12:100% free 13:100% free 14:100% free 15:100% free --------------------------------------------------------
grep ^frmt tracefile.trc
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 13 13 13 16 16 0 338
@/u01/dba/blkspc.sql
@/tmp/addrow4
@/u01/dba/blkspc.sql
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 13 14 20 24 24 0 339
@/tmp/extchk.sql
ALTER SESSION SET TRACEFILE_IDENTIFIER='BLOCK_DUMP';
ALTER SYSTEM DUMP DATAFILE 4 BLOCK MIN 2560521 BLOCK MAX 2560544;
grep ^frmt tracefile.trc
@/tmp/addrow4
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 20 21 28 32 32 0 521
@/tmp/addrow4
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 29 29 35 40 40 0 729
@/tmp/addrow4
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 36 36 43 48 48 0 911
There seems to be a pattern. 8 blocks per extent. Every other extent gets an additional "first level bitmap block". But, as we know from our orginal tables, after 16 extents, Oracle seems to increase the extent size. Let's reproduce that. We should fill 16 extents at 3068 rows. 26 rows per block, with a pattern of 5,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8 usable blocks across 16 extents.... ((8*8)+(7*7)+5)*26... (64+49+5)*26... 118*26 = 3068
@/tmp/addrow4
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 120 118 118 128 128 0 3068
@/tmp/addrow4
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 120 119 244 256 256 0 3069
@/tmp/extchk.sql
@/tmp/addrow4
TABLE_NAME EST_BLOCKS ROWID_BLOCKS TABLE_BLOCKS EXTENT_BLOCKS SEGMENT_BLOCKS EMPTY_BLOCKS TABLE_ROWS------------------------------ ---------- ------------ ------------ ------------- -------------- ------------ ----------MYTAB4 391 385 496 512 512 0 10000
@/tmp/extchk.sql
I have had ocassions where the tracefile seems to contain duplicate rows which throws off the counts below. The checkpoint may be required to ensure consistent data.
ALTER SYSTEM CHECKPOINT;
ALTER SESSION SET TRACEFILE_IDENTIFIER='BLOCK_DUMP';
ALTER SYSTEM DUMP DATAFILE 4 BLOCK MIN 2560521 BLOCK MAX 2561032;
grep ^buffer tracefile.trc | sort | uniq | wc -l
512
grep ^frmt tracefile.trc | grep "trans data" | wc -l
496
@/tmp/blkchk.sql
Argh! Why is table_blocks different?
Unformatted Blocks 0Full Blocks 384FS1 Blocks (0-25) 0FS2 Blocks (25-50) 1FS3 Blocks (50-75) 0FS4 Blocks (75-100)0total 385
Unformatted Blocks 62Full Blocks 384FS1 Blocks (0-25) 0FS2 Blocks (25-50) 1FS3 Blocks (50-75) 0FS4 Blocks (75-100)49total 496
Don't forget to delete the tracefiles before continuing.
ls *BLOCK_DUMP*
rm *BLOCK_DUMP*
*** UNDER CONSTRUCTION BELOW THIS POINT *** TODO
DELETE FROM mytab1 WHERE MOD(id, 2) = 0;
DELETE FROM mytab2 WHERE id < 5001;
DELETE FROM mytab3 WHERE id > 5000;
COMMIT;
EXEC DBMS_STATS.gather_table_stats(NULL,'MYTAB1');
EXEC DBMS_STATS.gather_table_stats(NULL,'MYTAB2');
EXEC DBMS_STATS.gather_table_stats(NULL,'MYTAB3');
Let's delete 50% of the rows from each table, but in different ways....
The first option should leave all the blocks 50% utilised.
The second option should leave empty blocks at the start of the table.
The third option should leave empty blocks at the end of the table.
We should gather stats when we're done so that that the figures in user_tables are updated.
SELECT table_name,
num_rows,
avg_row_len,
blocks,
pct_free,
empty_blocks,
ROUND(num_rows*avg_row_len/8192*100/(100-pct_free)*1.08) AS est_blocks
FROM user_tables;
Let's see how many blocks our tables are now using.
The actual number of blocks remains the same but our estimate approximately halves.
INSERT INTO &mytab
SELECT level+5000 AS id,
DBMS_RANDOM.string('x',10) AS code,
DBMS_RANDOM.string('a',50) AS short_description,
DBMS_RANDOM.string('a',200) AS long_description
FROM dual
CONNECT BY level <= 5000;
COMMIT;
EXEC DBMS_STATS.gather_table_stats(NULL,'MYTAB1');
EXEC DBMS_STATS.gather_table_stats(NULL,'MYTAB2');
EXEC DBMS_STATS.gather_table_stats(NULL,'MYTAB3');
Let's add 5000 more rows to each table.
SELECT table_name,
num_rows,
avg_row_len,
blocks,
pct_free,
ROUND(num_rows*avg_row_len/8192*100/(100-pct_free)*1.08) AS est_blocks
FROM user_tables;
Let's see how many blocks our tables are now using.
This shows us that blocks get reused. i..e. before reclaiming space, be sure you're not just wasting time. Reclaim space when you delete data and don't expect to add new data.
DELETE FROM mytab1 WHERE MOD(id, 2) = 0;
DELETE FROM mytab2 WHERE id < 5001;
DELETE FROM mytab3 WHERE id > 5000;
COMMIT;
EXEC DBMS_STATS.gather_table_stats(NULL,'MYTAB1');
EXEC DBMS_STATS.gather_table_stats(NULL,'MYTAB2');
EXEC DBMS_STATS.gather_table_stats(NULL,'MYTAB3');
So, let's delete 50% again
DROP TABLE MYTAB1;
DROP TABLE MYTAB2;
DROP TABLE MYTAB3;
Cleanup
SELECT COUNT(1)
FROM( SELECT DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid)
FROM mytab1
GROUP BY DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID))
SELECT SUM(BLOCKS) FROM user_segments WHERE segment_name = 'MYTAB1'
SELECT DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid) AS Block, COUNT(1) AS ROWCNT
FROM mytab1
GROUP BY DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
SELECT table_name, num_rows, avg_row_len, blocks, pct_free,
ROUND(num_rows*avg_row_len/8192*100/(100-pct_free)*1.08) est_blocks
FROM user_tables;
SELECT table_name, num_rows, avg_row_len, blocks
FROM user_tables;
Bibliography & References
CREATE TABLEhttps://docs.oracle.com/cd/E11882_01/server.112/e41084/statements_7002.htm (11.2)https://stackoverflow.com/questions/1008248/how-do-i-use-create-or-replacehttps://oracle-base.com/articles/11g/segment-creation-on-demand-11gr2
DROP TABLEhttps://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9003.htmhttps://dbtut.com/index.php/2018/12/11/how-to-recover-dropped-tables-with-oracle-recyclebin/
TRUNCATEhttps://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_10007.htmhttps://docs.oracle.com/html/E25494_01/general003.htm
MODIFYhttps://www.oracletutorial.com/oracle-basics/oracle-alter-table-modify-column/
ROW MOVEMENThttps://stackoverflow.com/questions/19430145/what-is-the-impact-of-enabling-disabling-row-movement-in-oracle-10g
SHRINK SPACEhttps://oracle-base.com/articles/misc/alter-table-shrink-space-online https://smarttechways.com/2019/11/08/check-progress-and-status-of-alter-shrink-space-command/https://asktom.oracle.com/pls/apex/f%3Fp%3D100:11:::::P11_QUESTION_ID:77579471985961https://www.dbi-services.com/blog/how-much-free-space-can-be-reclaimed-from-a-segment/
DBMS_SPACEhttps://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_space.htmhttps://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_SPACE.html
DBMS_SPACE_ADMINhttps://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_SPACE_ADMIN.html
Blocks/Extentshttps://asktom.oracle.com/ords/f?p=100:11:::::P11_QUESTION_ID:9531625700346446827 (blocks column in dba_tables)(2) https://asktom.oracle.com/ords/f?p=100:11:0::::P11_QUESTION_ID:492636200346818072 (HWM) (see answer at "April 05, 2010 - 9:45 am UTC" re: EMPTY_BLOCKS)https://asktom.oracle.com/ords/f?p=100:11:0::::P11_QUESTION_ID:5350053031470#1752788300346387205 (SHOW_SPACE)https://forums.oracle.com/ords/apexds/post/why-the-total-number-of-blocks-is-a-little-different-from-u-5274https://forums.oracle.com/ords/apexds/post/how-much-a-data-block-is-full-7219https://forums.oracle.com/ords/apexds/post/extent-block-id-and-parallel-processing-5780http://www.juliandyke.com/Diagnostics/Dumps/DatabaseBlocks.phphttp://www.juliandyke.com/Internals/BlockTypes.phphttps://www.oracledistilled.com/oracle-database/internals/formatted-data-block-dumps/https://iamdbablog.wordpress.com/2018/06/23/de-mystifying-the-oracle-data-block/https://renenyffenegger.ch/notes/development/databases/Oracle/SQL/statement/nouns/system/alter/dump/datafile/https://www.morganslibrary.org/reference/dump_ora.htmlhttps://www.peasland.net/2011/08/01/dumping-data-blocks/https://stackoverflow.com/questions/64284627/how-to-determine-the-empty-logical-data-block-for-a-tablehttps://stackoverflow.com/questions/26076377/segments-extents-and-data-blocks-in-the-oracle-database-architecture-how-tohttps://stackoverflow.com/questions/28884613/find-block-number-of-a-table-in-oracle-databasehttps://dba.stackexchange.com/questions/107465/oracle-table-and-number-of-blocks#107467