6

SELECTステートメント内でユーザー定義関数を使用すると、いくつかの興味深い動作が発生します。

単一のテーブルからデータを読み取ってパージするストアドプロシージャがいくつかあります。これらのストアドプロシージャは、複数のソースで使用されています。

私の観察では、ユーザー定義関数は、それが使用されているSELECTステートメントの実行直後または実行中に常に評価されるとは限らず、任意に評価されることがあるようです。

たとえば、ストアドプロシージャでは、selectステートメントは次のようになります。

SELECT Something, MyFunction(Something) FROM Somewhere;

これに続いて、テーブルからデータをパージする別のストアドプロシージャが呼び出されます。パージされるデータの量は、読み取られた最大IDを格納する別のテーブルによって管理されます。これは、実行中のストアドプロシージャの別のインスタンスによってまだ読み取られていないデータがパージによって削除されないようにするためです。

私のテストコードでは、MyFunctionはテーブルSomewhereの行数を返すだけです。したがって、SELECTステートメントが返す行数と常に同じである必要があると思います。ただし、このストアドプロシージャの2つのインスタンスを実行すると、次のような結果が得られます。

最初のクエリインスタンス:

Something  MyFunction(Something)
---------  ---------------------
A          3
B          3
C          3

2番目のクエリインスタンス:

Something  MyFunction(Something)
---------  ---------------------
A          0
B          0
C          0    

2番目のクエリがすべての行を返すのに、同じテーブルを操作するユーザー定義関数が、テーブルにこれ以上行がないことを報告するのはなぜですか?

とにかく、2番目のクエリインスタンスが一貫していることを確認できますか?ユーザー定義関数は、親ストアドプロシージャが表示しているのと同じデータを引き続き表示しますか?

4

1 に答える 1

9

一般に、発生している問題は、Oracleのマルチバージョン読み取りの一貫性により、単一のSQLステートメントが常にデータの一貫したビューを表示することを保証しますが、同じ一貫性は、すべてのSQLステートメントが元のSQLステートメントによって呼び出された関数は、元のステートメントと同じデータセットを参照します。

実際には、それは次のようなことを意味します

SELECT something,
       COUNT(*) OVER ()
  FROM table_name

関数にまったく同じロジックを配置すると、常に正しい答えが返されます(クエリが3行を返す場合は3)

CREATE OR REPLACE FUNCTION count_table_name
  RETURN NUMBER
AS
  l_cnt INTEGER;
BEGIN
  SELECT COUNT(*)
    INTO l_cnt
    FROM table_name;
  RETURN l_cnt;
END;

そのSQLステートメント

SELECT something,
       count_table_name
  FROM table_name

テーブル内の行数に一致する値を返すとは限りません(また、すべての行に対して同じ結果を返すとは限りません)。別のセッションでデータを変更できるように関数に遅延を組み込むと、実際に動作していることがわかります。例えば

SQL> create table foo( col1 number );

Table created.

SQL> insert into foo select level from dual connect by level <= 3;

3 rows created.

行ごとに10秒の遅延を追加する関数を作成します

SQL> ed
Wrote file afiedt.buf

  1  create or replace function fn_count_foo
  2    return number
  3  is
  4    l_cnt  integer;
  5  begin
  6    select count(*)
  7      into l_cnt
  8      from foo;
  9    dbms_lock.sleep(10);
 10    return l_cnt;
 11* end;
 12  /

Function created.

ここで、セッション1の場合、ステートメントを開始します

select col1, fn_count_foo
  from foo;

次に、セッション2に切り替えて、新しい行を挿入します

SQL> insert into foo values( 4 );

1 row created.

SQL> commit;

Commit complete.

SQLステートメント自体は3行しか表示しないにもかかわらず、関数は2回目の実行中に新しくコミットされた行を表示することがわかります。

SQL> select col1, fn_count_foo
  2    from foo;

      COL1 FN_COUNT_FOO
---------- ------------
         1            3
         2            4
         3            4

SQLステートメントを実行する前に、セッションでシリアル化可能なトランザクション分離レベルを使用することで、この問題を回避できます。したがって、たとえば、

セッション1で、トランザクション分離レベルをシリアル化可能に設定し、クエリを開始します

SQL> set transaction isolation level serializable;

Transaction set.

SQL> select col1, fn_count_foo
  2    from foo;

セッション2で、新しい行を挿入します

SQL> insert into foo values( 5 );

1 row created.

SQL> commit;

Commit complete.

セッション1が40秒後に戻ったとき、すべてが一貫しています

SQL> select col1, fn_count_foo
  2    from foo;

      COL1 FN_COUNT_FOO
---------- ------------
         1            4
         2            4
         3            4
         4            4
于 2012-06-05T23:28:56.790 に答える