26

我一直在尝试改善现有 Oracle 数据库驱动应用程序的查询时间,该应用程序运行缓慢。该应用程序执行几个大型查询,例如下面的查询,可能需要一个多小时才能运行。DISTINCT在下面的查询中用GROUP BY子句替换将执行时间从 100 分钟缩短到 10 秒。我的理解是,SELECT DISTINCT并且GROUP BY以几乎相同的方式操作。为什么执行时间之间存在如此巨大的差异?查询在后端执行的方式有什么区别?有没有SELECT DISTINCT跑得更快的情况?

注意:在以下查询中,WHERE TASK_INVENTORY_STEP.STEP_TYPE = 'TYPE A'仅表示可以过滤结果的多种方式之一。提供此示例是为了说明连接所有未包含列的表的原因,这SELECT将导致大约十分之一的可用数据

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之后执行一次。

尝试用连接替换它,而不是:

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有大致相同的执行计划。

由于我们必须猜测(因为我们没有解释计划) ,这里的区别是 IMO 内联子查询.GROUP BYDISTINCT

因此,如果您的查询返回 1M 行并聚合为 1k 行:

  • GROUP BY查询将运行子查询 1000 次,
  • DISTINCT查询将运行子查询 1000000 次。

tkprof 解释计划将有助于证明这一假设。


在我们讨论这个问题时,我认为重要的是要注意查询的编写方式对读者和优化器都有误导性:您显然希望从 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正在破坏结果集并进行逐行比较。

更新接近

对不起,我应该更清楚。当用户在系统中执行某些任务时会生成记录,因此没有时间表。用户可以在一天内或每小时生成数百条记录。重要的是,每次用户运行搜索时,都必须返回最新的记录,这让我怀疑物化视图是否可以在这里工作,特别是如果填充它的查询需要很长时间才能运行。

I do believe this is the exact reason to use a materialized view. So the process would work this way. You take the long running query as the piece that builds out your materialized view, since we know the user only cares about "new" data after they perform some arbitrary task in the system. So what you want to do is query against this base materialized view, which can be refreshed constantly on the back-end, the persistence strategy involved should not choke out the materialized view (persisting a few hundred records at a time won't crush anything). What this will allow is Oracle to grab a read lock (note we don't care how many sources read our data, we only care about writers). In the worst case a user will have "stale" data for microseconds, so unless this is a financial trading system on Wall Street or a system for a nuclear reactor, these "blips" should go unnoticed by even the most eagle eyed users.

如何执行此操作的代码示例:

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 回答