26

動作が少し遅い既存の Oracle データベース駆動型アプリケーションのクエリ時間を改善しようとしています。アプリケーションは、次のようないくつかの大規模なクエリを実行します。実行には 1 時間以上かかる場合があります。以下のクエリで を句に置き換えるDISTINCTと、GROUP BY実行時間が 100 分から 10 秒に短縮されました。私の理解はそれでSELECT DISTINCTありGROUP BY、ほとんど同じように動作しました。実行時間にこれほど大きな差があるのはなぜですか? バックエンドでクエリが実行される方法の違いは何ですか? SELECT DISTINCTより速く走る状況はありますか?

注: 次のクエリで、WHERE TASK_INVENTORY_STEP.STEP_TYPE = 'TYPE A'は、結果をフィルター処理できるさまざまな方法の 1 つにすぎません。この例は、列が含まれていないすべてのテーブルを結合する理由を示すために提供されており、SELECT使用可能なすべてのデータの約 10 分の 1 になります。

SQL を使用DISTINCT:

SELECT DISTINCT 
    ITEMS.ITEM_ID,
    ITEMS.ITEM_CODE,
    ITEMS.ITEMTYPE,
    ITEM_TRANSACTIONS.STATUS,
    (SELECT COUNT(PKID) 
        FROM ITEM_PARENTS 
        WHERE PARENT_ITEM_ID = ITEMS.ITEM_ID
        ) AS CHILD_COUNT
FROM
    ITEMS
    INNER JOIN ITEM_TRANSACTIONS 
        ON ITEMS.ITEM_ID = ITEM_TRANSACTIONS.ITEM_ID 
        AND ITEM_TRANSACTIONS.FLAG = 1
    LEFT OUTER JOIN ITEM_METADATA 
        ON ITEMS.ITEM_ID = ITEM_METADATA.ITEM_ID
    LEFT OUTER JOIN JOB_INVENTORY 
        ON ITEMS.ITEM_ID = JOB_INVENTORY.ITEM_ID     
    LEFT OUTER JOIN JOB_TASK_INVENTORY 
        ON JOB_INVENTORY.JOB_ITEM_ID = JOB_TASK_INVENTORY.JOB_ITEM_ID
    LEFT OUTER JOIN JOB_TASKS 
        ON JOB_TASK_INVENTORY.TASKID = JOB_TASKS.TASKID                              
    LEFT OUTER JOIN JOBS 
        ON JOB_TASKS.JOB_ID = JOBS.JOB_ID
    LEFT OUTER JOIN TASK_INVENTORY_STEP 
        ON JOB_INVENTORY.JOB_ITEM_ID = TASK_INVENTORY_STEP.JOB_ITEM_ID 
    LEFT OUTER JOIN TASK_STEP_INFORMATION 
        ON TASK_INVENTORY_STEP.JOB_ITEM_ID = TASK_STEP_INFORMATION.JOB_ITEM_ID
WHERE 
    TASK_INVENTORY_STEP.STEP_TYPE = 'TYPE A'
ORDER BY 
    ITEMS.ITEM_CODE

SQL を使用GROUP BY:

SELECT
    ITEMS.ITEM_ID,
    ITEMS.ITEM_CODE,
    ITEMS.ITEMTYPE,
    ITEM_TRANSACTIONS.STATUS,
    (SELECT COUNT(PKID) 
        FROM ITEM_PARENTS 
        WHERE PARENT_ITEM_ID = ITEMS.ITEM_ID
        ) AS CHILD_COUNT
FROM
    ITEMS
    INNER JOIN ITEM_TRANSACTIONS 
        ON ITEMS.ITEM_ID = ITEM_TRANSACTIONS.ITEM_ID 
        AND ITEM_TRANSACTIONS.FLAG = 1
    LEFT OUTER JOIN ITEM_METADATA 
        ON ITEMS.ITEM_ID = ITEM_METADATA.ITEM_ID
    LEFT OUTER JOIN JOB_INVENTORY 
        ON ITEMS.ITEM_ID = JOB_INVENTORY.ITEM_ID     
    LEFT OUTER JOIN JOB_TASK_INVENTORY 
        ON JOB_INVENTORY.JOB_ITEM_ID = JOB_TASK_INVENTORY.JOB_ITEM_ID
    LEFT OUTER JOIN JOB_TASKS 
        ON JOB_TASK_INVENTORY.TASKID = JOB_TASKS.TASKID                              
    LEFT OUTER JOIN JOBS 
        ON JOB_TASKS.JOB_ID = JOBS.JOB_ID
    LEFT OUTER JOIN TASK_INVENTORY_STEP 
        ON JOB_INVENTORY.JOB_ITEM_ID = TASK_INVENTORY_STEP.JOB_ITEM_ID 
    LEFT OUTER JOIN TASK_STEP_INFORMATION 
        ON TASK_INVENTORY_STEP.JOB_ITEM_ID = TASK_STEP_INFORMATION.JOB_ITEM_ID
WHERE 
    TASK_INVENTORY_STEP.STEP_TYPE = 'TYPE A'
GROUP BY
    ITEMS.ITEM_ID,
    ITEMS.ITEM_CODE,
    ITEMS.ITEMTYPE,
    ITEM_TRANSACTIONS.STATUS
ORDER BY 
    ITEMS.ITEM_CODE

を使用したクエリの Oracle クエリ プランは次のDISTINCTとおりです。

DISTINCT を使用したクエリの Oracle クエリ プラン

を使用したクエリの Oracle クエリ プランは次のGROUP BYとおりです。

GROUP BY を使用したクエリの Oracle クエリ プラン

4

4 に答える 4

20

パフォーマンスの違いは、おそらくSELECT節でのサブクエリの実行によるものです。個別ののすべての行に対してこのクエリを再実行していると推測しています。の場合、group by のgroup byに1 回実行されます。

代わりに、結合に置き換えてみてください。

select . . .,
       parentcnt
from . . . left outer join
      (SELECT PARENT_ITEM_ID, COUNT(PKID) as parentcnt
       FROM ITEM_PARENTS 
      ) p
      on items.item_id = p.parent_item_id
于 2012-12-19T16:39:52.670 に答える
18

私はそれをかなり確信してGROUP BYおりDISTINCT、ほぼ同じ実行計画を持っています。

推測する必要があるため (説明計画がないため)、ここでの違いは、インライン サブクエリが の後に実行されるGROUP BY、のに実行されるという IMO ですDISTINCT

したがって、クエリが 100 万行を返し、1k 行に集計される場合:

  • クエリはGROUP BYサブクエリを 1000 回実行しますが、
  • 一方、DISTINCTクエリはサブクエリを 1000000 回実行します。

tkprof Explain Plan は、この仮説を実証するのに役立ちます。


これについて議論している間、クエリの記述方法がリーダーとオプティマイザの両方に誤解を与えることに注意することが重要だと思います: item/item_transactions からTASK_INVENTORY_STEP.STEP_TYPE値が "タイプA」。

IMOあなたのクエリはより良い計画を立て、次のように書くと読みやすくなります:

SELECT ITEMS.ITEM_ID,
       ITEMS.ITEM_CODE,
       ITEMS.ITEMTYPE,
       ITEM_TRANSACTIONS.STATUS,
       (SELECT COUNT(PKID) 
          FROM ITEM_PARENTS 
         WHERE PARENT_ITEM_ID = ITEMS.ITEM_ID) AS CHILD_COUNT
  FROM ITEMS
  JOIN ITEM_TRANSACTIONS 
    ON ITEMS.ITEM_ID = ITEM_TRANSACTIONS.ITEM_ID 
   AND ITEM_TRANSACTIONS.FLAG = 1
 WHERE EXISTS (SELECT NULL
                 FROM JOB_INVENTORY   
                 JOIN TASK_INVENTORY_STEP 
                   ON JOB_INVENTORY.JOB_ITEM_ID=TASK_INVENTORY_STEP.JOB_ITEM_ID
                WHERE TASK_INVENTORY_STEP.STEP_TYPE = 'TYPE A'
                  AND ITEMS.ITEM_ID = JOB_INVENTORY.ITEM_ID)

多くの場合、DISTINCT は、クエリが適切に記述されていないことを示している可能性があります (適切なクエリは重複を返すべきではないため)。

元の選択では 4 つのテーブルが使用されていないことにも注意してください。

于 2012-12-19T16:38:36.510 に答える
8

最初に注意すべきことは、 の使用がDistinctコードの匂い、つまりアンチパターンを示していることです。これは通常、結合が欠落しているか、重複データを生成している余分な結合があることを意味します。group by上記のクエリを見ると、(クエリを表示せずに)より高速である理由は、の場所group byが返されるレコードの数を減らすためだと推測しています。一方distinct、結果セットを吹き飛ばし、行ごとの比較を行っています。

アプローチの更新

すみません、もっとはっきり言うべきでした。ユーザーがシステムで特定のタスクを実行するとレコードが生成されるため、スケジュールはありません。ユーザーは、1 日に 1 つのレコードを生成することも、1 時間に数百のレコードを生成することもできます。重要なことは、ユーザーが検索を実行するたびに最新のレコードが返される必要があることです。これにより、マテリアライズド ビューがここで機能するかどうかが疑わしくなります。特にクエリの実行に時間がかかる場合はそうです。

これが具体化されたビューを使用する正確な理由だと思います。したがって、プロセスはこのように機能します。システムで任意のタスクを実行した後、ユーザーは「新しい」データのみを気にすることがわかっているため、実体化されたビューを構築する部分として長時間実行されるクエリを使用します。したがって、実行したいのは、このベース マテリアライズド ビューに対してクエリを実行することです。これは、バックエンドで常に更新できます。関連する永続化戦略は、マテリアライズド ビューを詰まらせてはなりません (一度に数百のレコードを永続化しても、何もクラッシュしません) )。これにより、Oracle が読み取りロックを取得できるようになります (データを読み取るソースの数は気にしません。ライターのみを気にします)。最悪の場合、ユーザーはマイクロ秒の間「古い」データを保持します。

これを行う方法のコード例:

create materialized view dept_mv FOR UPDATE as select * from dept; 

ここで重要なのは、更新を呼び出さない限り、永続化されたデータが失われないことです。マテリアライズド ビューをいつ「ベースライン」にするかは、あなた次第です (おそらく真夜中?)。

于 2012-12-19T16:40:24.860 に答える
-3

GROUP BY を使用して各グループに集計演算子を適用し、重複を削除するだけであれば DISTINCT を使用する必要があります。

性能は同じだと思います。

あなたの場合、GROUP BYを使用する必要があると思います。

于 2012-12-19T16:34:02.967 に答える