Monday, April 9, 2018

TM lock and No Transaction Commit, TX & TM Locks and No Transaction Visible

Oracle Document wrote:
TRANSACTION 
  groups SQL statements so that they are either all committed, which means they are applied to the database, or all rolled back, 
  which means they are undone from the database. Oracle Database assigns every transaction a unique identifier called a transaction ID.

AUTONOMOUS TRANSACTION
  an independent transaction that can be called from another transaction, which is the main transaction. You can suspend the calling transaction, 
  perform SQL operations and commit or undo them in the autonomous transaction, and then resume the calling transaction.
  The autonomous transaction does not see uncommitted changes made by the main transaction 
  and does not share locks or resources with the main transaction.
  When you enter the executable section of an autonomous routine, the main routine suspends. 
  When you exit the autonomous routine, the main routine resumes.

COMMIT
  Use the COMMIT statement to end your current transaction and make permanent all changes performed in the transaction. 
  A transaction is a sequence of SQL statements that Oracle Database treats as a single unit. 
  This statement also erases all savepoints in the transaction and releases transaction locks.

ROLLBACK
  Use the ROLLBACK statement to undo work done in the current transaction or to manually undo the work done by an in-doubt distributed transaction.
It looks like that COMMIT / ROLLBACK are for transactions.

In this Blog, we will make two demonstrations. The first one will show that COMMIT/ROLLBACK has to be executed even without any transactions.

The test code is also trying to reproduce and study MOS:
    Bug 26965236 : DELETE FROM TSDP_SENSITIVE_DATA$ CAUSING ENQ: TM - CONTENTION WAITS

The second one is about TX and TM Locks without Transaction Visible.

Note: Tested in Oracle 12c and 19c.


1. TM lock and no transaction commit


In Oracle 12c, when you execute:

  alter table ttx drop column colx;
It will trigger an internal delete statement:

  delete from tsdp_sensitive_data$ where obj# = :1 and col_argument# = :2
even you don't use TSDP (Transparent Sensitive Data Protection).

Note: All tests are done in Oracle 12.1.0.2.

Now we can start TM lock test on above TSDP tables.

------------------ T0: Test SetUp ------------------ 
drop table tt1;

create table tt1 (x number, c1 number, c2 number);

------------------ T1: Session_1------------------ 
SQL(sid 190) > delete from sys.tsdp_sensitive_data$ where 1=2; 
  0 rows deleted.

------------------ T2: Session_2 ------------------ 
SQL(sid 290) > alter table tt1 drop column c2;

-- Session 290 hanging, blocked by 190.
-- same for   alter table k.tt1 set unused (c1);

------------------ T3: Monitor Session_3 ------------------ 
SQL(sid 390) > select o.object_name, k.* from v$lock k, dba_objects o where k.type in ('TM') and k.ID1 = o.object_id;
 
  OBJECT_NAME                    SID  TYPE  ID1      ID2  LMODE  REQUEST  CTIME  BLOCK
  -----------------------------  ---  ----  -------  ---  -----  -------  -----  -----
  TSDP_SENSITIVE_DATA$           190  TM    1576498  0    3      0        14     0    
  TSDP_SUBPOL$                   190  TM    1578689  0    3      0        14     0    
  TSDP_PROTECTION$               190  TM    1578695  0    3      0        14     1    
  TT1                            290  TM    2321087  0    6      0        9      0    
  WRI$_OPTSTAT_HISTHEAD_HISTORY  290  TM    601844   0    3      0        9      0    
  WRI$_OPTSTAT_HISTGRM_HISTORY   290  TM    601856   0    3      0        9      0    
  COM$                           290  TM    136      0    3      0        9      0    
  COL_USAGE$                     290  TM    456      0    3      0        9      0    
  OBJAUTH$                       290  TM    61       0    3      0        9      0    
  TSDP_SENSITIVE_DATA$           290  TM    1576498  0    3      0        9      0    
  TSDP_SUBPOL$                   290  TM    1578689  0    3      0        9      0    
  TSDP_PROTECTION$               290  TM    1578695  0    0      5        9      0    

--TSDP_PROTECTION$  REQUEST = 5
--Session_2 blocked by Session_1 in 'enq: TM - contention' Lock Mode 5 (SSX/SRX)

SQL(sid 390) > select * from v$transaction;
  0 rows selected.
  
  -- no TRX

SQL(sid 390) > select chain_signature, sid, blocker_sid, wait_event_text, p1, p1_text, p2, p2_text, p3, p3_text from v$wait_chains;

  CHAIN_SIGNATURE                                        SID  BLOCKER_SID  WAIT_EVENT_TEXT              P1          P1_TEXT    P2       P2_TEXT   P3  P3_TEXT
  -----------------------------------------------------  ---  -----------  ---------------------------  ----------  ---------  -------  --------  --  ---------------
  'SQL*Net message from client'<='enq: TM - contention'  290  190          enq: TM - contention         1414332421  name|mode  1578695  object #  0   table/partition
  'SQL*Net message from client'<='enq: TM - contention'  190               SQL*Net message from client  1413697536  driver id  1        #bytes    0


SQL(sid 390) > select * from dba_constraints where constraint_name = 'TSDP_PROTECTION$FKSD';

  OWNER  CONSTRAINT_NAME       CONSTRAINT_TYPE  TABLE_NAME        R_CONSTRAINT_NAME       DELETE_RULE  STATUS
  -----  --------------------  ---------------  ----------------  ----------------------  -----------  ------
  SYS    TSDP_PROTECTION$FKSD  R                TSDP_PROTECTION$  TSDP_SENSITIVE_DATA$PK  CASCADE      ENABLED


-- Suspending LGWR will not block commit in Session_1
--SQL> oradebug setorapid 13
--  Oracle pid: 13, Unix process pid: 18482, image: oracle@testdb (LGWR)
--SQL> oradebug suspend
--SQL> oradebug resume

------------------ T4: Session_1, Release TM Locks to deblock ------------------ 
SQL(sid 290) > commit;
One quick workaround is to disable the foreign key constraint if TSDP is not used.

  alter table sys.tsdp_protection$ disable constraint tsdp_protection$fksd;
or create an index if TSDP is used.

  create index sys.tsdp_protection$sensitiv on sys.tsdp_protection$(sensitive#);
If we suspend LGWR in Session_3 (see commented-out code), and repeat the whole test, the "commit" in Session_1 will not be blocked. It indicates no "commit record" written to redo log for this "commit" execution, in other words, not every "commit" generates a "commit record".

With event 10704, we can list all triggered TM Locks. (see Blog: Investigating Oracle lock issues with event 10704 )

SQL(sid 290) > alter session set events='10046 trace name context forever, level 1 : 10704 trace name context forever, level 3' tracefile_identifier='ksun_10704_1';

SQL(sid 290) > alter table tt1 drop column c1;

SQL(sid 290) > alter session set events='10046 trace name context off : 10704 trace name context off ';


ksqgtl *** TM-00236ABF-00000000-00000000-00000000 mode=6 flags=0x400 timeout=5 ***
...
ksqgtl *** TM-001816C7-00000000-00000000-00000000 mode=5 flags=0x400 timeout=21474836 ***
ksqcnv: TM-001816C7-00000000-00000000-00000000 mode=3 timeout=21474836
ksqcmi: TM-001816C7-00000000-00000000-00000000 mode=3 timeout=21474836
ksqrcl: TM-001816C7-00000000-00000000-00000000
...
ksqrcl: TM-00236ABF-00000000-00000000-00000000

  -- 00236ABF (2321087) = TT1
  -- 001816C7 (1578695) = SYS.TSDP_PROTECTION$
  
  -- ksqgtl: get lock (kernel subroutine: ksqgtlctx)
  -- ksqcnv: convert lock
  -- ksqcmi: change mode internal
  -- ksqrcl: release lock
We can see that DDL_LOCK_TIMEOUT = 5 seconds has no effect in this case (line with "TM-00236ABF" for TT1). TM lock on SYS.TSDP_PROTECTION$ ("TM-001816C7") is first obtained in MODE 5 (SRX) by ksqgtl, then converted to 3 (RX) by ksqcnv and ksqcmi, finally released by ksqrcl.


2. TX & TM Locks and No Transaction Visible


In this section, we will show one case, in which there are TX and TM locks (v$lock), but no visible transaction (v$transaction).


2.1 Test Setup


We create 2 tables and 3 Plsql programs.

drop table test_tab_1;
drop table test_tab_2;
create table test_tab_1 as select 1 x from dual;
create table test_tab_2 as select 2 x from dual;

create or replace procedure prt (p_name varchar2) as
  l_trx_cnt number;
begin
  dbms_output.put_line('===============' ||p_name||' at '||localtimestamp||'===============' );
  for cm in (select sid from v$mystat where rownum=1) loop
    for c in (select s.sid, s.serial#, s.saddr, t.addr, t.start_time, t.start_scn, 
                     t.xid, t.xidusn, t.xidslot, t.xidsqn 
               from v$session s, v$transaction t where s.taddr=t.addr(+) and s.sid=cm.sid) loop
      dbms_output.put_line(' ----1.----- Session');
      dbms_output.put_line('  sid='||c.sid||', serial#='||c.serial#||', saddr='||c.saddr);
      dbms_output.put_line(' ----2.----- Transaction');
      if c.xid is null then
        dbms_output.put_line('    ****** No Transaction Visible ******');
      else
        dbms_output.put_line('  addr='||c.addr||', start_time='||c.start_time||', start_scn='||c.start_scn||
                             ', xid='||c.xid||', xidusn='||c.xidusn||', xidslot='||c.xidslot||', xidsqn='||c.xidsqn);
      end if;
    end loop;
    
    dbms_output.put_line(' ----3.----- Locks');
    for c in (select addr, kaddr, sid, type, id1, id2, lmode, request, ctime, block
               from v$lock where type in ('TM', 'TX') and sid = cm.sid order by type) loop
      dbms_output.put('  addr='||c.addr||', kaddr='||c.kaddr||', sid='||c.sid||', type='||c.type);
      dbms_output.put(', id1='||c.id1);
      if c.type = 'TX' then
        dbms_output.put('(rbs='||trunc(c.id1/power(2,16))||', slot='||bitand(c.id1,to_number('ffff','xxxx'))||')');
      end if;
      dbms_output.put(', id2='||c.id2||', lmode='||c.lmode);
      dbms_output.put_line(', request='||c.request||', ctime='||c.ctime||', block='||c.block);
    end loop;
  end loop;
end;
/

create or replace function retx (p_x number, p_sleep_seconds number) return number as
begin
  prt('retx');
  dbms_lock.sleep(p_sleep_seconds); 
  return p_x;
end;
/

create or replace procedure update_tab_1 (p_sleep_seconds number)as
  pragma autonomous_transaction;
begin
  prt('Start autonomous_TRX'); 
  update test_tab_1 set x=retx(x, p_sleep_seconds); -- two TX and TM locks
  commit;  
    -- autonomous_transaction have to commit/rollback, 
    -- otherwise: ORA-06519: active autonomous transaction detected and rolled back
end;
/


2.2 Case1 - TX and TM Locks without Transaction Visible


Open a Sqlplus session and run test below with 10046 and 10704 trace. In the main transaction, we update test_tab_2, and in autonomous_transaction, we update test_tab_1.

alter session set events='10046 trace name context forever, level 1 : 10704 trace name context forever, level 3' 
                    tracefile_identifier='10704_trx_3';
begin 
  prt('Start Main Trx');
  update test_tab_2 set x=retx(x, 3);
  update_tab_1(6);
  commit;
end;
/

alter session set events='10046 trace name context off : 10704 trace name context off '; 
Here the test output:

===============Start Main Trx at 14-MARCH-2018 14:03:43===============
 ----1.----- Session
  sid=16, serial#=48272, saddr=000000018FCFA770
 ----2.----- Transaction
    ****** No Transaction Visible ******
 ----3.----- Locks
===============retx at 14-MARCH-2018 14:03:43===============
 ----1.----- Session
  sid=16, serial#=48272, saddr=000000018FCFA770
 ----2.----- Transaction
  addr=0000000186678CE8, start_time=05/14/20 14:03:43, start_scn=9447196146707, xid=030010006E629B00, xidusn=3, xidslot=16, xidsqn=10183278
 ----3.----- Locks
  addr=FFFF80FFBC14C4F0, kaddr=FFFF80FFBC14C558, sid=16, type=TM, id1=2468649, id2=0, lmode=3, request=0, ctime=0, block=0
  addr=0000000186678CE8, kaddr=0000000186678D68, sid=16, type=TX, id1=196624(rbs=3, slot=16), id2=10183278, lmode=6, request=0, ctime=0, block=0

===============Start autonomous_TRX at 14-MARCH-2018 14:03:47===============
 ----1.----- Session
  sid=16, serial#=48272, saddr=000000018FCFA770
 ----2.----- Transaction
    ****** No Transaction Visible ******
 ----3.----- Locks
  addr=FFFF80FFBC148410, kaddr=FFFF80FFBC148478, sid=16, type=TM, id1=2468649, id2=0, lmode=3, request=0, ctime=3, block=0
  addr=0000000186678CE8, kaddr=0000000186678D68, sid=16, type=TX, id1=196624(rbs=3, slot=16), id2=10183278, lmode=6, request=0, ctime=3, block=0
===============retx at 14-MARCH-2018 14:03:47===============
 ----1.----- Session
  sid=16, serial#=48272, saddr=000000018FCFA770
 ----2.----- Transaction
  addr=0000000184F54338, start_time=05/14/20 14:03:46, start_scn=9447196146719, xid=06002000BDADD700, xidusn=6, xidslot=32, xidsqn=14134717
 ----3.----- Locks
  addr=FFFF80FFBC139448, kaddr=FFFF80FFBC1394B0, sid=16, type=TM, id1=2468648, id2=0, lmode=3, request=0, ctime=0, block=0
  addr=FFFF80FFBC139448, kaddr=FFFF80FFBC1394B0, sid=16, type=TM, id1=2468649, id2=0, lmode=3, request=0, ctime=3, block=0
  addr=0000000184F54338, kaddr=0000000184F543B8, sid=16, type=TX, id1=393248(rbs=6, slot=32), id2=14134717, lmode=6, request=0, ctime=0, block=0
  addr=0000000186678CE8, kaddr=0000000186678D68, sid=16, type=TX, id1=196624(rbs=3, slot=16), id2=10183278, lmode=6, request=0, ctime=3, block=0
There are two lines of "****** No Transaction Visible ******".

The first one has no Transaction and no Locks:

 ----2.----- Transaction
    ****** No Transaction Visible ******
 ----3.----- Locks
However the second one has no Transaction, but has one TM lock and one TX lock:

 ----2.----- Transaction
    ****** No Transaction Visible ******
 ----3.----- Locks
  addr=FFFF80FFBC148410, kaddr=FFFF80FFBC148478, sid=16, type=TM, id1=2468649, id2=0, lmode=3, request=0, ctime=3, block=0
  addr=0000000186678CE8, kaddr=0000000186678D68, sid=16, type=TX, id1=196624(rbs=3, slot=16), id2=10183278, lmode=6, request=0, ctime=3, block=0
Under "retx at 14-MARCH-2018 14:03:43", there is one transaction:

 ----2.----- Transaction
  addr=0000000186678CE8, start_time=05/14/20 14:03:43, start_scn=9447196146707, xid=030010006E629B00, xidusn=3, xidslot=16, xidsqn=10183278
but 3 seconds later (we sleep 3 seconds), when we step into autonomous_transaction without any commit/rollback, the transaction is no more visible as showed under line: "Start autonomous_TRX at 14-MARCH-2018 14:03:47"

 ----2.----- Transaction
    ****** No Transaction Visible ******
Here the output of 10046 and 10704 trace (unrelated lines removed)

*** 2018-03-14 14:03:43.937
begin
  prt('Start Main Trx');
  update test_tab_2 set x=retx(x, 3);
  update_tab_1(6);
  commit;
end;

UPDATE TEST_TAB_2 SET X=RETX(X, 3)
ksqgtl *** TM-0025AB29-00000000-00000000-00000000 mode=3 flags=0x400 timeout=21474836 ***
ksqgtl *** TX-00030010-009B626E-00000000-00000000 mode=6 flags=0x401 timeout=0 ***

*** 2018-03-14 14:03:47.019
UPDATE TEST_TAB_1 SET X=RETX(X, :B1 )
ksqgtl *** TM-0025AB28-00000000-00000000-00000000 mode=3 flags=0x400 timeout=21474836 ***
ksqgtl *** TX-00060020-00D7ADBD-00000000-00000000 mode=6 flags=0x401 timeout=0 ***

ksqrcl: TX-00060020-00D7ADBD-00000000-00000000
ksqrcl: TM-0025AB28-00000000-00000000-00000000

ksqrcl: TX-00030010-009B626E-00000000-00000000
ksqrcl: TM-0025AB29-00000000-00000000-00000000


2.3 Case2 - TX and TM Locks without Transaction Visible, and Deadlock


In the above test, we first update test_tab_2, then update test_tab_1 (in procedure update_tab_1). If we make both updates on the same table: test_tab_1 as follows:

begin 
  prt('Start Main Trx');
  update test_tab_1 set x=retx(x, 3);
  update_tab_1(6);
  commit;
end;
/
In the test output, there are also two lines of "****** No Transaction Visible ******" as above, in which the second one shows no Transaction, but one TM lock and one TX lock.

In addition to that, it throws "ORA-00060: deadlock".

===============Start Main Trx at 14-MARCH-2018 14:16:33===============
 ----1.----- Session
  sid=550, serial#=28708, saddr=00000000B8D16608
 ----2.----- Transaction
    ****** No Transaction Visible ******
 ----3.----- Locks
===============retx at 14-MARCH-2018 14:16:33===============
 ----1.----- Session
  sid=550, serial#=28708, saddr=00000000B8D16608
 ----2.----- Transaction
  addr=00000000AD763D60, start_time=05/14/20 14:16:32, start_scn=9684875117947, xid=6800100069270000, xidusn=104, xidslot=16, xidsqn=10089
 ----3.----- Locks
  addr=00007F758AA7EAC0, kaddr=00007F758AA7EAE8, sid=550, type=TM, id1=3307704, id2=0, lmode=3, request=0, ctime=0, block=0
  addr=00000000AD763D60, kaddr=00000000AD763D98, sid=550, type=TX, id1=6815760(rbs=104, slot=16), id2=10089, lmode=6, request=0, ctime=0, block=0

===============Start autonomous_TRX at 14-MARCH-2018 14:16:36===============
 ----1.----- Session
  sid=550, serial#=28708, saddr=00000000B8D16608
 ----2.----- Transaction
    ****** No Transaction Visible ******
 ----3.----- Locks
  addr=00007F758AA72BA0, kaddr=00007F758AA72BC8, sid=550, type=TM, id1=3307704, id2=0, lmode=3, request=0, ctime=3, block=0
  addr=00000000AD763D60, kaddr=00000000AD763D98, sid=550, type=TX, id1=6815760(rbs=104, slot=16), id2=10089, lmode=6, request=0, ctime=3, block=0
begin
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at "S.UPDATE_TAB_1", line 5
ORA-06512: at line 4
Here the "ORA-00060: deadlock" trace file, which contains Deadlock graph and call stack (subroutine "ksqgtlctx" is for "ksqgtl" in 10704 trace).

Deadlock graph:
                                       ------------Blocker(s)-----------  ------------Waiter(s)------------
Resource Name                          process session holds waits serial  process session holds waits serial
TX-00680010-00002769-00000000-00000000      57     550     X        28708      57     550           X  28708

0: waiting for 'enq: TX - row lock contention'
   name|mode=0x54580006, usn<<16 | slot=0x680010, sequence=0x2769

xid:  0x0068.010.00002769 

------- Binary Stack Dump --------

   [1]  (ksedst1()+95 -> kgdsdst())
   [2]  (ksedst()+58 -> ksedst1())
   [3]  (dbkedDefDump()+23080 -> ksedst())
   [4]  (ksedmp()+577 -> dbkedDefDump())
   [5]  (ksqdld_hdr_dump()+4900 -> ksedmp())
   [6]  (ksqcmi()+25602 -> ksqdld_hdr_dump())
   [7]  (ksqgtlctx()+6620 -> ksqcmi())
   [8]  (ksqgelctx()+838 -> ksqgtlctx())
   [9]  (ktuGetTxForXid()+324 -> ksqgelctx())
   [10] (ktcwit1()+367 -> ktuGetTxForXid())
   [11] (kdddgb()+5478 -> ktcwit1())
   [12] (kdusru()+458 -> kdddgb())
   [13] (kauupd()+356 -> kdusru())
   [14] (updrow()+1699 -> kauupd())
   [15] (qerupUpdRow()+725 -> updrow())
   [16] (qerupRopRowsets()+259 -> qerupUpdRow())
   [17] (kdstf000110100001000km()+1757 -> qerupRopRowsets())
   [18] (kdsttgr()+2154 -> kdstf000110100001000km())
   [19] (qertbFetch()+1089 -> kdsttgr())
   [20] (qerupFetch()+532 -> qertbFetch())
   [21] (updaul()+1416 -> qerupFetch())
   [22] (updThreePhaseExe()+340 -> updaul())
   [23] (updexe()+443 -> updThreePhaseExe())
   [24] (opiexe()+11815 -> updexe())
In fact, in the above tests, if repeatedly running query below during the test, we can also see two TM and TX locks, but XID is null. Immediately before session raising "ORA-00060: deadlock", the same session holds TX in LMODE=6, but still make REQUEST=6, hence BLOCK=1, a typical cyclic deadlock.

SQL > select s.sid, l.*, t.xid 
      from v$session s, v$lock l, v$transaction t 
      where s.sid=l.sid and s.taddr=t.addr(+) and s.sid = 550;

  SID ADDR             KADDR            SID TY      ID1   ID2 LMODE REQUEST CTIME BLOCK  XID
  --- ---------------- ---------------- --- -- -------- ----- ----- ------- ----- -----  ---
  550 00007F80B92BC410 00007F80B92BC438 550 TM  3307704     0     3       0     6     0 
  550 00007F80B92BC410 00007F80B92BC438 550 TM  3307704     0     3       0     3     0 
  550 00000000B5B9B958 00000000B5B9B988 550 AE      100     0     4       0  5019     0 
  550 00000000B5B9DE30 00000000B5B9DE60 550 TX  7012376 16581     0       6     3     0 
  550 00000000AD67B2C0 00000000AD67B2F8 550 TX  7012376 16581     6       0     6     1 
For Oracle applications, it means that we cannot rely on v$transaction to determine the existence of Oracle transactions, as well as TX and TM Locks. There are the time period, in which transactions are hidden, thus not visible in v$transaction.