Skip to Main Content
  • Questions
  • Library Cache Locks and Compiling New PL/SQL Code

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Kaley.

Asked: July 20, 2018 - 6:41 pm UTC

Last updated: July 31, 2018 - 10:16 am UTC

Version: 11.2.0.4

Viewed 1000+ times

You Asked

Hello! I have a friend that told me that compiling a new piece of Pl/SQL code will put library cache locks on the dependent objects, and that there's a risk for database contention when new code is deployed.

In other words, if I go out to a database and compile for the very first time when nobody else is trying to call or use the get_record_count procedure yet:

create procedure get_record_count
is
  v_record_count number;
begin

  select count(*) into v_record_count
  from my_dependent_table;

  log_record_count(v_record_count);

  dbms_output.put_line(v_record_count || ' records');
end get_record_count;


...my friend says that I risk putting library cache locks on MY_DEPENDENT_TABLE and/or the LOG_RECORD_COUNT procedure during the compilation, and as a result there could be database contention with the other sessions.

I've seen library cache locks firsthand when re-compiling existing code that someone else is trying to use, or when people are trying to use code that calls the code I'm re-compiling (for example, if I tried to re-compile LOG_RECORD_COUNT while someone was calling GET_RECORD_COUNT I can see where this would likely cause library cache lock contention). But I've never once seen contention as a result of new code being deployed that no one else is trying to run.

I suspect my friend is incorrect, but I'm not sure how to even write a test to verify whether or not this is true though.

First, is my friend correct or not?
Second, is possible to write a test that will prove this one way or another?

Thanks so much!

and Chris said...

OK, so we can test this. In one session, create the table, a procedure which queries it in an infinite loop, and run the procedure:

create table my_dependent_table (
  c1 int
);

create procedure infinite_loop
is
  v_record_count number;
begin

  loop
    select count(*) into v_record_count
    from my_dependent_table;
  end loop;

end infinite_loop;
/

exec infinite_loop;


If you try and compile infinite_loop in another session, it'll hang, stuck on "library cache pin":

alter procedure infinite_loop compile;


If, in yet another session, you try and create a modified version of get_record_count, you'll see it hangs on "library cache lock":

create procedure get_record_count
is
  v_record_count number;
begin

  select count(*) into v_record_count
  from my_dependent_table;

  dbms_output.put_line(v_record_count || ' records');
  
  infinite_loop; -- calling running procedure
  
end get_record_count;
/


So clearly it's possible you could run into issues when creating a procedure.

But how often to you create a procedure that accesses another procedure that someone else is waiting to compile? Almost never. I hope.

So let's start again. Kill the second and third sessions and reconnect. So you only have one session executing infinite_loop.

This time create another procedure that creates/drops get_record_count in an infinite loop. Then execute the procedure:

create procedure infinite_loop_create
is
  v_record_count number;
begin

  loop
    execute immediate q'!create procedure get_record_count
is
  v_record_count number;
begin

  select count(*) into v_record_count
  from my_dependent_table;

  dbms_output.put_line(v_record_count || ' records');
  
  infinite_loop;

end get_record_count;!';
    execute immediate 'drop procedure get_record_count';
  end loop;

end infinite_loop_create;
/

exec infinite_loop_create;


At this point you have two sessions. One querying my_dependent_table in an infinite loop. Another creating/dropping get_record_count forever. These should be merrily chugging along.

You can verify that infinite_loop_create is creating/dropping get_record_count by querying user_objects in another session. You should see this procedure disappear/reappear with an increasing date in created.

Now in a third sesison run the following:

SQL> create or replace function wait_fn ( p int )
  2    return int as
  3  begin
  4    dbms_lock.sleep ( 1 ) ;
  5    return p;
  6  end wait_fn;
  7  /

Function created.

SQL> set timing on
SQL> insert into my_dependent_table
  2    select wait_fn ( level ) from dual
  3    connect by level <= 5;

5 rows created.

Elapsed: 00:00:05.43
SQL> update my_dependent_table set c1 = c1 + 1
  2  where  c1 = wait_fn ( c1 ) ;

5 rows updated.

Elapsed: 00:00:05.31
SQL> commit;

Commit complete.

Elapsed: 00:00:00.38
SQL>
SQL> select * from my_dependent_table;

        C1
----------
         2
         3
         4
         5
         6

Elapsed: 00:00:00.47
SQL>
SQL> delete my_dependent_table
  2  where  c1 = wait_fn ( c1 ) ;

5 rows deleted.

Elapsed: 00:00:05.50
SQL>
SQL> select * from my_dependent_table;

no rows selected

Elapsed: 00:00:00.32
SQL>
SQL> commit;

Commit complete.

Elapsed: 00:00:00.31
SQL>
SQL> alter table my_dependent_table add c2 int;

Table altered.

Elapsed: 00:00:00.37
SQL>
SQL> desc my_dependent_table
 Name                                                              Null?    Type
 ----------------------------------------------------------------- -------- --------------------------------------------
 C1                                                                         NUMBER(38)
 C2                                                                         NUMBER(38)

SQL>
SQL> lock table my_dependent_table in exclusive mode;

Table(s) Locked.

Elapsed: 00:00:00.31


Wait_fn is to ensure that the DML statements take a few seconds to execute. So if creating/droppping get_record_count lead to library cache issues we should see them.

But I didn't. And all the statements took the expected amount of time (just over 5s or "instant"). And there were no library cache waits recorded in ASH or elsewhere.

So, we can:

- Run all DML against my_dependent_table
- Alter it
- Lock it

And in the third session, you can even run:

SQL> drop table my_dependent_table cascade constraints purge;

Table dropped.

Elapsed: 00:00:00.46


At which point the other two sessions will fail:

SQL> exec infinite_loop_create;
ERROR:
ORA-24344: success with compilation error
ORA-06512: at "CHRIS.INFINITE_LOOP_CREATE", line 7
ORA-06512: at line 1


SQL> exec infinite_loop;
BEGIN infinite_loop; END;

*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at "CHRIS.INFINITE_LOOP", line 7
ORA-06512: at line 1


So yes, it's possible you could run into library cache locking issues when creating new objects. There may be some other cases I haven't though of. And maybe things were different in older verions (I tested on 11.2.0.4 & 12.2.0.1). But in practice it's unlikely!

Is this answer out of date? If it is, please let us know via a Comment

More to Explore

Administration

Need more information on Administration? Check out the Administrators guide for the Oracle Database