Monday, April 13, 2026

Oracle Table DDL Change Notification Feature

Oracle Table DDL Change Notification, which is part of a broader Oracle technology called Continuous Query Notification (CQN)

 

History:

One of traditional solutions to controlling schema changes without interruptions in oracle to releasing and developing applications backend is Edition Based Redefinition (EBR) feature that was available from 11g.

Before explanations of TDCN, is better to have a review on EBR:

Overview of Edition-Based Redefinition

To upgrade an application while it is in use, you must copy the database objects that comprise the database component of the application and redefine the copied objects in isolation. Your changes do not affect users of the application—they can continue to run the unchanged application. When you are sure that your changes are correct, you make the upgraded application available to all users.

Using EBR means using one or more of its component features. The features you use, and the downtime, depend on these factors:

  • What kind of database objects you redefine
  • How available the database objects must be to users while you are redefining them
  • Whether you make the upgraded application available to some users while others continue to use the older version of the application

You always use the edition feature to copy the database objects and redefine the copied objects in isolation; that is why the procedure that this chapter describes for upgrading applications online is called edition-based redefinition (EBR).

If every object that you will redefine is editioned, then the edition is the only feature you use.

Tables are not editioned objects. If you change the structure of one or more tables, then you also use the editioning view feature.

If other users must be able to change data in the tables while you are changing their structure, then you also use forward crossedition triggers. If the pre- and post-upgrade applications will be in ordinary use at the same time (hot rollover), then you also use reverse crossedition triggers. Crossedition triggers are not a permanent part of the application—you drop them when all users are using the post-upgrade application.

An EBR operation that you can perform on an application in one edition while the application runs in other editions is a live operation.

 

For more info you can visit:

https://docs.oracle.com/en/database/oracle/oracle-database/19/adfns/editions.html#GUID-C213A6AA-5427-4426-80B5-D5301386253E

https://docs.oracle.com/en/database/oracle/oracle-database/26/adfns/editions.html#GUID-F0D940E0-618D-4656-982E-1C5E49FCCD42

But in this topic, I will present a new feature to alerting to the application when a DDL change occurred! This can help obviously be helpful when we have a mid-layer cache in application layer that cached Schema objects structure on startup and any change in table structure can lead a downtime.        

v  But in this topic, I will introduce a new feature to send an alert to the application when a DDL change occurs! This feature can be clearly useful when we have a middle-tier cache in the application layer that stores the structure of Schema objects at startup and any change in the table structure can lead to a crash.

Oracle introduce a feature called to Table DDL Change Notification to handle these changes, by this solution your application be aware for any schema changes dynamically.

In simple terms, this feature allows an application (like a Java, C#, or Python program) to be automatically notified by the database whenever the structure (the DDL) of a specific table changes, this feature is available for both OCI and On-Premises deployments.

 Overview of Table DDL Change Notification

Table DDL Change Notification provides Oracle Call Interface (OCI) clients an efficient mechanism to subscribe for DDL notifications on tables of interest. Table DDL Change Notification ensures that applications are notified when DDL statements make changes to a table. Changes are captured as DDL events and these events are processed asynchronously without blocking the user DML activity.

Applications seeking table metadata can use Table DDL Change Notification instead of continuously polling for DDL changes, and benefit from reduced network round trips. Table DDL Change Notification is also useful for OCI clients of the database that cache table metadata in the middle tier to avoid round trips to the database.

An OCI client can register to receive table DDL change notification for any of the following:

  • A list of tables
  • A list of schemas
  • All tables in a database

Table DDL Change Notification is included in Oracle AI Database 26ai, and later releases.

Benefits of Table DDL Change Notification

Table DDL Change Notification works without affecting the application activity in the database.

The following are some of the benefits of using Table DDL Change Notification:

  • Notifications can be enabled on highly active tables.
  • Registering and event processing do not block the DDL activity on a table.
  • Registrations and notifications are processed with minimal overhead on DML activity and without invalidating table cursors.
  • Registrations are processed quickly while allowing concurrent registrations of other client applications.
  • Client applications can dynamically subscribe to any number of additional tables or schemas.

Features of Table DDL Change Notification

The following are the features of Table DDL Change Notification:

  • DDL events are processed asynchronously without blocking user DML queries.
  • The client application can choose to include optional events, such as Partition Maintenance Operations (PMOP) or truncate, which are not available for event registration by default.
  • DDL notifications are staged in the System Global Area (SGA). If an instance restarts, unprocessed notifications in SGA are lost. Persistent events are not supported.
  • On failover of the database, any undelivered events in SGA are lost. The client caches must be invalidated on failover to avoid reconciliation issues in the table metadata due to lost notifications.
  • For logical standby and physical standby, the OCI client must register again over the new primary database to resume notification.

What is the Core Problem it Solves?

Imagine you have an application that caches data from an Oracle table to improve performance. For example, a web application might load a list of product categories from a PRODUCT_CATEGORIES table into its memory when it starts up.

Now, what happens if a Database Administrator (DBA) does one of the following things to that table?

  • ALTER TABLE PRODUCT_CATEGORIES ADD (IS_FEATURED VARCHAR2(1)); (Adds a new column)
  • ALTER TABLE PRODUCT_CATEGORIES MODIFY (CATEGORY_NAME VARCHAR2(200)); (Changes a column's size)
  • ALTER TABLE PRODUCT_CATEGORIES DROP COLUMN OBSOLETE_FLAG; (Drops a column)
  • TRUNCATE TABLE PRODUCT_CATEGORIES; (Deletes all data instantly)
  • DROP TABLE PRODUCT_CATEGORIES; (Deletes the entire table)

The application's in-memory cache is now stale and incorrect. It doesn't know about the new IS_FEATURED column, or it might crash trying to insert a 150-character category name into a field it thinks is still only 100 characters long. The application is now out of sync with the database schema.

Without this feature, the only way to fix this is to restart the application or build a complex, manual process to signal a refresh.

How Does Table DDL Change Notification Solve This?

This feature provides an elegant, push-based solution. Instead of the application constantly checking if the table has changed (polling), the database itself tells the application when a change happens.

  1. The Application Registers its Interest:

o   The application (through a database driver like JDBC for Java) creates a special "notification registration" object.

o   It tells the database, "I am interested in any DDL changes made to the SCOTT.EMP table."

o   It also provides a "callback" method—a function in the application's code that the database driver should execute when a notification arrives.

  1. A DDL Change Occurs:

o   A DBA or another process executes a command like: ALTER TABLE SCOTT.EMP ADD (COMMISSION NUMBER);

Ø  The Database Sends a Notification:

o   The Oracle database detects this DDL change on the SCOTT.EMP table.

o   It sees that an application has registered interest in this specific event.

o   The database generates a notification message and sends it over the network to the application's database driver. This message contains details about what happened (e.g., EVENT_TYPE=ALTER, TABLE_NAME=EMP).

  1. The Application Reacts:

o   The application's database driver receives the notification.

o   It automatically invokes the "callback" function that the application registered in Step 1.

o   Inside this function, the application developer writes code to handle the event. The typical action is to invalidate the cache. The application would discard its old, stale copy of the EMP table's structure and data, and then re-query the database to fetch the fresh information.

Registering for Table DDL Change Notification

A client can register for DDL change notifications at the table level or the schema level (for schema-wide table DDL events).

To register for notifications, you must fulfill the following conditions:

  • You must be a non-SYS user.
  • You must have SELECT ANY TABLE privileges.
  • You must have CHANGE NOTIFICATION privileges.
  • Set up a TCP or IPC listener for OCI DDL Notification client connections, and set the local_listener parameter of the ROOT container to this listener. The local_listener parameter can be set in the initialization parameter file (init.ora), or as follows:

ALTER SYSTEM SET LOCAL_LISTENER=listener_name;

ALTER SYSTEM REGISTER;

Note:If a user loses the required privileges, the user's table or schema-level registrations are implicitly unregistered.

Monitoring Table DDL Change Notification

The registration identifier (ID) uniquely identifies a subscription. A client can register on multiple tables or schemas in the database using the same subscription (meaning, registration ID). The following views can be queried by the registration ID to monitor table DDL events received for a subscription.

Step-by-step guide to implementing and using the Table DDL Change Notification feature in Oracle.

This guide will walk you through:

  1. Setting up the database user and permissions.
  2. Creating a sample table to monitor.
  3. Writing a complete Java application that registers for notifications and reacts to them.
  4. Triggering a DDL change and observing the application's response.

We will use Java with the Oracle JDBC driver, as it's the most common platform for this feature.

 

Step 1: Database Setup (as a DBA)

First, you need a database user with the necessary privileges. It's best practice not to use SYS or SYSTEM for this.

  1. Create a dedicated user. Let's call our user app_user.

-- Connect as SYS or a user with DBA privileges

CREATE USER app_user IDENTIFIED BY YourStrongPassword;

ALTER USER app_user QUOTA UNLIMITED ON users;

  1. Grant necessary privileges. The application user needs basic connection privileges plus the special CHANGE NOTIFICATION privilege, which is the key to this entire feature.

-- Grant connection and resource creation rights

GRANT CREATE SESSION, CREATE TABLE, CREATE PROCEDURE TO app_user;

-- *** This is the most important grant for this feature ***

GRANT CHANGE NOTIFICATION TO app_user;

 

Step 2: Create the Target Table (as app_user)

Now, connect as the new app_user and create the table that your application will monitor.

  1. Connect as app_user in SQL*Plus, SQL Developer, or your favorite SQL tool.
  2. Create a simple table. We'll create an EMPLOYEES table.

CREATE TABLE employees (

    emp_id    NUMBER PRIMARY KEY,

    emp_name  VARCHAR2(100),

    hire_date DATE

);

-- Insert some sample data (optional, but good for context)

INSERT INTO employees (emp_id, emp_name, hire_date) VALUES (101, 'Alice', SYSDATE);

COMMIT;

At this point, the database is ready. The app_user owns a table called employees and has the permission to register for notifications on it.

 

 

 

 

Step 3: Create the Java Application

This is the core of the implementation. We will write a Java program that connects to the database, registers a listener on the employees table, and then waits indefinitely for notifications.

Prerequisites:

  • Java Development Kit (JDK) 8 or higher.
  • An IDE like IntelliJ IDEA, Eclipse, or VS Code.
  • The Oracle JDBC driver (ojdbc8.jar or higher) and the Notification driver (ons.jar). If you use a build tool like Maven, this is much easier.

Maven Dependencies (Recommended)

If you're using Maven, add these dependencies to your pom.xml file. This is the easiest way to manage the required JARs.

XML

<dependencies>

    <!-- Oracle JDBC Driver -->

    <dependency>

        <groupId>com.oracle.database.jdbc</groupId>

        <artifactId>ojdbc11</artifactId>

        <version>21.5.0.0</version> <!-- Use a recent version -->

    </dependency>

    <!-- Oracle Notification Service (ONS) - required for notifications -->

    <dependency>

        <groupId>com.oracle.database.jdbc</groupId>

        <artifactId>ons</artifactId>

        <version>21.5.0.0</version>

    </dependency>

</dependencies>

 

 

Java Code (DDLNotificationListener.java)

Here is the complete, commented Java code.

Java

import java.sql.Connection;

import java.sql.ResultSet;

import java.sql.Statement;

import java.util.Properties;

 

import oracle.jdbc.OracleConnection;

import oracle.jdbc.OracleStatement;

import oracle.jdbc.dcn.DatabaseChangeEvent;

import oracle.jdbc.dcn.DatabaseChangeListener;

import oracle.jdbc.dcn.DatabaseChangeRegistration;

import oracle.jdbc.dcn.TableChangeDescription;

 

public class DDLNotificationListener {

 

    // --- Configuration: Change these to match your database ---

    private static final String DB_URL = "jdbc:oracle:thin:@//your_db_host:1521/your_service_name";

    private static final String DB_USER = "app_user";

    private static final String DB_PASSWORD = "YourStrongPassword";

 

    public static void main(String[] args) {

        try {

            new DDLNotificationListener().run();

        } catch (Exception e) {

            System.err.println("An error occurred: " + e.getMessage());

            e.printStackTrace();

        }

    }

 

    private void run() throws Exception {

        System.out.println("Connecting to the database...");

        OracleConnection conn = connect();

 

        // 1. Create Notification Registration Properties

        Properties props = new Properties();

        // We are interested in DDL changes, not DML (data changes)

        props.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS, "false");

        props.setProperty(OracleConnection.DCN_NOTIFY_DDL, "true"); // <-- The key property!

 

        // 2. Create a Database Change Registration

        DatabaseChangeRegistration dcr = conn.registerDatabaseChangeNotification(props);

        System.out.println("Created database change registration.");

 

        try {

            // 3. Add a listener to the registration

            MyDDLListener listener = new MyDDLListener();

            dcr.addListener(listener);

            System.out.println("Added a listener to the registration.");

 

            // 4. Associate the registration with the target table

            // We do this by creating a statement and associating it with the registration.

            // The query itself is just a dummy to specify the table we're watching.

            try (Statement stmt = conn.createStatement()) {

                ((OracleStatement) stmt).setDatabaseChangeRegistration(dcr);

                try (ResultSet rs = stmt.executeQuery("SELECT * FROM employees")) {

                    // We don't need to process the result set.

                    // The act of executing the query on the registered statement

                    // is what links the 'employees' table to our notification.

                }

            }

            System.out.println("Associated the 'employees' table with the notification listener.");

            System.out.println("----------------------------------------------------------");

            System.out.println("Application is now running. Waiting for DDL changes on the EMPLOYEES table...");

            System.out.println("Go to your SQL tool and run an ALTER, TRUNCATE, or DROP command on the table.");

 

            // 5. Keep the application alive to listen for notifications

            // In a real app, this would be a server or a background thread.

            while (true) {

                Thread.sleep(5000);

            }

 

        } finally {

            // In a real app, you would unregister and close the connection gracefully.

            System.out.println("Unregistering notification and closing connection.");

            conn.unregisterDatabaseChangeNotification(dcr);

            conn.close();

        }

    }

    private OracleConnection connect() throws Exception {

        return (OracleConnection) java.sql.DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);

    }

    // Inner class that defines what to do when a notification is received

    class MyDDLListener implements DatabaseChangeListener {

        @Override

        public void onDatabaseChangeNotification(DatabaseChangeEvent event) {

            System.out.println("\n!!! NOTIFICATION RECEIVED !!!");

            System.out.println("Event Details: " + event.toString());

            // Get specific details about the table DDL change

            TableChangeDescription[] tableChanges = event.getTableChangeDescription();

            if (tableChanges != null && tableChanges.length > 0) {

                for (TableChangeDescription change : tableChanges) {

                    System.out.println("  -> Table Name: " + change.getTableName());

                    System.out.println("  -> DDL Operation Type: " + change.getDDLOperationType());

                    // --- THIS IS WHERE YOU WOULD PUT YOUR APPLICATION LOGIC ---

                    // For example: invalidate a cache, reload configuration, etc.

                    System.out.println("  -> ACTION: Invalidating cache for " + change.getTableName());

                }

            }

            System.out.println("----------------------------------------------------------");

            System.out.println("Resuming wait for next notification...");

        }

    }

}

 

Step 4: Run the Application and Trigger the Notification

  1. Compile and Run the Java Application. Execute the main method of the DDLNotificationListener class. You should see the following output in your console, and then the program will pause, waiting:

Connecting to the database...

Created database change registration.

Added a listener to the registration.

Associated the 'employees' table with the notification listener.

----------------------------------------------------------

Application is now running. Waiting for DDL changes on the EMPLOYEES table...

Go to your SQL tool and run an ALTER, TRUNCATE, or DROP command on the table.

  1. Trigger a DDL Change. Go back to your SQL tool (connected as app_user or another user with privileges on the table) and execute a DDL command on the employees table.

Example 1: Add a column

ALTER TABLE employees ADD (department_id NUMBER);

  1. Observe the Java Application's Output. Almost immediately after you execute the ALTER TABLE command, you will see new output in your Java application's console. This is the database pushing the notification to your running program.

!!! NOTIFICATION RECEIVED !!!

Event Details: oracle.jdbc.dcn.DatabaseChangeEvent[CONNECTION_ID=... EVENT_TYPE=DCN_EVENT_TYPE_DDL]

  -> Table Name: APP_USER.EMPLOYEES

  -> DDL Operation Type: ALTER

  -> ACTION: Invalidating cache for APP_USER.EMPLOYEES

----------------------------------------------------------

Resuming wait for next notification...

  1. Try another DDL command. Now, truncate the table.

TRUNCATE TABLE employees;

Again, the Java application will instantly receive a notification:

!!! NOTIFICATION RECEIVED !!!

Event Details: oracle.jdbc.dcn.DatabaseChangeEvent[CONNECTION_ID=... EVENT_TYPE=DCN_EVENT_TYPE_DDL]

  -> Table Name: APP_USER.EMPLOYEES

  -> DDL Operation Type: TRUNCATE

  -> ACTION: Invalidating cache for APP_USER.EMPLOYEES

----------------------------------------------------------

Resuming wait for next notification...

This completes the end-to-end process. You have successfully configured the database and built an application that can react in real-time to structural changes in a database table.

***************************************

Sunday, April 5, 2026

Tablespace Shrink in 26ai(A good enhancement)

 Tablespace Shrink in 26ai

Background:

Traditionally in Oracle, shrinking is possible at:

  • Segment level → ALTER TABLE ... SHRINK SPACE, ALTER INDEX ... SHRINK SPACE
  • Datafile level → ALTER DATABASE DATAFILE ... RESIZE
  • Export & Re-Import → Expdp and Impdp
  • Movement: → Move into another TBS.

But shrinking an entire tablespace automatically was not directly possible until recent improvements in Oracle 26c/26ai, where DBMS_SPACE has been extended.

 

In this post, I will explain 26ai's features regarding table space shrinking capability for Oracle tablespaces.


A first use case is illustrated in the following scenario:
Imagine that database objects have been removed, resulting in unused space within the tablespace. In such situations, you may wish to reduce the actual data file size to free up space on your disks. With 26ai, you can now reorganize tablespaces both SMALLFILE or BIGFILE tablespaces while resizing the corresponding data file(s) This is done by moving objects within the tablespace, which can be done either online or offline.

Just a reminder:


A bigfile tablespace is defined as a tablespace that has one large data file (up to 4G blocks). Traditional smallfile tablespaces, in contrast, can contain multiple data files, but the files cannot be as large. And starting with Oracle Database 26ai, the BIGFILE functionality is the default for SYSAUX, SYSTEM, and USER tablespaces.

You can use the SHRINK_TABLESPACE procedure from the DBMS_SPACE package to resize both BIGFILE and SMALLFILE tablespaces or analyze them prior to resizing. This procedure utilizes online DDL to reorganize the objects within the data file, and after the objects have been successfully reorganized, it executes a resize of the data file. 

The usage is quite simple: You only need to specify the tablespace name and the shrink mode such as 

  • TS_MODE_ANALYZE for an analyze mode
  • TS_MODE_SHRINK, the default
  • TS_MODE_SHRINK_FORCE, this will move objects online by default, but if the online move fails, it will attempt to move them offline.

The following code snippet illustrates the arguments of the new procedure. A complete description can be found in the PL/SQL Packages and Types Reference DBMS_SPACE.

DBMS_SPACE.SHRINK_TABLESPACE

  • Allows you to shrink free space inside a tablespace automatically, instead of manually resizing each datafile.
  • Internally, Oracle will move segments toward the beginning of the datafile(s), release unused extents, and then reduce the size.

 

PROCEDURE SHRINK_TABLESPACE

   Argument Name       Type       In/Out Default?

   ---------------------- -------------- ------------ --------

   TS_NAME                       VARCHAR2         IN

   SHRINK_MODE             NUMBER            IN     DEFAULT

   TARGET_SIZE                 NUMBER            IN     DEFAULT

   SHRINK_RESULT           CLOB                   OUT

 

 

SHRINK_TABLESPACE Procedure Parameters

Parameter

Description

ts_name

The name of the tablespace to be analyzed or resized

shrink_mode

The shrink mode to execute. The values are:

TS_SHRINK_MODE_ANALYZE

TS_SHRINK_MODE_ONLINE

TS_SHRINK_MODE_AUTO

TS_SHRINK_MODE_OFFLINE

The default mode is TS_SHRINK_MODE_ONLINE which moves objects online by default, except for index-organized tables. TS_SHRINK_MODE_AUTO will move objects online by default, but if the online move fails, it will attempt to move them offline. TS_SHRINK_MODE_OFFLINE offers the best shrink outcome and performance.

target_size

The desired tablespace size specified in bytes. The default value is TS_TARGET_MAX_SHRINK.

shrink_result

Output result of the procedure returned as a CLOB.

The output results for TS_SHRINK_MODE_ONLINE include:

total number and size of moved objects

original and new datafile size

process time

The output results for TS_SHRINK_MODE_ANALYZE include:

list of movable objects

total number and size of movable objects

suggested target size

process time

The following code snippet shows a first example.

The end result is a CLOB and you may view it e.g. in SQL*Plus.

 

SQL> set long 20000

SQL> variable result clob

SQL> col result format a200

SQL> execute DBMS_SPACE.SHRINK_TABLESPACE ('TEST_TBS', SHRINK_MODE => DBMS_SPACE.TS_MODE_ANALYZE, SHRINK_RESULT =>: result);

PL/SQL procedure successfully completed.

SQL> print result

RESULT

----------------------------------------------------------------------------------

Movable Objects:

1. {RECLAIM_USER.T2 | type: TABLE | blocks: 20480 | tablespace_name: TEST_TBS}

2. {RECLAIM_USER.T2_PK | type: INDEX | blocks: 32 | tablespace_name: TEST_TBS}

Total Movable Objects: 2

Total Movable Size (GB): .16

Original Datafile Size (GB): 1.01

Suggested Target Size (GB): .38

Process Time: +00 00:00:01.669704

Data dictionary views such as DBA_FREE_SPACE and USER_SEGMENTS and the "Tablespace Content" View from Oracle Enterprise Manager Cloud control will be used to demonstrate the process.

 

Setup and example

You can also repeat the following example in the available 26ai database FREE version or in Oracle 26ai Database Cloud, on Exadata or ODA. 

Let's connect to one PDB and create a BIGFILE tablespace named TEST_TBS. The new user RECLAIM_USER will execute the user commands.


SQL> drop tablespace TEST_TBS including contents and datafiles;

Tablespace dropped.


SQL> drop user reclaim_user;

User dropped.


SQL> create bigfile tablespace TEST_TBS datafile size 10M autoextend on next 5M;

Tablespace created.


SQL> create user reclaim_user identified by reclaim default tablespace TEST_TBS quota unlimited on TEST_TBS;

User created.


SQL> grant connect, resource, dba to reclaim_user;

Grant succeeded.


Connect to user RECLAIM_USER and create the following 2 tables. Then insert some rows.


SQL> connect reclaim_user/reclaim@<servicename>


SQL> create table t1 (

     id   number,

     col1 varchar2(4000),

     col2 varchar2(4000),

     constraint t1_pk primary key (id));

SQL> create table t2 (

     id   number,

     col1 varchar2(4000),

     col2 varchar2(4000),

     constraint t2_pk primary key (id));

SQL> insert /*+append*/ into t1 select rownum, rpad('x', 4000, 'x'), rpad('x', 4000, 'x')

     from dual connect by level <= 10000;

10000 rows created.

SQL> commit;

SQL> insert /*+append*/ into t2 select rownum, rpad('x', 4000, 'x'), rpad('x', 4000, 'x')

     from dual connect by level <= 10000;

10000 rows created.

SQL> commit;

Let's generate some statistics.

SQL> exec dbms_stats.gather_schema_stats('RECLAIM_USER');

PL/SQL procedure successfully completed.

Check the size now in DBA_DATA_FILES and USER_SEGMENTS.


SQL> col tablespace_name format a25

SQl> select tablespace_name, blocks, bytes/1024/1024 as size_mb

     from dba_data_files  where tablespace_name = 'TEST_TBS';

TABLESPACE_NAME           BLOCKS        SIZE_MB

-------------------------              ----------         ----------

TEST_TBS                             132480            1035

SQL> col segment_name format a10

SQL> select segment_name, segment_type, bytes/1024 size_kb  from user_segments;

SEGMENT_NA SEGMENT_TY    SIZE_KB

----------    ----------               ----------

T1             TABLE                  163840

T2            TABLE                   163840

T1_PK      INDEX                 256

T2_PK      INDEX                 256

Let's truncate table T1 and collect statistics:

SQL> truncate table t1;

Table truncated.

SQL> exec dbms_stats.gather_schema_stats('RECLAIM_USER');

PL/SQL procedure successfully completed.

Let's examine the space - first the segment space. Obviously T1 is truncated.

SQL> col segment_name format a10

SQL> select segment_name, segment_type, bytes/1024 size_kb  from user_segments;

SEGMENT_NA SEGMENT_TY    SIZE_KB

----------          ----------                      ----------

T1                   TABLE                         64

T1_PK             INDEX                      64

T2                   TABLE                        163840

T2_PK            INDEX                        256

Let's now investigate the space within the tablespace. Five free fragments are visible, and these could be potential locations for moving objects. The largest one is around 646 MB in size. 

SQL> select ts.tablespace_name, sum (nvl(fs. bytes,0))/1024/1024 as MB_FREE,

     count (*) as FRAGMENTS, max (nvl(fs. bytes,0))/1024/1024 as BIGGEST

     from user_free_space fs, user_tablespaces ts

     where fs.tablespace_name(+) = ts.tablespace_name and ts.tablespace_name='TEST_TBS'

     group by ts.tablespace_name;  

TABLESPACE_NAME    MB_FREE               FRAGMENTS    BIGGEST

-------------------------       ----------                   ----------          ----------

TEST_TBS                   806.5625                            5                     646

To get the full picture, we could use the following query to show the mapping of the segments in the tablespace.

SQL> col segment_type format a10

SQL> set pagesize 100

SQl> select block_id begin_block, block_id + blocks - 1 end_block, blocks, bytes/1024/1024 MB,

     segment_name, segment_type

     from dba_extents where tablespace_name = 'TEST_TBS'

     union all

     select block_id begin_block, block_id + blocks - 1 end_block, blocks, bytes/1024/1024 MB,

     'free' segment_name, null segment_type

     from dba_free_space

     where tablespace_name = 'TEST_TBS'

     order by 1 desc;

 

BEGIN_BLOCK  END_BLOCK     BLOCKS         MB SEGMENT_NA SEGMENT_TY

----------- ---------- ---------- ---------- ---------- ----------

      49792     132479      82688        646 free    <== first free space starting at Block id 49792

      48768      49791       1024          8 T2         TABLE

      47744      48767       1024          8 T2         TABLE

      46720      47743       1024          8 T2         TABLE

      45696      46719       1024          8 T2         TABLE

      44672      45695       1024          8 T2         TABLE

      43648      44671       1024          8 T2         TABLE

      42624      43647       1024          8 T2         TABLE

      41600      42623       1024          8 T2         TABLE

      40576      41599       1024          8 T2         TABLE

      39552      40575       1024          8 T2         TABLE

      ...     

      29312      29319          8      .0625 T2         TABLE

       9216      29311      20096        157 free   <== another notable free space starting with 9216

       1024       1279        256          2 free

       1016       1023          8      .0625 T2         TABLE

       1008       1015          8      .0625 T2         TABLE

       ...

Let's use Oracle Enterprise Manager Cloud Control to display the tablespace content.

Go to Administration => Storage=> Tablespace, click on tablespace TEST_TBS and choose "Show Tablespace Content" in the menu on the right. Then you will get a new page with the Extent Map you can expand. Notice the free space is always colored in green.


IMG_3465.jpeg

Shrinking Tablespace

What is the possible outcome of the shrink run now? You can get a list of objects by checking SHRINK_RESULT returned. It will list the objects that are not supported such as cluster tables, some advanced queuing tables etc.

SQL> set linesize window

SQL> variable result clob

SQL> col result format a200

SQL> execute DBMS_SPACE.SHRINK_TABLESPACE('TEST_TBS', SHRINK_MODE => DBMS_SPACE.TS_MODE_ANALYZE, SHRINK_RESULT =>: result);

PL/SQL procedure successfully completed.

SQL> print result

RESULT

---------------------------------------------

Movable Objects:

1. {RECLAIM_USER.T2 | type: TABLE | blocks: 20480 | tablespace_name: TEST_TBS}

2. {RECLAIM_USER.T2_PK | type: INDEX | blocks: 32 | tablespace_name: TEST_TBS}

Total Movable Objects: 2

Total Movable Size (GB): .16

Original Datafile Size (GB): 1.01

Suggested Target Size (GB): .38

Process Time: +00 00:00:01.669704

The table T2 and the index T2_PK are the two objects that can be moved. The data file size should then be approximately 0.38 GB after that. When checking out the Extent Map above, we could see that there are at least two bigger empty pieces which could be used for that - one starts with block id 9216 and one with block id 49792.

So let's shrink the tablespace with SHRINK_TABLESPACE and the default setting to achieve the maximum possible shrink result. You can use a specific TARGET_SIZE to reach your own shrink size goal (see example at the end of the posting).

SQL> set serveroutput on

SQL> execute dbms_space.shrink_tablespace('TEST_TBS');

-----------SHRINK RESULT--------------

Total Moved Objects: 2

Total Moved Size (GB): .16

Original Datafile Size (GB): 1.01

New Datafile Size (GB): .39

Process Time: +00 00:00:10.611905

PL/SQL procedure successfully completed.

Let's do the same checks as before. There are still 5 fragments but the free space is less than before.

SQL> select ts.tablespace_name, sum (nvl(fs. bytes,0))/1024/1024 as MB_FREE,

     count (*) as FRAGMENTS, max (nvl(fs. bytes,0))/1024/1024 as BIGGEST

     from user_free_space fs, user_tablespaces ts

     where fs.tablespace_name(+) = ts.tablespace_name and ts.tablespace_name='TEST_TBS'

     group by ts.tablespace_name;   

TABLESPACE_NAME              MB_FREE  FRAGMENTS    BIGGEST

-------------- ---------- ---------- ----------

TEST_TBS                   168.5625          5        167

Let's get some more details.

SQL> col segment_type format a10

SQL> set pagesize 100

SQl> select block_id begin_block, block_id + blocks - 1 end_block, blocks, bytes/1024/1024 MB,

     segment_name, segment_type

     from dba_extents

     where tablespace_name = 'TEST_TBS'   

     union all

     select block_id begin_block, block_id + blocks - 1 end_block, blocks, bytes/1024/1024 MB,

     'free' segment_name, null segment_type

     from dba_free_space

     where tablespace_name = 'TEST_TBS'

     order by 1 desc;

 

BEGIN_BLOCK  END_BLOCK     BLOCKS         MB SEGMENT_NA SEGMENT_TY

----------- ---------- ---------- ---------- ---------- ----------

      49792      50815       1024          8 T2         TABLE

      28416      49791      21376        167 free        <== could be used for a segment move

      27392      28415       1024          8 T2         TABLE

      26368      27391       1024          8 T2         TABLE

      25344      26367       1024          8 T2         TABLE

      24320      25343       1024          8 T2         TABLE

      23296      24319       1024          8 T2         TABLE

      22272      23295       1024          8 T2         TABLE

      21248      22271       1024          8 T2         TABLE

      20224      21247       1024          8 T2         TABLE

      19200      20223       1024          8 T2         TABLE

...

       1056       1151         96        .75 free        <= free space

       1048       1055          8      .0625 T2_PK      INDEX

       1040       1047          8      .0625 T2_PK      INDEX

...

Let's check the Extent Map.


 

So, let's analyze again. There is still some possible savings as we can see from the Extent Map above and the analyze command confirms that.

SQL> variable result clob

SQL> col result format a200

SQL> execute DBMS_SPACE.SHRINK_TABLESPACE('TEST_TBS', SHRINK_MODE => DBMS_SPACE.TS_MODE_ANALYZE, SHRINK_RESULT =>: result);

SQL> print result

RESULT

-------------------------------------------

Movable Objects:

1. {RECLAIM_USER.T2 | type: TABLE | blocks: 20480 | tablespace_name: TEST_TBS}

2. {RECLAIM_USER.T2_PK | type: INDEX | blocks: 32 | tablespace_name: TEST_TBS}

Total Movable Objects: 2

Total Movable Size (GB): .16

Original Datafile Size (GB): .39

Suggested Target Size (GB): .38

Process Time: +00 00:00:01.674684

Therefore, let's shrink again.

SQL> set serveroutput on;

SQL> execute dbms_space.shrink_tablespace('TEST_TBS');


--------SHRINK RESULT-----------

Total Moved Objects: 2

Total Moved Size (GB): .16

Original Datafile Size (GB): .39

New Datafile Size (GB): .37

Process Time: +00 00:00:11.726847

PL/SQL procedure successfully completed.

Let's check the result.

SQL> select ts.tablespace_name, sum (nvl(fs. bytes,0))/1024/1024 as MB_FREE,

            count (*) as FRAGMENTS, max (nvl(fs. bytes,0))/1024/1024 as BIGGEST

    from user_free_space fs, user_tablespaces ts

    where fs.tablespace_name(+) = ts.tablespace_name and ts.tablespace_name='TEST_TBS'

    group by ts.tablespace_name;

 

TABLESPACE_NAME         MB_FREE                   FRAGMENTS    BIGGEST

--------    --------   ---------  ----------

TEST_TBS      152.5625           5          150


Let's check the Extent Map. We can see that fragments are moved again.


SQL> select block_id begin_block, block_id + blocks - 1 end_block, blocks, bytes/1024/1024 MB,  segment_name, segment_type

 from dba_extents where tablespace_name = 'TEST_TBS'

 union all

  select block_id begin_block, block_id + blocks - 1 end_block, blocks, bytes/1024/1024 MB,      

     'free' segment_name, null segment_type

     from dba_free_space where tablespace_name = 'TEST_TBS' order by 1 desc;

 

BEGIN_BLOCK  END_BLOCK     BLOCKS         MB SEGMENT_NA SEGMENT_TYPE

----------- ---------- ---------- ---------- ---------- --------------------      

      47744      48767       1024          8 T2         TABLE      

      46720      47743       1024          8 T2         TABLE      

      45696      46719       1024          8 T2         TABLE      

      44672      45695       1024          8 T2         TABLE      

      43648      44671       1024          8 T2         TABLE      

      42624      43647       1024          8 T2         TABLE      

      41600      42623       1024          8 T2         TABLE

...

      28416      28543        128          1 T2         TABLE        

       9216      28415      19200        150 free <= 

first free space...

And the Extent Map in Cloud Control. 


Summary and Additional Considerations

With the new procedure SHRINK_TABLESPACE you can reorganize tablespaces and resize the associated data file(s) to its smallest possible size by moving objects within the tablespace, either online or offline. You only need to use one command for this. Please note: When shrinking a smallfile tablespace, the data file sizes may increase or decrease depending on the size of the objects placed in the data files.

 As mentioned above you can use DBMS_SPACE.SHRINK_TABLESPACE in different ways:

  • Analyze a tablespace for a suggested target size.
  • Resize a tablespace to its minimum possible size.
  • Attempt to resize a tablespace to a specified target size.

Before trying to shrink, you should use the analyze command to find out the potential of a shrink operation. It will take much less time that actually shrinking it. The result of this analysis contains useful information including a list of movable objects, the list of unsupported objects, the total size of movable objects in the tablespace, and the suggested target size for the tablespace.

The shrink operation itself will reorganize the tablespace which will take some time. If you want to monitor long-running executions you may use V$SESSION_LONGOPS. OPNAME "Tablespace Shrink" defines our tablespace shrink operation. 

SQL> select opname, sofar, time_remaining,

             to_char(start_time,'dd.mm.yyyy-HH24:MI:SS') StartTime, message

      from v$session_longops

      where time_remaining>=0 and opname like '%Table%' order by 4;

OPNAME

-------------------------------------------------

     SOFAR TIME_REMAINING STARTTIME

---------- -------------- --------------------

MESSAGE

-------------------------------------------------

Online Move Table

         9              0 22.09.2024-19:44:04

Online Move Table: T2 : 9 out of 9 Steps done

 

Transform Heap Mapping Table to IOT Mapping Table

         1              0 22.09.2024-19:44:10

Transform Heap Mapping Table to IOT Mapping Table: T2 : 1 out of 1 Step done

 

Wait for DMLs - Swap dobj# - Move Table Online

         1              0 22.09.2024-19:44:12

Wait for DMLs - Swap dobj# - Move Table Online: T2 : 1 out of 1 Step done

 

Tablespace Shrink

         1              0 22.09.2024-19:44:14

Tablespace Shrink: segment: 1 out of 1 segments done

 

Tablespace Shrink

         1              0 22.09.2024-19:44:19

Tablespace Shrink: segment: 1 out of 1 segments done

...

Here is an example how to use SHRINK_TABLESPACE with a specific TARGET_SIZE in bytes. It will always use and adjust the target size that is required to complete the operation successfully.  

SQL> set serveroutput on

SQL> execute dbms_space.shrink_tablespace('TEST_TBS', target_size => 500000000);

Specified target size is too small, adjusting it to 1395654656

------------SHRINK RESULT-------------

Total Moved Objects: 1

Total Moved Size (GB): 0

Original Datafile Size (GB): 2.01

New Datafile Size (GB): 1.3

Process Time: +00 00:00:06.728767

PL/SQL procedure successfully completed.


Partially failing DBMS_SPACE.SHRINK_TABLESPACE is possible. Nevertheless, if a move DDL fails, the command always reports the cause. The command still resizes the data file to a smaller size if it already successfully moved some objects.

For instance, in the following example it is not possible to move table T2 because a lock is held in another session.


SQL> set serveroutput on

SQL> execute dbms_space.shrink_tablespace('TEST_TBS');

Procedure exited because it can't move an object: Failed Move DDL: alter table "RECLAIM_USER"."T2" move online

Failed Reason: ORA-00054: Failed to acquire a lock (Type: "TM", Name: "DML", Description: "Synchronizes accesses to an object")

because it is currently held by another session. The resource being locked can be identified by 132917 ("Table") and 0 ("operation")


Another option is DBMS_SPACE.TS_MODE_SHRINK_FORCE, which is useful when moving an object offline is possible but not when moving it online. DMLs and queries will be blocked by an offline move. Because some objects cannot be moved online or offline, this mode won't always work.

In any case, my initial testing experience showed that using the Tablespace Shrink feature during a maintenance window produced the best results for me. Therefore, I would advise using a dedicated window for that.


Note

  • If you shrink a bigfile tablespace that has autoextend disabled, there will be minimal or no free space left for new objects or data. You must manually resize the tablespace to accommodate any new objects or data, or enable autoextend.
  • It is possible for DBMS_SPACE.SRHRNK_TABLESPACE to partially fail. The command always reports the reason if a move DDL failed, but the command still resizes the datafile to a smaller size if it already successufully moved some objects.
  • DBMS_SPACE.SHRINK_TABLESPACE('TBS_1') is equivalent to DBMS_SPACE.SHRINK_TABLESPACE('TBS_1', SHRINK_MODE => DBMS_SPACE.TS_SHRINK_MODE_ONLINE, TARGET_SIZE => TS_TARGET_MAX_SHRINK). TS_TARGET_MAX_SHRINK means the target size will be automatically set based on tablespace usage (target size = sum of all object size + buffer). However, it is the best effort to shrink to the target size, and final size may be different than the target size.
  • DBMS_SPACE.TS_SHRINK_MODE_AUTO mode can be used if an object can’t be moved online but can be moved offline, and offline move is acceptable. An offline move will block DMLs and queries. This mode won’t always succeed because some objects can’t be moved either online or offline.
  • See Restrictions on the ONLINE Clause in Oracle Database SQL Language Reference for objects that cannot be moved online.
  • The following objects cannot be moved offline:

1.    tables with a LONG datatype

2.    cluster tables

3.    tables with reservable columns

  • DBMS_SPACE.TS_SHRINK_MODE_ANALYZE mode will only do space related estimation, but it can’t predict the success or failure of actual shrink. However, you can get a list of unsupported objects by checking SHRINK_RESULT returned by DBMS_SPACE.SRHINK_TABLESPACE('TBS_1', SHRINK_MODE => DBMS_SPACE.TS_SHRINK_MODE_ANALYZE, SHRINK_RESULT => :result). Currently, the unsupported object list includes cluster tables and some advanced queueing tables.
  • It is possible to shrink the SYSAUX tablespace.

 

Restrictions on the ONLINE Clause

The ONLINE clause is subject to the following restrictions when moving table partitions:

  • You cannot specify the ONLINE clause for tables owned by SYS.
  • You cannot specify the ONLINE clause for index-organized tables.
  • You cannot specify the ONLINE clause for heap-organized tables that contain object types or on which bitmap join indexes or domain indexes are defined.
  • Parallel DML and direct path INSERT operations require an exclusive lock on the table. Therefore, these operations are not supported concurrently with an ongoing online partition MOVE, due to conflicting locks.

 

Things to Note

  • Shrink is online in 26ai (no downtime for most operations).
  • May involve row movement and segment compaction.
  • Works best with locally managed tablespaces and auto segment space management (ASSM).
  • Always check reclaimable space with ...ESTIMATE before shrinking.

 

Conclusion:
Oracle 26ai introduces DBMS_SPACE.SHRINK_TABLESPACE and related APIs to shrink tablespaces and reclaim space more easily and automatically than before.

*********************************

Oracle Table DDL Change Notification Feature

Oracle Table DDL Change Notification , which is part of a broader Oracle technology called Continuous Query Notification (CQN)   History...