我一直在努力改善现有Oracle数据库驱动的应用程序的查询时间,该应用程序运行有点迟缓。应用程序执行几个大型查询,例如下面的查询,这可能需要一个多小时才能运行。在下面的查询中用DISTINCT
子句替换GROUP BY
会将执行时间从100分缩短到10秒。我的理解是SELECT DISTINCT
和GROUP BY
的操作方式几乎相同。为什么执行时间之间存在如此巨大的差距?查询在后端执行的方式有何不同?是否有SELECT DISTINCT
运行得更快的情况?
注意:在以下查询中,WHERE TASK_INVENTORY_STEP.STEP_TYPE = 'TYPE A'
仅代表可以过滤结果的多种方式之一。提供此示例是为了显示加入SELECT
中未包含列的所有表的原因,并将导致所有可用数据的十分之一
使用DISTINCT
的SQL:
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
使用GROUP BY
的SQL:
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
以下是使用DISTINCT
的查询的Oracle查询计划:
以下是使用GROUP BY
的查询的Oracle查询计划:
答案 0 :(得分:18)
性能差异可能是由于在SELECT
子句中执行了子查询。我猜它是在之前每行重新执行此查询。对于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
答案 1 :(得分:16)
我很确定GROUP BY
和DISTINCT
的执行计划大致相同。
由于我们必须猜测(因为我们没有解释计划),因此IMO认为内联子查询被执行 AFTER GROUP BY
但 BEFORE < / strong> DISTINCT
。
因此,如果您的查询返回1M行并聚合到1k行:
GROUP BY
查询将运行子查询1000次,DISTINCT
查询将运行子查询1000000次。tkprof解释计划将有助于证明这一假设。
虽然我们正在讨论这个问题,但我认为重要的是要注意查询的编写方式会误导读者和优化器:你显然希望找到item / item_transactions中具有{{{}的所有行。 1}}的值为“TYPE A”。
IMO您的查询会有更好的计划,如果这样写的话会更容易阅读:
TASK_INVENTORY_STEP.STEP_TYPE
在许多情况下,DISTINCT可能表示查询编写不正确(因为好的查询不应返回重复项)。
另请注意,原始选择中不使用4个表。
答案 2 :(得分:8)
应该注意的第一件事是使用Distinct
表示代码气味,即反模式。它通常意味着缺少连接或生成重复数据的额外连接。看看上面的查询,我猜测group by
更快(没有看到查询)的原因是group by
的位置减少了最终返回的记录数。而distinct
正在吹灭结果集并逐行比较。
更新接近
对不起,我应该更清楚了。记录生成时 用户在系统中执行某些任务,因此没有计划。一个 用户可以在一天内生成一条记录,或者每小时生成数百条记录。该 重要的是每次用户运行搜索时都是最新的 记录必须归还,这让我怀疑是否实现了 view会在这里工作,特别是如果填充它的查询需要 很长时间。
我相信这是使用物化视图的确切原因。所以这个过程就是这样的。您将长时间运行的查询作为构建物化视图的部分,因为我们知道用户在执行系统中的任意任务后只关心“新”数据。所以你要做的是查询这个基本的物化视图,它可以在后端不断刷新,所涉及的持久性策略不应该扼杀物化视图(一次持有几百条记录不会破坏任何东西)。这将允许Oracle获取读锁(注意我们不关心有多少源读取我们的数据,我们只关心编写器)。在最坏的情况下,用户将拥有微秒的“陈旧”数据,因此,除非这是华尔街的金融交易系统或核反应堆系统,否则这些“昙花一现”应该被即使是最鹰眼的用户忽视。
如何执行此操作的代码示例:
create materialized view dept_mv FOR UPDATE as select * from dept;
现在关键是,只要您不调用刷新,就不会丢失任何持久数据。您可以自行决定何时再次对物化视图进行“基线”(也许是午夜?)
答案 3 :(得分:-3)
如果您只需删除重复项,则应使用GROUP BY将聚合运算符应用于每个组和DISTINCT。
我认为表现是一样的。
在你的情况下,我认为你应该使用GROUP BY。