Tuesday, July 11, 2017

Getting Oracle Transaction Commit SCN

This Blog demonstrates one approach to get exact Commit SCN and compare it with other methods. It has not only (Oracle) academic interest, but also practical demand, for example, data replication (The COMMIT SCN - an undocumented feature).

Note: all tests are done in Oracle 12.1.0.2.0.

Update (2020-October-27):
If we use flashback_transaction_query (which requires minimum supplemental logging and archivelogs), we can also get the exact COMMIT_SCN.

select xid, start_scn, start_timestamp, commit_scn, commit_timestamp 
from flashback_transaction_query 
where table_name = 'TEST_TAB' and rownum = 1;

XID               START_SCN          START_TIMESTAMP      COMMIT_SCN        COMMIT_TIMESTAMP
----------------  -----------------  -------------------  ----------------- -------------------
550002009EC10000  9,714,099,942,712  27-10-2020:10:46:14  9,714,099,942,724 27-10-2020:10:46:20

1. Setup



drop table test_tab;
drop table commitscn_gtt;
create table test_tab(id number, sid number);

create global temporary table commitscn_gtt(dummy)
on commit preserve rows
as select -1 from dual;

create or replace procedure push_commitscn as
begin
  delete from commitscn_gtt;
  insert into commitscn_gtt (dummy) values (-1);
end;
/

create or replace function get_commitscn return number as
 l_commitscn number;
begin
  select ora_rowscn into l_commitscn from commitscn_gtt where rownum=1;
  return l_commitscn;
end;
/


2. Test



insert into test_tab (id, sid) values (1, sys.dbms_support.mysid);

-- run immediately before commit
exec push_commitscn;

commit;

set numformat 999,999,999,999,999

-- get exact commitscn after commit
select get_commitscn from dual;

       GET_COMMITSCN
--------------------
   9,183,757,165,446


3. Comparing with Other Approaches


Test below comprises 5 different commit scn getting methods.

drop table test_tab2;
create table test_tab2(id number, sid number, scn number) rowdependencies;
insert into test_tab2 (id, sid, scn) values (1, sys.dbms_support.mysid, userenv('commitscn'));
exec push_commitscn;
commit;

set numformat 999,999,999,999,999

select scn from test_tab2 where id = 1;
                 SCN
--------------------
   9,183,757,165,468
   
select ora_rowscn from test_tab2 where id = 1;
          ORA_ROWSCN
--------------------
   9,183,757,165,469
   
select get_commitscn from dual;
       GET_COMMITSCN
--------------------
   9,183,757,165,469

select dbms_flashback.get_system_change_number from dual;
GET_SYSTEM_CHANGE_NUMBER
------------------------
   9,183,757,165,471
       
select current_scn from v$database;
         CURRENT_SCN
--------------------
   9,183,757,165,472

Let's look their differences.


Method 1. USERENV('COMMITSCN')

select scn from test_tab2 where id = 1;
→ USERENV('COMMITSCN') is the actual COMMIT SCN minus 1, i.e immediately scn before commit.
    Each commit creates 1 new number (for commit Record).
→ Undocumented
→ 12c USERENV deprecated


Method 2. ORA_ROWSCN

select ora_rowscn from test_tab2 where id = 1;
→ Oracle Database SQL Language Reference - ORA_ROWSCN Pseudocolumn said:
    Whether at the block level or at the row level, the ORA_ROWSCN should not be considered to be an exact SCN.
    If a block is queried twice, then it is possible for the value of ORA_ROWSCN to change between the queries
    even though rows have not been updated in the time between the queries. The only guarantee is that the value of
    ORA_ROWSCN in both queries is greater than the commit SCN of the transaction that last modified that row.
   (Note: "greater than" seems an Oracle Document error. It should be "not less than")

    So ORA_ROWSCN is not deterministic because returned scn also depends on query start scn
    due to preious discussed Delayed Block Cleanout.
    Hence it can decrease even there are not any user updates on the selected table.

    It can increase if there are any committed updates between two queries as demonstrated in
    How can ORA_ROWSCN change between queries when no update? at Jul 8, 2017 9:20 PM.
→ For each row, ORA_ROWSCN returns the conservative upper bound SCN of the most recent change to the row.
    For example, "delayed block cleanout".
→ It increases the size of each row by 6 bytes.
→ Oracle BUG: INSERT to a Table With ROWDEPENDENCIES Failed With ORA-00600 [kdtgsph-row] (Doc ID 2210391.1)


Method 3. Commit SCN of this Blog

select get_commitscn from dual;
→ Exact commit SCN.
    Once a commit is executed, this single row (single block) is stamped with that commit SCN.
    Since the one-row GTT is session local, it can never be selected by other sessions
    and is hardly to imagine a "delayed block cleanout". Therefore it never be changed.


Method 4. System functions

select dbms_flashback.get_system_change_number from dual;

select current_scn from v$database;
→ Upper bound system change number (SCN), not precise.


4. Commit SCN exposed


Blog: PL/SQL Function Result Cache Invalidation observed that commit accomplishes invalidating Result Cache before returns, and interpreted:

    The above Stack Trace shows that when a transaction user session calls commit command,
    commit takes a detour to visit Result Cache along its code path in order to perform
    the invalidation before publishing the confirmed and consistent news to the world.

The following test will demonstrate that Commit SCN is exposed into V$RESULT_CACHE_OBJECTS.SCN during Result Cache Invalidation.

drop table rc_tab;
create table rc_tab (id number, val number);
insert into rc_tab select level, level*10 from dual connect by level <= 3;
commit;

create or replace function get_val (p_id number) return number result_cache as
  l_val  number;
begin
  select val into l_val from rc_tab where id = p_id;
  return l_val ;
end;
/

create or replace procedure run_test as
  l_val number;
begin
  for i in 1 .. 3 loop
    l_val := get_val(i);
  end loop;
end;
/

exec dbms_result_cache.flush;

exec run_test;

column name format a13

select id, type, status, name, namespace, creation_timestamp, scn
  from v$result_cache_objects ro
order by scn desc, type, id;

ID TYPE       STATUS    NAME          NAMESPACE  CREATION_TIMESTAMP                    SCN
-- ---------- --------- ------------- ---------- -------------------- --------------------
 0 Dependency Published K.GET_VAL                2017*JUL*12 07:06:43    9,198,309,753,651
 2 Dependency Published K.RC_TAB                 2017*JUL*12 07:06:43    9,198,309,753,651
 1 Result     Published "K"."GET_VAL" PLSQL      2017*JUL*12 07:06:43    9,198,309,753,651
 3 Result     Published "K"."GET_VAL" PLSQL      2017*JUL*12 07:06:43    9,198,309,753,651
 4 Result     Published "K"."GET_VAL" PLSQL      2017*JUL*12 07:06:43    9,198,309,753,651

update rc_tab set val = -2 where id = 2;

exec push_commitscn;

commit;

select get_commitscn from dual;

     GET_COMMITSCN
------------------
 9,198,309,753,654

select id, type, status, name, namespace, creation_timestamp, scn
  from v$result_cache_objects ro
order by scn desc, type, id;

ID TYPE       STATUS    NAME          NAMESPACE  CREATION_TIMESTAMP                    SCN
-- ---------- --------- ------------- ---------- -------------------- --------------------
 2 Dependency Published K.RC_TAB                 2017*JUL*12 07:06:43    9,198,309,753,654
 0 Dependency Published K.GET_VAL                2017*JUL*12 07:06:43    9,198,309,753,651
 1 Result     Invalid   "K"."GET_VAL" PLSQL      2017*JUL*12 07:06:43    9,198,309,753,651
 3 Result     Invalid   "K"."GET_VAL" PLSQL      2017*JUL*12 07:06:43    9,198,309,753,651
 4 Result     Invalid   "K"."GET_VAL" PLSQL      2017*JUL*12 07:06:43    9,198,309,753,651

The above test shows that Commit SCN:
    9,198,309,753,654
is exposed in V$RESULT_CACHE_OBJECTS.SCN as invalidation SCN.

Oracle 12c Database Reference said about V$RESULT_CACHE_OBJECTS.SCN:
    Build SCN (TYPE = Result) or invalidation SCN (TYPE = Dependency)

There are also a few Oracle Tables/Views containing "commitscn" or "commit_scn" columns, which could point to its usage.