What Happens When You Corrupt the Wrong Block: Lessons from Oracle 26ai’s Transaction Quarantine

Introduction 

I run a single-instance Oracle AI Database 26ai lab on my own VM, and I keep a running list of new features I want to actually get my hands dirty with instead of just reading the docs. Automatic Transaction Quarantine had been on that list for a while — it’s SMON’s new ability to isolate a bad transaction during recovery instead of taking the whole instance down with it. It sounded like one of those features that’s easy to explain in a slide and much harder to actually watch happen. So, I set out to force it in my own lab and write down exactly what happened, including the parts that didn’t go the way I expected. 

Two attempts later, I still haven’t gotten it to fire. But each attempt taught me something specific about how Oracle’s recovery process works, and I think that’s worth writing on its own. 

Why this feature matters to a DBA 

Before 26ai, transaction recovery was pretty much all-or-nothing. When an instance or PDB restarts after a crash, SMON has two jobs: roll forward using redo (crash recovery), then roll back whatever was still uncommitted at the moment of the crash (transaction recovery, using undo). If SMON hit corruption while applying undo for one particular transaction — bad block, internal error, whatever the cause — the whole recovery attempt could fail and take the instance or PDB down with it. And the next time you tried to start it back up, SMON would hit the exact same wall again. That’s the crash loop every DBA dread: the database won’t stay up long enough for you to even get in and fix anything. 

Automatic Transaction Quarantine changes that. If SMON can’t recover a specific transaction, it no longer crashes — it quarantines that one transaction instead. Its row locks stay held (so touching those rows gets you ORA-60451), but everything else in the database keeps running normally. The DBA gets alerted, gets time to actually investigate, and once the underlying issue is sorted, drops the quarantine so SMON can finish the job. There’s also a limit on how many transactions can be quarantined in one PDB before Oracle decides containment has failed and shuts the PDB down anyway — I never got far enough to test that ceiling. 

How this would have played out before 26ai 

I don’t have a spare 19c box running right now, so this next bit is background, not something I tested side by side — if I get a 19c VM up at some point I’d like to actually run this comparison for real. 

In 19c and earlier, there’s no quarantine mechanism at all. If SMON hit corruption during undo application, the classic manual workaround was setting the hidden parameter _CORRUPTED_ROLLBACK_SEGMENTS to explicitly tell the instance which rollback segment to skip during recovery. It works, but it’s disruptive — every other transaction relying on that same rollback segment gets affected too, and you’re usually left rebuilding the segment and checking for logical inconsistency afterward. There was no row-level containment like ORA-60451, and nothing like dba_quarantined_transactions to see what was actually going on. You were mostly reading the alert log and making a judgment call under pressure. 

Even with my three misses below, the underlying shift is clear: Oracle handling this automatically, without a manual parameter and a restart-time workaround, is a real reduction in how much of this used to fall on the DBA to fix by hand. 

Setting up an isolated lab environment 

I didn’t want to touch anything else on my instance, so I built a separate tablespace, schema, and table just for this: 

ALTER SESSION SET CONTAINER = SHANMU;

CREATE TABLESPACE quarantine_test
  DATAFILE SIZE 50M AUTOEXTEND OFF;

CREATE USER quarantine_lab IDENTIFIED BY ********
  DEFAULT TABLESPACE quarantine_test
  QUOTA UNLIMITED ON quarantine_test;

GRANT CREATE SESSION, CREATE TABLE TO quarantine_lab; 

One small thing that tripped me up: I first tried to hand-specify a datafile path with DATAFILE ‘/path/to/file.dbf’ . My instance uses Oracle Managed Files, so that’s unnecessary — I just let Oracle place it itself. Once I stopped fighting that, it went through fine and landed as an OMF-named file, something like o1_mf_quaranti_o4ycwxn0_.dbf . 

With that done, I created the test table: 

 

CREATE TABLE quarantine_demo (
  id NUMBER,
  payload VARCHAR2(100)
);

INSERT INTO quarantine_demo VALUES (1‘baseline row’);
COMMIT; 

Attempt 1 — corrupting a data block mid-transaction 

The plan: find the block holding my baseline row, start a second insert and deliberately leave it uncommitted, corrupt that block at the OS level while the transaction was still open, crash the instance, and see what SMON did on restart. 

Found the block: 

SELECT dbms_rowid.rowid_block_number(rowid) AS block_no,
       dbms_rowid.rowid_relative_fno(rowid) AS rel_fno
FROM quarantine_demo
WHERE id = 1;
— block_no = 782 

Confirmed the datafile and block size: 

SELECT file_id, file_name FROM dba_data_files WHERE tablespace_name = ‘QUARANTINE_TEST’;

SHOW PARAMETER db_block_size; 

Left a second insert hanging: 

INSERT INTO quarantine_lab.quarantine_demo VALUES (2‘the doomed transaction’);
— I have not commited this transaction 

Corrupted block 782 directly, with that session still open: 

dd if=/dev/urandom of=/u01/app/oracle/oradata/RAJA/…/o1_mf_quaranti_o4ycwxn0_.dbf \
   bs=8192 seek=782 count=1 conv=notrunc 

Crashed the instance with the transaction still uncommitted: 

SHUTDOWN ABORT;
STARTUP; 

The alert log showed exactly what I’d hoped to see: 

Hex dump (file 16, block 782)…
Bad header found during crash/instance recovery
physically corrupt:Yes, logically block checking not done., and no upper layer check done.
Process USER with os pid: 18109 marked blocks with rdba: 0x30e … as soft corrupt.
Completed crash recovery at
Thread 1: RBA 5.212919.16, nab 212919, scn 0x00000000005e7536
17 data blocks read, 17 data blocks written, 448 redo k-bytes read 

It caught the corruption, said so plainly, and then finished recovery anyway. The PDB opened up normally. 

Checking afterward: dba_quarantined_transactions was empty, and row id=2 was gone — rolled back cleanly. No quarantine, no ORA-60451. 

Why attempt 1 didn’t trigger it 

What I’d hit was crash recovery’s redo application phase marking a physically damaged block as soft corrupt and tolerating it — a mechanism that predates 26ai. Automatic Transaction Quarantine protects a different, later phase: SMON’s transaction recovery, where it applies undo to roll back a specific uncommitted transaction. I’d corrupted a data block, not the undo the transaction needed to be rolled back. Two separate steps in the recovery pipeline, and I’d only poked at the first one. 

There’s a simpler issue underneath that too — I picked block 782 based on where row id=1 was sitting before I ever inserted row id=2. Nothing guaranteed the new row landed in that same block. 

I’ll also admit a mistake in my own process here: my first pass at checking the quarantine views came back empty because I queried them from CDB$ROOT , not from inside the SHANMU PDB. ALTER SESSION SET CONTAINER doesn’t persist across a fresh connection, and I hadn’t re-set it after reconnecting post-restart. An empty result from the wrong container looks identical to a genuinely empty result from the right one — worth remembering. 

Attempt 2— going after the real undo block 

For the second attempt, I decided to stop guessing at data blocks and go straight to the thing SMON needs: the transaction’s own undo block. I checked first that nothing else was running on the instance, since this meant touching a shared structure. 

Started the transaction: 

INSERT INTO quarantine_lab.quarantine_demo VALUES (5‘undo target test’); 

Found its exact undo location: 

SELECT s.sid, s.serial#, s.username, t.xidusn, t.xidslot, t.ubafil, t.ubablk, t.status
FROM v$transaction t
JOIN v$session s ON t.addr = s.taddr;–   SID 237, SERIAL# 28178, USERNAME SYS
—   XIDUSN 2, XIDSLOT 23, UBAFIL 14, UBABLK 4597, STATUS ACTIVE 

Confirmed file 14 really was UNDOTBS 1: 

SELECT tablespace_name FROM dba_data_files WHERE file_id = 14; 

Corrupted block 4597 directly, from a separate terminal, without touching the session holding the transaction: 

dd if=/dev/urandom of=/u01/app/oracle/oradata/RAJA/…/o1_mf_undotbs1_ns9wpss5_.dbf \
   bs=8192 seek=4597 count=1 conv=notrunc 

Then crashed the instance directly, without letting that session exit on its own: 

SHUTDOWN ABORT;
STARTUP; 

This time, the alert log showed nothing about corruption at all — no bad header, no soft-corrupt marking, nothing. Crash recovery just completed: “17 data blocks read, 17 data blocks written, 33 redo k-bytes read.” Checking the quarantine views (correctly, in SHANMU this time) came back empty again, row id=5 was gone, and no background transaction recovery was pending in v$fast_start_transactions . 

Why attempt 2 didn’t trigger it either 

At first I couldn’t tell if this meant “no corruption happened” or “corruption happened but still didn’t matter.” So I dumped the exact block to check directly: 

ALTER SYSTEM DUMP DATAFILE 14 BLOCK MIN 4597 BLOCK MAX 4597; 

The resulting trace file showed a completely clean, valid KTU UNDO BLOCK header with a sane checksum and normal-looking transaction table entries — no trace of corruption at all. 

My best explanation: undo tablespaces are circular and constantly reused. Between the moment I ran dd and the moment I dumped that block to check it, the crash, restart, and various background activity (SMON, checkpointing, archiving) had time to legitimately overwrite block 4597 with fresh, valid undo data. By the time I looked, whatever I’d written was already gone, replaced by an ordinary database churn. A stable data block sits still until something touches it; a live undo block is a moving target, and hitting it at exactly the right instant is much harder than I expected going in. 

What I walked away with 

Two attempts, Two misses, each for a genuinely different reason: 

Attempt 1 corrupted a data block and hit crash recovery’s redo-application tolerance, not the undo-application step quarantine guards. 

Attempt 2 targeted the real undo block correctly, but undo circular reuse meant the corruption likely got overwritten by legitimate activity before recovery ever had to read it. 

Along the way I also confirmed a few things worth knowing on their own: crash recovery tolerates physically corrupt data blocks during redo application without invoking quarantine at all; quarantine is specifically about undo application failing during transaction recovery, a distinct and later phase; and querying the wrong container gives you an empty result that looks exactly like a genuine one. 

I still haven’t watched Automatic Transaction Quarantine fire with my own eyes. Doing it properly would likely mean scripting the corruption and crash to happen within a much tighter window than I did by hand, right after the undo write — and even then, there’s no guarantee of hitting it before the block cycles again. For now, I’m more interested in finding a supported, documented way to simulate this failure than in repeatedly hand-corrupting a shared undo tablespace to chase a narrower timing window. 

Useful queries if you want to poke at this yourself 

— check for active quarantines
SELECT * FROM dba_quarantined_transactions;
SELECT * FROM cdb_quarantined_transactions;

— check open/uncommitted transactions and their undo location
SELECT s.sid, s.serial#, s.username, t.xidusn, t.xidslot, t.ubafil, t.ubablk, t.status
FROM v$transaction t
JOIN v$session s ON t.addr = s.taddr;

— check for in-progress background transaction recovery
SELECT * FROM v$fast_start_transactions;

— confirm which container you’re actually in before trusting empty results
SELECT sys_context(‘userenv’,‘con_name’FROM dual;

— inspect a specific block directly if you suspect corruption didn’t stick
ALTER SYSTEM DUMP DATAFILE <file_id> BLOCK MIN <block> BLOCK MAX <block>; 

If anyone’s managed to get a real quarantine to fire — especially with reliable timing against a live undo block, or through a supported fault-injection method rather than raw corruption — I’d genuinely like to hear how you did it. 

Recent Posts