4

これは長くなりそうなので、ここに簡単な要約を示します。トップ N のクエリを使用した場合でもCOUNT STOPKEYORDER BY STOPKEYそのプランに含まれている場合でも、正当な理由もなく低速です。

では、詳細です。それは遅い関数から始まります。実際には、正規表現を使用した文字列操作が含まれます。デモンストレーションの目的で、意図的にばかげた再帰的なフィボナッチ アルゴリズムを次に示します。約 25 までの入力ではかなり速く、約 30 では遅く、35 ではばかげていることがわかりました。

-- I repeat: Please no advice on how to do Fibonacci correctly.
-- This is slow on purpose!
CREATE OR REPLACE FUNCTION tmp_fib (
  n INTEGER
)
  RETURN INTEGER
AS
BEGIN
  IF n = 0 OR n = 1 THEN
    RETURN 1;
  END IF;
  RETURN tmp_fib(n-2) + tmp_fib(n-1);
END;
/

名前と番号のリストです。

CREATE TABLE tmp_table (
  name VARCHAR2(20) UNIQUE NOT NULL,
  num NUMBER(2,0)
);
INSERT INTO tmp_table (name,num)
  SELECT 'Alpha',    10 FROM dual UNION ALL
  SELECT 'Bravo',    11 FROM dual UNION ALL
  SELECT 'Charlie',  33 FROM dual;

スロー クエリの例を次に示します。スロー フィボナッチ関数を使用して、num が 2 桁のフィボナッチ数を生成する行を選択します。

SELECT p.name, p.num
FROM tmp_table p
WHERE REGEXP_LIKE(tmp_fib(p.num), '(.)\1')
ORDER BY p.name;

これは 11 と 33 に当てはまり、出力にはBravoCharlieが含まれます。実行には約 5 秒かかりますが、そのほとんどは の遅い計算ですtmp_fib(33)。そのため、遅いクエリを上位 N クエリに変換することで、より高速なバージョンを実行したいと考えています。N=1 の場合、次のようになります。

SELECT * FROM (
  SELECT p.name, p.num
  FROM tmp_table p
  WHERE REGEXP_LIKE(tmp_fib(p.num), '(.)\1')
  ORDER BY p.name
)
WHERE ROWNUM <= 1;

そして今、それはトップの結果を返しますBravo. しかし、実行にはまだ 5 秒かかります。唯一の説明は、 tmp_fib(33)その計算の結果が結果と無関係であるにもかかわらず、まだ計算中であるということです。それが出力されると判断できたはずBravoなので、テーブルの残りの WHERE 条件をテストする必要はありません。

tmp_fibたぶん、オプティマイザは高価であると言われる必要があるだけだと思いました 。そこで、次のように伝えてみました。

ASSOCIATE STATISTICS WITH FUNCTIONS tmp_fib DEFAULT COST (999999999,0,0);

これにより、プランの一部のコスト数値が変更されますが、クエリの実行が速くなるわけではありません。

SELECT * FROM v$versionこれがバージョンに依存する場合の出力:

Oracle Database 11g Enterprise Edition Release 11.2.0.2.0 - 64bit Production
PL/SQL Release 11.2.0.2.0 - Production
CORE    11.2.0.2.0      Production
TNS for 64-bit Windows: Version 11.2.0.2.0 - Production
NLSRTL Version 11.2.0.2.0 - Production

そして、これがトップ 1 クエリのオートトレースです。クエリに 1 秒かかったと主張しているように見えますが、そうではありません。5秒ほど走りました。

NAME                        NUM
-------------------- ----------
Bravo                        11


Execution Plan
----------------------------------------------------------
Plan hash value: 548796432

-------------------------------------------------------------------------------------
| Id  | Operation               | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |           |     1 |    55 |     4  (25)| 00:00:01 |
|*  1 |  COUNT STOPKEY          |           |       |       |            |          |
|   2 |   VIEW                  |           |     1 |    55 |     4  (25)| 00:00:01 |
|*  3 |    SORT ORDER BY STOPKEY|           |     1 |    55 |     4  (25)| 00:00:01 |
|*  4 |     TABLE ACCESS FULL   | TMP_TABLE |     1 |    55 |     3   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(ROWNUM<=1)
   3 - filter(ROWNUM<=1)
   4 - filter( REGEXP_LIKE (TO_CHAR("TMP_FIB"("P"."NUM")),'(.)\1'))

Note
-----
   - dynamic sampling used for this statement (level=2)


Statistics
----------------------------------------------------------
         27  recursive calls
          0  db block gets
         25  consistent gets
          0  physical reads
          0  redo size
        593  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

UPdATE : コメントで述べたように、INDEXヒントはこのクエリに大いに役立ちます。私の現実世界のシナリオにうまく当てはまらないとしても、正しい答えとして受け入れられるだけで十分です。そして皮肉なことに、オラクルはその経験から学んだようで、現在はINDEXデフォルトでプランを選択しています。NO_INDEX元の遅い動作を再現するように指示する必要があります。

実際のシナリオでは、より複雑なソリューションを適用し、クエリを PL/SQL 関数として書き直しました。fib問題に適用された私のテクニックは次のようになります。

CREATE OR REPLACE PACKAGE tmp_package IS
  TYPE t_namenum IS TABLE OF tmp_table%ROWTYPE;
  FUNCTION get_interesting_names (howmany INTEGER) RETURN t_namenum PIPELINED;
END;
/

CREATE OR REPLACE PACKAGE BODY tmp_package IS
  FUNCTION get_interesting_names (howmany INTEGER) RETURN t_namenum PIPELINED IS
    CURSOR c IS SELECT name, num FROM tmp_table ORDER BY name;
    rec c%ROWTYPE;
    outcount INTEGER;
  BEGIN
    OPEN c;
    outcount := 0;
    WHILE outcount < howmany LOOP
      FETCH c INTO rec;
      EXIT WHEN c%NOTFOUND;
      IF REGEXP_LIKE(tmp_fib(rec.num), '(.)\1') THEN
        PIPE ROW(rec);
        outcount := outcount + 1;
      END IF;
    END LOOP;
  END;
END;
/

SELECT * FROM TABLE(tmp_package.get_interesting_names(1));

質問を読んでテストを実行し、実行計画を理解するのを手伝ってくれた回答者に感謝します。彼らが提案する方法でこの質問を破棄します。

4

2 に答える 2