优化在Oracle上运行速度慢的SELECT查询,该查询在SQL Server上快速运行

时间:2008-09-23 11:50:56

标签: sql sql-server oracle optimization

我正在尝试在Oracle中运行以下SQL语句,运行需要很长时间:

SELECT orderID FROM tasks WHERE orderID NOT IN 
(SELECT DISTINCT orderID FROM tasks WHERE
 engineer1 IS NOT NULL AND engineer2 IS NOT NULL)

如果我只运行IN子句中的子部分,它在Oracle中运行得非常快,即

SELECT DISTINCT orderID FROM tasks WHERE
engineer1 IS NOT NULL AND engineer2 IS NOT NULL

为什么整个声明在Oracle中需要这么长时间?在SQL Server中,整个语句运行得很快。

或者我应该使用更简单/不同/更好的SQL语句吗?

有关此问题的更多详细信息:

  • 每个订单都由许多任务组成
  • 每个订单将被分配(其任务中的一个或多个将设置为engineer1和engineer2)或订单可以未分配(其所有任务都具有工程师字段的空值)
  • 我正在尝试查找所有未分配的订单ID。

如果它有任何区别,表中有大约120k行,每个订单有3个任务,所以~40k不同的订单。

回答答案:

  • 我更喜欢在SQL Server和Oracle中都有效的SQL语句。
  • 任务只有orderID和taskID的索引。
  • 我尝试了该语句的NOT EXISTS版本,但在取消之前它运行了超过3分钟。也许需要一个JOIN版本的声明?
  • 还有一个“订单”表和orderID列。但我试图通过不在原始SQL语句中包含它来简化问题。

我想在原始的SQL语句中,每次为SQL语句的第一部分中的每一行运行子查询 - 即使它是静态的,只需要运行一次吗?

执行

ANALYZE TABLE tasks COMPUTE STATISTICS;

使我原来的SQL语句执行得更快。

虽然我仍然很好奇为什么我必须这样做,如果/何时我需要再次运行它?

  

统计数据给出了Oracle的信息   基于成本的优化器信息   它需要确定效率   不同的执行计划:for   例如,表中的行数,   行的平均宽度,最高和   每列最低值,数量   每列不同的值,聚类   指数因子等。

     

在小型数据库中,您可以进行设置   每晚收集统计数据的工作   并且不管它。事实上,这是   默认值低于10g。对于更大   你通常需要的实现   衡量执行的稳定性   计划反对数据的方式   变化,这是一个棘手的平衡。

     

Oracle也有一个名为的功能   用于的“动态采样”   样本表确定相关   执行时的统计数据。它的   更常用于数据   仓库的开销   取样比它重要   a的潜在性能提升   长期运行的查询。

18 个答案:

答案 0 :(得分:9)

如果您分析所涉及的表,这种问题通常会消失(因此Oracle可以更好地了解数据的分布)

ANALYZE TABLE tasks COMPUTE STATISTICS;

答案 1 :(得分:3)

Oracle中已知“IN” - 子句非常慢。实际上,Oracle中的内部查询优化器无法处理带有“IN”的语句。尝试使用“EXISTS”:

SELECT orderID FROM tasks WHERE orderID NOT EXISTS 
    (SELECT DISTINCT orderID FROM tasks WHERE
         engineer1 IS NOT NULL AND engineer2 IS NOT NULL)`print("code sample");`

警告:请检查查询是否构建了相同的数据结果。

伊迪丝说:ooops,查询不是很好,但总体思路是正确的。 Oracle必须为第二个(内部)查询完成全表扫描,构建结果,然后将它们与第一个(外部)查询进行比较,这就是它减速的原因。尝试

SELECT orderID AS oid FROM tasks WHERE NOT EXISTS 
    (SELECT DISTINCT orderID AS oid2 FROM tasks WHERE
         engineer1 IS NOT NULL AND engineer2 IS NOT NULL and oid=oid2)

或类似的东西; - )

答案 2 :(得分:3)

我会尝试使用连接

SELECT 
    t.orderID 
FROM 
    tasks  t
    LEFT JOIN tasks t1
        ON t.orderID =  t1.orderID
        AND t1.engineer1 IS NOT NULL 
        AND t1.engineer2 IS NOT NULL
WHERE
    t1.orderID IS NULL 

如果将原始查询指定为:

,也可能更容易理解
SELECT orderID FROM orders WHERE orderID NOT IN 
(SELECT DISTINCT orderID FROM tasks WHERE
 engineer1 IS NOT NULL AND engineer2 IS NOT NULL)

(假设您的订单表中列出了所有订单)

然后可以使用连接重写为:

SELECT 
    o.orderID 
FROM 
    orders o
    LEFT JOIN tasks t
        ON o.orderID =  t.orderID
        AND t.engineer1 IS NOT NULL 
        AND t.engineer2 IS NOT NULL
WHERE
    t.orderID IS NULL 

答案 3 :(得分:2)

有些问题:

  • 任务中有多少行?
  • 在其上定义了哪些索引?
  • 最近是否分析了表格?

编写同一查询的另一种方法是:

select orderid from tasks
minus
select orderid from tasks
where engineer1 IS NOT NULL AND engineer2 IS NOT NULL

但是,我宁愿期望查询涉及“订单”表:

select orderid from ORDERS
minus
select orderid from tasks
where engineer1 IS NOT NULL AND engineer2 IS NOT NULL

select orderid from ORDERS
where orderid not in
( select orderid from tasks
  where engineer1 IS NOT NULL AND engineer2 IS NOT NULL
)

select orderid from ORDERS
where not exists
( select null from tasks
  where tasks.orderid = orders.orderid
  and   engineer1 IS NOT NULL OR engineer2 IS NOT NULL
)

答案 4 :(得分:2)

我同意TZQTZIO,我不接受你的询问。

如果我们假设查询确实有意义,那么您可能想尝试使用EXISTS作为一些建议并避免使用IN。 IN并不总是坏的,并且有可能表明它实际上比EXISTS表现更好。

问题标题不是很有帮助。我可以在一个Oracle数据库中设置此查询并使其运行缓慢并使其在另一个Oracle数据库中快速运行。有许多因素决定数据库如何解析查询,对象统计信息,SYS模式统计信息和参数以及服务器性能。 Sqlserver与Oracle不是问题所在。

对于那些对查询调优和性能感兴趣并希望了解更多搜索谷歌条款的人来说,有“橡皮桌oracle”和“oracle jonathan lewis”。

答案 5 :(得分:1)

“虽然我仍然很好奇为什么我必须这样做,如果/我什么时候需要再次运行呢?”

统计信息为Oracle提供了确定不同执行计划效率所需的基于成本的优化器信息:例如,表中的行数,行的平均宽度,每列的最高值和最低值,不同的数量每列的值,索引的聚类因子等。

在一个小型数据库中,您可以设置一个工作,每晚收集统计数据并不管它。实际上,这是10g以下的默认值。对于较大的实现,您通常必须权衡执行计划的稳定性与数据更改的方式,这是一个棘手的平衡。

Oracle还有一个名为“动态采样”的功能,用于对表进行采样,以确定执行时的相关统计信息。它经常用于数据仓库,其中采样的开销远远超过长期运行查询的潜在性能提升。

答案 6 :(得分:1)

我认为有几个人拥有正确的SQL,但缺少内部和外部查询之间的连接 试试这个:

SELECT t1.orderID 
FROM   tasks t1
WHERE  NOT EXISTS
       (SELECT 1 
        FROM   tasks t2 
        WHERE  t2.orderID   = t1.orderID
        AND    t2.engineer1 IS NOT NULL 
        AND    t2.engineer2 IS NOT NULL)

答案 7 :(得分:0)

您的查询不是

SELECT orderID FROM tasks
WHERE engineer1 IS NOT NULL OR engineer2 IS NOT NULL

答案 8 :(得分:0)

我同意ΤΖΩΤΖΙΟΥ和wearejimbo你的查询应该是......

SELECT DISTINCT orderID FROM Tasks 
WHERE Engineer1 IS NULL OR Engineer2 IS NULL;

我不了解SQL Server,但此查询无法利用任何索引,因为空行不在索引中。对此的解决方案是以允许创建仅包含空值行的基于函数的索引的方式重新编写查询。这可以使用NVL2完成,但可能无法移植到SQL Server。

我认为最好的答案不是符合您标准的答案,而是针对最适合该平台的每个平台编写不同的声明。

答案 9 :(得分:0)

新观点。

<强> IFF

  • COUNT()函数不计算NULL值

  • 您希望任务的所有任务的orderID将engineer1或engineer2设置为值

然后这应该做你想做的事情:

SELECT orderID
FROM tasks
GROUP BY orderID
HAVING COUNT(engineer1) = 0 AND COUNT(engineer2) = 0

请测试一下。

答案 10 :(得分:0)

Oracle优化器在处理MINUS语句方面做得很好。如果您使用MINUS重新编写查询,则可能会很快运行:

SELECT orderID FROM tasks
MINUS
SELECT DISTINCT orderID FROM tasks WHERE
 engineer1 IS NOT NULL AND engineer2 IS NOT NULL

答案 11 :(得分:0)

表中有多少行符合条件“engineer1 IS NOT NULL且engineer2 IS NOT NULL”?

这告诉你(粗略地)是否值得尝试使用索引来检索关联的orderid。

在Oracle中编写可以很好地处理未编入索引的案例的另一种方法是:

select distinct orderid
from
(
select orderid,
       max(case when engineer1 is null and engineer2 is null then 0 else 1)
          over (partition by orderid)
          as max_null_finder
from   tasks
)
where max_null_finder = 0

答案 12 :(得分:0)

如果您决定创建一个ORDERS表,我会向其添加一个ALLOCATED标志,并创建一个位图索引。这种方法还会强制您修改业务逻辑以保持标志更新,但查询速度会很快。这取决于应用程序查询的重要程度。

关于答案,在这种情况下越简单越好。忘记子查询,连接,不同和分组,根本不需要它们!

答案 13 :(得分:0)

另一种选择是使用MINUS(在MSSQL上除外)

SELECT orderID FROM tasks
MINUS
SELECT DISTINCT orderID FROM tasks WHERE engineer1 IS NOT NULL 
AND engineer2 IS NOT NULL

答案 14 :(得分:0)

怎么样:

SELECT DISTINCT orderID FROM tasks t1 WHERE NOT EXISTS (SELECT * FROM tasks t2 WHERE t2.orderID=t1.orderID AND (engineer1 IS NOT NULL OR engineer2 IS NOT NULL));

我不是优化的大师,但也许你也忽略了Oracle数据库中的一些索引。

答案 15 :(得分:-1)

如果您没有Engineer1和Engineer2列的索引,那么您总是会在SQL Server中生成表扫描以及Oracle中可能存在的等价物。

如果您只需要具有未分配任务的Orders,则以下内容在两个平台上都可以正常工作,但您还应考虑将索引添加到Tasks表中以提高查询性能。

SELECT DISTINCT orderID 
FROM tasks 
WHERE (engineer1 IS NULL OR engineer2 IS NULL)

答案 16 :(得分:-1)

以下是我认为可以提供您想要的替代方法:

SELECT orderID
 FROM tasks
 GROUP BY orderID
 HAVING COUNT(engineer1) = 0 OR COUNT(engineer2) = 0

我不确定你是否想在HAVING子句中使用“AND”或“OR”。听起来根据业务逻辑,这两个字段要么都要填充,要么都是NULL;如果这是保证,那么你可以将条件减少到只检查engineer1。

我认为,您的原始查询会为每个orderID提供多行,而我的只会提供一行。我猜这是可以的,因为你只是获取orderID。

答案 17 :(得分:-2)

子查询对Oracle来说“不好”。通常使用连接更好。

这是一篇关于如何使用join重写子查询的文章: http://www.dba-oracle.com/sql/t_rewrite_subqueries_performance.htm