【问题标题】:Strange behavior with large Object Types大型对象类型的奇怪行为
【发布时间】:2011-02-08 12:57:56
【问题描述】:

我认识到,当实例变大时,在 Oracle 对象类型上调用方法需要更长的时间。

下面的代码只是将行添加到存储在 Object Type 中的集合中,并在循环中调用 empty dummy-procedure。

当集合中有更多行时,调用会花费更长的时间。当我删除对dummy 的调用时,性能好多了(集合仍然包含相同数量的记录):

Calling dummy:               Not calling dummy:
11                           0
81                           0
158                          0

要重现的代码:

Create Type t_tab Is Table Of VARCHAR2(10000);

Create Type test_type As Object(
  tab t_tab,
  Member Procedure dummy
);

Create Type Body test_type As
  Member Procedure dummy As Begin
    Null;  --# Do nothing
  End dummy;
End;


Declare
  v_test_type  test_type := New test_type( New t_tab() );

  Procedure run_test As
    start_time  NUMBER := dbms_utility.get_time;
  Begin
    For i In 1 .. 200 Loop
      v_test_Type.tab.Extend;
      v_test_Type.tab(v_test_Type.tab.Last) := Lpad(' ', 10000);
      v_test_Type.dummy();  --# Removed this line in second test
    End Loop;
    dbms_output.put_line( dbms_utility.get_time - start_time );
  End run_test;

Begin
  run_test;
  run_test;
  run_test;
End;

我尝试了 10g11g
谁能解释/重现这种行为?

【问题讨论】:

    标签: sql performance oracle plsql object-type


    【解决方案1】:

    我可以在我的 11.1.0.7 数据库上重现该行为。我不确定我是否有解释,但我确实有一个理论。

    如果您将 Extend 调用移到循环之外并仅向集合中添加 200 个元素,性能下降就会消失(见下文)。这让我相信,问题不仅仅是调用对象方法的行为——似乎与集合的低效扩展 1 个元素 200 次而不是 200 个元素 1 次的一些交互。

    SQL> ed
    Wrote file afiedt.buf
    
      1  Declare
      2    v_test_type  test_type := New test_type( New t_tab() );
      3    Procedure run_test As
      4      start_time  NUMBER := dbms_utility.get_time;
      5    Begin
      6      v_test_Type.tab.Extend(200);
      7      For i In 1 .. 200 Loop
      8        v_test_Type.tab(v_test_Type.tab.Last) := Lpad(' ', 10000);
      9        v_test_Type.dummy();  --# Removed this line in second test
     10      End Loop;
     11      dbms_output.put_line( dbms_utility.get_time - start_time );
     12    End run_test;
     13  Begin
     14    run_test;
     15    run_test;
     16    run_test;
     17* End;
    SQL> /
    11
    9
    10
    
    PL/SQL procedure successfully completed.
    

    在这里推测,但如果对过程的调用可能会修改集合,编译器可能会对扩展集合的调用进行一些优化,而这些优化无法(或不会)进行。

    作为对这种推测的快速测试,我创建了一个成员函数而不是一个成员过程,并在循环中调用了该函数。由于函数不会修改对象状态,因此它们不会排除我所推测的那种优化。果然,如果我用成员函数创建对象类型,性能下降就消失了

    SQL> ed
    Wrote file afiedt.buf
    
      1  Create or replace Type test_type As Object(
      2    tab t_tab,
      3    Member Procedure dummy,
      4    Member Function dummy2 return number
      5* );
    SQL> /
    
    Type created.
    
    SQL> ed
    Wrote file afiedt.buf
    
      1  Create or replace Type Body test_type As
      2    Member Procedure dummy As Begin
      3      Null;  --# Do nothing
      4    End dummy;
      5    Member Function dummy2
      6      return number
      7    Is
      8    Begin
      9      Return 1;
     10    End dummy2;
     11* End;
     12  /
    
    Type body created.
    
    SQL> ed
    Wrote file afiedt.buf
    
      1  Declare
      2    v_test_type  test_type := New test_type( New t_tab() );
      3    Procedure run_test As
      4      start_time  NUMBER := dbms_utility.get_time;
      5      l_num       NUMBER;
      6    Begin
      7      For i In 1 .. 200 Loop
      8        v_test_Type.tab.Extend;
      9        v_test_Type.tab(v_test_Type.tab.Last) := Lpad(' ', 10000);
     10        l_num := v_test_Type.dummy2();  --# Removed this line in second test
     11      End Loop;
     12      dbms_output.put_line( dbms_utility.get_time - start_time );
     13    End run_test;
     14  Begin
     15    run_test;
     16    run_test;
     17    run_test;
     18* End;
     19  /
    11
    9
    9
    
    PL/SQL procedure successfully completed.
    

    最后,在我看来,有问题的语句是 Extend,但优化器足够聪明,如果循环中没有任何东西能够修改对象,则能够避免惩罚。

    【讨论】:

    • @Justin Cave:谢谢,但恐怕你的回答不正确。 function 部分非常有趣,但是您的测试集 v_test_Type.tab(v_test_Type.tab.Last),因此您总是覆盖 id 200400600 中的值,这使您的对象保持小,而我的解决方案总是将集合增加一,所以我填写了每个id。不过,我花了一些时间来解决这个问题:)
    • @Justin Cave:再次感谢,发现了我自己——如果你有兴趣,请看我的回答。
    【解决方案2】:

    自己发现,问题描述在Using SELF IN OUT NOCOPY with Member Procedures

    在成员过程中,如果没有声明SELF,则其参数模式默认为IN OUT

    因此,每次调用过程时,我的整个对象都被复制了两次,并且随着大小的增加,这需要的时间越来越长。


    解决方案是使用SELF IN OUT NOCOPY test_type 作为我的过程声明的第一个参数:

    Create Type test_type As Object(
      tab t_tab,
      Member Procedure dummy(SELF IN OUT NOCOPY test_type)
    );
    

    并且仍然在没有参数的情况下调用

    v_test_type.dummy();
    

    性能恢复正常:

    0
    0
    0
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-14
      • 1970-01-01
      • 2016-02-29
      • 2014-10-23
      • 2019-05-25
      • 2018-06-30
      • 2021-01-05
      相关资源
      最近更新 更多