SQL性能:SELECT DISTINCT与GROUP BY

woe*_*ler 23 sql oracle performance group-by distinct

我一直在努力改善现有Oracle数据库驱动的应用程序的查询时间,该应用程序运行有点迟缓.应用程序执行几个大型查询,例如下面的查询,这可能需要一个多小时才能运行.在下面的查询中替换DISTINCTwith a GROUP BY子句会将执行时间从100分缩短到10秒.我的理解是,SELECT DISTINCTGROUP 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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

以下是使用以下内容的查询的Oracle查询计划DISTINCT:

使用DISTINCT进行查询的Oracle查询计划

以下是使用以下内容的查询的Oracle查询计划GROUP BY:

使用GROUP BY进行查询的Oracle查询计划

Gor*_*off 18

性能差异可能是由于SELECT子句中子查询的执行.我猜它是在distinct 之前的每一行重新执行这个查询.对于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
Run Code Online (Sandbox Code Playgroud)


Vin*_*rat 16

我很确定GROUP BY并且DISTINCT执行计划大致相同.

因为我们猜测(因为我们没有解释计划)这里的区别是IMO的内嵌子查询被执行GROUP BY,但之前DISTINCT.

因此,如果您的查询返回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)
Run Code Online (Sandbox Code Playgroud)

在许多情况下,DISTINCT可能是查询未正确写入的标志(因为好的查询不应返回重复项).

另请注意,原始选择中不使用4个表.


Woo*_*Moo 8

应该注意的第一件事是使用Distinct表示代码气味,也就是反模式.它通常意味着缺少连接或生成重复数据的额外连接.看看上面的查询,我猜测原因group by更快(没有看到查询),是因为位置group by减少了最终返回的记录数.然而distinct,吹出结果集并逐行比较.

更新接近

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

我相信这是使用物化视图的确切原因.所以这个过程就是这样的.您将长时间运行的查询作为构建物化视图的部分,因为我们知道用户在执行系统中的任意任务后只关心"新"数据.所以你要做的是查询这个基本的物化视图,它可以在后端不断刷新,所涉及的持久性策略不应该扼杀物化视图(一次持续几百条记录不会破坏任何东西).这将允许Oracle获取读锁(注意我们不关心有多少源读取我们的数据,我们只关心编写器).在最坏的情况下,用户将拥有微秒的"陈旧"数据,因此,除非这是华尔街的金融交易系统或核反应堆系统,否则这些"昙花一现"应该被即使是最鹰眼的用户忽视.

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

create materialized view dept_mv FOR UPDATE as select * from dept; 
Run Code Online (Sandbox Code Playgroud)

现在关键是,只要你不调用刷新,你就不会丢失任何持久数据.您可以决定何时再次对物化视图进行"基线"(也许是午夜?)

  • +1代码气味.通过PK连接表的查询不应返回重复项; 如果他们这样做可能有些不对劲:) (3认同)