Sunday, July 10, 2016

SubProgram Inlining

One Optimization that PL/SQL Compiler can perform is known as “Subprogram in lining”, that replaces the subprogram invocation with copy of the invoked subprogram (as long as if the invoked and invoking subprograms are in the same program unit). Subprogram inline can be controlled by this parameter PLSQL_OPTIMIZE_LEVEL with its default value (which is set to 2) or set it to 3.

With plsql_optimize_level=2 (the default value), you must specify each subprogram to be in lined with the INLINE pragma explicitly, with plsql_optimize_level set to 3 the compiler will make the Subprogram in liningimplicitly as part of compilation process.

Let start the demo for the “subprogram in-lining” with a simple procedure that check the inputs parameter to be purely numeric or not.

demo@ORA11G> create or replace procedure is_num
  2  as
  3     function check_num(x varchar2)
  4     return varchar2 as
  5             x1 int;
  6     begin
  7             x1 := to_number(x);
  8             return 'Y';
  9             exception
 10                     when others then
 11                     return 'N';
 12     end;
 13  begin
 14     dbms_output.put_line(' 1 ='|| check_num('1'));
 15     dbms_output.put_line(' A ='|| check_num('A'));
 16  end;
 17  /

Procedure created.

demo@ORA11G> exec is_num;
 1 =Y
 A =N

PL/SQL procedure successfully completed.

demo@ORA11G>

So far look good; now let’s see what happens when PLSQL_OPTIMIZE_LEVEL is set to 3.

demo@ORA11G>
demo@ORA11G> alter procedure is_num compile plsql_optimize_level=3;

Procedure altered.

demo@ORA11G> exec is_num;
 1 =Y
 A =Y

PL/SQL procedure successfully completed.

demo@ORA11G>

This is not a BUG! This is documented:

What has happened, when this procedure is recompiled with PLSQL_OPTIMIZE_LEVEL is set to 3?
  • We know that variable X1 is unreferenced, don’t use it anywhere other than the assignment.
  • We know that to_number() is deterministic
  • We know that to_number() is pure. (Don’t make any changes to database tables, don’t write contents to file, don’t make changes to package global variables)

So the above code check_num() inside the function, becomes like this.

Begin
  Return ‘Y’;
End;

So the right way to code the above “check_num()” function would be to explicitly disable Pragma In lining.

demo@ORA11G> create or replace procedure is_num
  2  as
  3     function check_num(x varchar2)
  4     return varchar2 as
  5             x1 int;
  6     begin
  7             x1 := to_number(x);
  8             return 'Y';
  9             exception
 10                    when others then
 11                     return 'N';
 12     end;
 13  begin
 14     pragma inline(check_num,'NO');
 15     dbms_output.put_line(' 1 ='|| check_num('1'));
 16     pragma inline(check_num,'NO');
 17     dbms_output.put_line(' A ='|| check_num('A'));
 18  end;
 19  /

Procedure created.

demo@ORA11G> exec is_num;
 1 =Y
 A =N

PL/SQL procedure successfully completed.

demo@ORA11G> alter procedure is_num compile plsql_optimize_level=3;

Procedure altered.

demo@ORA11G> exec is_num;
 1 =Y
 A =N

PL/SQL procedure successfully completed.

demo@ORA11G>

You can see the output remains the same in both cases, even when the program is recompiled with PLSQL_OPTIMIZE_LEVEL is set to 3

Other possible approach would be rewriting the function “check_num” to avoid the un-referenced variables.

demo@ORA11G> create or replace procedure is_num
  2  as
  3     function check_num(x varchar2)
  4     return varchar2 as
  5             x1 int;
  6     begin
  7             x1 := to_number(x);
  8             if x1 is not null then
  9                     return 'Y';
 10             else
 11                     return 'N';
 12             end if;
 13             exception
 14                     when others then
 15                     return 'N';
 16     end;
 17  begin
 18     dbms_output.put_line(' 1 ='|| check_num('1'));
 19     dbms_output.put_line(' A ='|| check_num('A'));
 20  end;
 21  /

Procedure created.

demo@ORA11G> exec is_num;
 1 =Y
 A =N

PL/SQL procedure successfully completed.

demo@ORA11G> alter procedure is_num compile plsql_optimize_level=3;

Procedure altered.

demo@ORA11G> exec is_num;
 1 =Y
 A =N

PL/SQL procedure successfully completed.

demo@ORA11G>


Again, output remains the same in both cases, even when the program is recompiled with PLSQL_OPTIMIZE_LEVEL is set to 3.

Direct path operations on Reference partitioned Table

Direct path operation is the fastest way to load a huge set of data into an Oracle database, direct path operation
  • Will be done implicitly during Parallel DML operations
  • Explicitly requested using APPEND hints in sql’s
  • By setting the parameter direct=Y in sql* loader 

That being said, each of the following is a benefit in some cases, each of the following could be a disaster in other cases.

If you direct path load,
  • The transaction that did the direct path load CANNOT query that segment - but other transactions can, they just cannot see the newly loaded data
  • You never use any existing free space, it always writes above the high water mark
  • Bypass UNDO on the table - only the table - modifications (datadictionary updates)
  • Will maintain indexes - we build mini indexes on the newly loaded data and merge them into the 'real' indexes in bulk. A direct path load of large amounts of data will maintain indexes very efficiently.
  • Can bypass redo on the TABLE in archivelog mode, if the database is set up to allow nologging and you have the segment set to nologging
  • Direct path loading bypasses the buffer cache, you write directly to the datafiles
  • Direct path loading is only appropriate for the first load of a segment or an increment load of lots of data - or an increment load into a table that never has any deletes (so there is no free space to consider)

Transactional systems – probably won’t use it
Warehousing system – tool, we will use a lot.

However – direct path loading is not possible (turned off silently), if the table to load the data has
  • Either a row level or statement level trigger in “ENABLED” status
  • If foreign key is defined on the table, direct path will go-away.


demo@ORA12C> create table t1(x int primary key, y int);

Table created.

demo@ORA12C> create table t2(x int);

Table created.

demo@ORA12C> insert into t1(x,y) values(1,1);

1 row created.

demo@ORA12C> commit;

Commit complete.

demo@ORA12C> insert /*+ append */ into t2(x)
2  select 1 from dual;

1 row created.

demo@ORA12C> select * from t2;
select * from t2
              *
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying it in parallel

That shows we did a direct path operation, definitely.  You cannot read from a table in the sametransaction that direct path loads it... Now

demo@ORA12C> rollback;

Rollback complete.

demo@ORA12C> alter table t2 add constraint t2_fk
2  foreign key(x)
3  references t1;

Table altered.

demo@ORA12C> insert /*+ append */ into t2(x)
2  select 1 from dual;

1 row created.

demo@ORA12C> select * from t2;

         X
----------
         1

1 row selected.

demo@ORA12C>

That shows the append was ignored, referential integrity makes append "go away".

But in case of reference partitioned Table, it goes like this.

demo@ORA12C> create table t1(x int primary key, y int)
2  partition by hash(y)
3  partitions 4;

Table created.

demo@ORA12C> create table t2(x int not null , constraint t2_fk
  2             foreign key(x) references t1)
3  partition by reference(t2_fk);

Table created.

demo@ORA12C>
demo@ORA12C> insert into t1(x,y) values(1,1);

1 row created.

demo@ORA12C> insert /*+ append */ into t2(x)
2  select 1 from dual;

1 row created.

demo@ORA12C> select * from t2;
select * from t2
              *
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying it in parallel


demo@ORA12C>


So with the presence of foreign key constraint, direct path operation is still possible only in reference partitioned Tables.