【问题标题】:ORACLE 9i PLSQL Optimizer causing some performance issues with constant VARIABLESORACLE 9i PLSQL 优化器导致常量变量的一些性能问题
【发布时间】:2014-05-21 15:27:55
【问题描述】:

我正在开发一些基于夜间的批处理作业,在这些作业中,我使用 rowids 来定位表中的记录,但如果 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;

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

我发现问题主要是检查是否为 null,如果我删除/注释该代码,我的时间也接近于零......

最后的sn-p代码在这里:

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。 如果第一次不为空,则在该查询的所有测试用例中都不为空......我显然错了

非常感谢。

【问题讨论】:

  • ROWID 不能保证在事务之外保持不变。数据库可以随意移动行,因此会更改 ROWID。 ROWID 不能替代主键。分享和享受。

标签: sql performance oracle plsql oracle9i


【解决方案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 的后半部分。

【讨论】:

    【解决方案2】:

    为什么您甚至需要在 SQL 代码中包含 w_rowid?您提前知道它是否为空,因此将其提升到 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;
    

    【讨论】:

    • 我明白你在说什么,但是导致这个问题的 qhery 是一个大查询,有 4 个连接和大约 50 列要分析。
    猜你喜欢
    • 1970-01-01
    • 2011-10-27
    • 2011-10-21
    • 2019-05-01
    • 1970-01-01
    • 2015-02-16
    • 1970-01-01
    • 2014-01-03
    • 2013-11-04
    相关资源
    最近更新 更多