ORACLE 9i PLSQL Optimizer使用常量VARIABLES导致一些性能问题

时间:2014-04-08 18:32:18

标签: sql performance oracle plsql oracle9i

我正在开发一些基于夜间的批处理作业,其中我使用rowid来查找表中的记录,但是如果没有传递rowid,则总是会回退到某个逻辑键或其他东西。

我开发了一个小测试用例来向您展示我的问题,并提供可以在您自己的环境中复制的内容。

创建表并在

中放入一些数据
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;

我发现的是,如果我运行类似的东西:

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;

我获得了几秒钟的获取时间,类似于5秒......奇怪吧?因为我使用的是ROWID,所以应该是直接获取。

但是如果我用代码替换代码中的w_rowid。像这样:

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;

我的执行时间几乎为零。 (???)

我发现问题主要是检查是否为空,如果我删除/评论该代码我也会接近零...

最后一段代码在这里:

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;

如果你们中有任何人有任何好的建议来克服这个问题我真的很感激。 如果您有任何人可以使用不同版本的oracle在本地环境中测试此代码,我也不胜感激。

我目前正在运行Oracle 9.2I,我认为优化器更聪明,并且意识到我正在使用在查询中未更改的常量varchar2。 如果它不是null,那么第一次在该查询的所有测试用例中都不为null ......我显然是错误的

非常感谢。

2 个答案:

答案 0 :(得分:1)

为什么甚至需要在SQL代码中包含w_rowid?您提前知道它是否为null,因此将其提升到PL / SQL代码并针对两种不同情况优化SQL查询(为清晰起见,删除一些代码):

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;

答案 1 :(得分:1)

在第一种情况下:

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

查询优化器不会对OR条件进行“延迟”评估。它会评估所有内容,包括您的“繁重操作”。认识到你将w_rowid声明为常量是不够聪明的。

在第二种情况下:

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

由于常量值,优化器可以在编译时简化表达式,因此忽略了OR的后半部分。