Question

I'm developing some nightly based batch jobs in which I'm using rowids to locate the record in the table but have always a fallback to some logical key or something if the rowid is not passed.

I developed a little test case to show you what my problem has been and to provide something you can replicate on your own environment.

Create the table and put some data inside

create table table_with_4M_records (varchar_column varchar2(7));

begin
  for i in 1..4000000
  loop
    insert into table_with_4M_records(varchar_column) values (''||lpad(i,7,0));
  end loop;
end;

And what I found is that if I run something like:

DECLARE
  w_rowid constant VARCHAR2(30) := 'AAF6CCAAnAAAaz9AHX';
  w_counter NUMBER;
  w_t0 TIMESTAMP;
  w_t1 TIMESTAMP;
  w_time_diff_in_ms NUMBER:=null;
BEGIN
  w_t0 := SYSTIMESTAMP;
  SELECT count(*) 
  INTO w_counter 
  FROM table_with_4M_records 
  WHERE ((w_rowid IS NOT NULL AND ROWID = w_rowid )
          OR
          (w_rowid IS NULL  /*Then do some heavy operations which I only want to do if the rowid comes with no value*/ )
        );
  w_t1 := SYSTIMESTAMP;      
  SELECT EXTRACT(DAY FROM diff )*24*60*60*1000 +             --Days
         EXTRACT(HOUR FROM diff )*60*60*1000 +               --Hours
         EXTRACT(MINUTE FROM diff )*60*1000 +                --Minutes
   round(EXTRACT(SECOND FROM diff )*1000)total_milliseconds  --Seconds
    INTO w_time_diff_in_ms
  FROM (SELECT (w_t1-w_t0) diff FROM dual);

  dbms_output.put_line('REC COUNTER: '||w_counter);
  dbms_output.put_line('APROX EXEC TIME IN MILLISECS: '||w_time_diff_in_ms);

END;

I get a fetch time of several seconds, something like 5 seconds... weird right? Because I'm using ROWID, should be a direct fetch.

But if I replace the w_rowid in the code with the value. Like so:

DECLARE
  w_rowid constant VARCHAR2(30) := 'AAAGg5AAWAAAaffAA0';
  w_counter NUMBER;
  w_t0 TIMESTAMP;
  w_t1 TIMESTAMP;
  w_time_diff_in_ms NUMBER:=null;
BEGIN
  w_t0 := SYSTIMESTAMP;
  SELECT count(*) 
  INTO w_counter 
  FROM table_with_4M_records 
  WHERE (('AAF6CCAAnAAAaz9AHX' IS NOT NULL AND ROWID = 'AAF6CCAAnAAAaz9AHX' )
          OR
          ('AAF6CCAAnAAAaz9AHX' IS NULL  /*Then do some heavy operations witch I only want to do if the rowid comes with no value*/ )
        );
  w_t1 := SYSTIMESTAMP;      
  SELECT EXTRACT(DAY FROM diff )*24*60*60*1000 +             --Days
         EXTRACT(HOUR FROM diff )*60*60*1000 +               --Hours
         EXTRACT(MINUTE FROM diff )*60*1000 +                --Minutes
   round(EXTRACT(SECOND FROM diff )*1000)total_milliseconds  --Seconds
    INTO w_time_diff_in_ms
  FROM (SELECT (w_t1-w_t0) diff FROM dual);

  dbms_output.put_line('REC COUNTER: '||w_counter);
  dbms_output.put_line('APROX EXEC TIME IN MILLISECS: '||w_time_diff_in_ms);

END;

I get an execution time of almost zero. (???)

I found out that the problem is mainly the check if null and if I remove/comment that code I get times near to zero as well...

The last snippet of code is here:

DECLARE
  w_rowid constant VARCHAR2(30) := 'AAF6CCAAnAAAaz9AHX';
  w_counter NUMBER;
  w_t0 TIMESTAMP;
  w_t1 TIMESTAMP;
  w_time_diff_in_ms NUMBER:=null;
BEGIN
  w_t0 := SYSTIMESTAMP;
  SELECT count(*) 
  INTO w_counter 
  FROM table_with_4M_records 
  WHERE ((w_rowid IS NOT NULL AND ROWID = w_rowid )
          --OR
          --(w_rowid IS NULL  /*Then do some heavy operations witch I only want to do if the rowid comes with no value*/ )
        );
  w_t1 := SYSTIMESTAMP;      
  SELECT EXTRACT(DAY FROM diff )*24*60*60*1000 +             --Days
         EXTRACT(HOUR FROM diff )*60*60*1000 +               --Hours
         EXTRACT(MINUTE FROM diff )*60*1000 +                --Minutes
   round(EXTRACT(SECOND FROM diff )*1000)total_milliseconds  --Seconds
    INTO w_time_diff_in_ms
  FROM (SELECT (w_t1-w_t0) diff FROM dual);

  dbms_output.put_line('REC COUNTER: '||w_counter);
  dbms_output.put_line('APROX EXEC TIME IN MILLISECS: '||w_time_diff_in_ms);

END;

If any of you have any good tip to overcome this problem I would really appreciate. If any of you could test this code on your local environment with different versions of oracle I would appreciate as well.

I'm currently running Oracle 9.2I and I thought that the optimizer would be smarter and realize that I'm using a constant varchar2 that isn't changed inside the query. If it is not null the first time it will be not null in all test cases on that query... I was clearly wrong

Thank you very much.

Was it helpful?

Solution

In the first case:

WHERE ((w_rowid IS NOT NULL AND ROWID = w_rowid ) OR
          (w_rowid IS NULL  /*Then do some heavy operations*/ )
        );

the query optimizer doesn't do a 'lazy' evaluation of the OR condition. It evaluates everything including your 'heavy operations'. It's not smart enough to recognize that you declared w_rowid as constant.

In the second case:

WHERE (('AAF6CCAAnAAAaz9AHX' IS NOT NULL AND ROWID = 'AAF6CCAAnAAAaz9AHX' ) OR
          ('AAF6CCAAnAAAaz9AHX' IS NULL  /*Then do some heavy operations*/ )
        );

the optimizer can simplify the expression at compile-time because of the constant value, so it ignores the second half of the OR.

OTHER TIPS

Why do you even need to include w_rowid in the SQL code? You know ahead of time if it is null or not, so hoist it out to the PL/SQL code and optimize your SQL queries for the two different cases (remove some code for clarity):

DECLARE
  w_rowid constant VARCHAR2(30) := 'AAF6CCAAnAAAaz9AHX';
  w_counter NUMBER;

BEGIN
  IF w_rowid IS NOT NULL THEN
      SELECT count(*) 
      INTO w_counter 
      FROM table_with_4M_records 
      WHERE ROWID = w_rowid;
  ELSE
      /* do another select if w_rowid is null */
  END IF;


END;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top