糟糕的Oracle更新性能

时间:2012-09-20 05:15:36

标签: sql performance oracle sql-update

我正在使用如下查询执行更新:

UPDATE (SELECT     h.m_id,
                   m.id
        FROM       h
        INNER JOIN m
        ON         h.foo = m.foo)
SET    m_id = id
WHERE  m_id IS NULL

一些信息:

  • h约为500万行
  • h中的所有行都包含NULL
  • m_id个值
  • m约为50万行
  • m_id上的
  • h是指向表id上的m的索引外键
  • id上的
  • m是主键
  • m.fooh.foo
  • 上有索引

此查询的EXPLAIN PLAN表示散列连接和全表扫描,但我不是DBA,所以我无法真正解释它。

查询本身运行了几个小时但没有完成。我原本预计它会在不超过几分钟内完成。我还尝试了以下查询重写:

UPDATE h
SET    m_id = (SELECT id
               FROM   m
               WHERE  m.foo = h.foo)
WHERE  m_id IS NULL

这个提到的ROWID查找和索引使用的EXPLAIN PLAN,但是它也持续了几个小时没有完成。我也一直认为像这样的查询会导致子查询被外部查询的谓词的每个结果执行,所以我认为这个重写的表现非常差。

我的方法有什么问题,还是我的问题与索引,表空间或其他一些与查询无关的因素有关?

修改

我也有这样的简单计数查询的糟糕表现:

SELECT COUNT(*)
FROM   h
WHERE  m_id IS NULL

这些查询需要大约30秒到有时大约30分钟(!)。

我注意到没有锁,但这些表的表空间现在只占99.5%的使用率(仅约6MB免费)。我被告知,只要索引被使用,这无关紧要,但我不知道......

5 个答案:

答案 0 :(得分:3)

有些观点:

  • Oracle执行索引NULL值(它将索引作为全局非null元组的一部分的NULL,但这就是它)。

  • 由于HASH JOINh的大小,Oracle需要m。这可能是表现最好的选择。

  • 第二个UPDATE 可能让Oracle使用索引,但是Oracle通常很聪明地合并子查询。无论如何,这将是一个更糟糕的计划。

  • 您是否有最新的,合理的架构统计信息? Oracle 确实需要体面的统计数据。

  • 在执行计划中,这是HASH JOIN中的第一个表?为获得最佳性能,它应该是较小的表(在您的情况下为m)。如果您没有良好的基数统计数据,Oracle将会搞砸。您可以强制Oracle使用cardinality提示假定固定的基数,这可能有助于Oracle获得更好的计划。

例如,在您的第一个查询中:

UPDATE (SELECT /*+ cardinality(h 5000000) cardinality(m 500000) */
               h.m_id, m.id 
        FROM h 
        INNER JOIN m 
        ON h.foo = m.foo) 
SET m_id = id 
WHERE m_id IS NULL
  • 在Oracle中,FULL SCAN不仅读取表中的每条记录,它基本上读取分配到最大值的所有存储(Oracle文档中的高水位标记 )。因此,如果您有大量已删除的行,那么您的表可能需要进行一些清理。我看到空表上的SELECT COUNT(*)消耗了30多秒,因为有问题的表有2.5亿个已删除的行。如果是这种情况,我建议用DBA分析您的具体情况,这样他/她就可以从已删除的行中回收空间并降低高水位线

答案 1 :(得分:2)

据我记忆,WHERE m_id IS NULL执行全表扫描,因为无法对NULL值编制索引。

全表扫描意味着引擎需要读取表中的每条记录以评估WHERE条件,并且不能使用索引。

如果m_id IS NULL,您可以尝试将virtual column集添加到非空值,并将此列编入索引,并在WHERE条件中使用此列。

然后你也可以将WHERE条件从UPDATE语句移动到子选择,这可能会使语句更快。

由于JOIN很昂贵,请将INNER JOIN m ON h.foo = m.foo重写为

WHERE h.foo IN (SELECT m.foo FROM m WHERE m.foo IS NOT NULL)

也可以提供帮助。

答案 2 :(得分:1)

对于大型表,MERGE通常比UPDATE快得多。试试这个(未经测试):

MERGE INTO h USING
(SELECT     h.h_id,
            m.id as new_m_id
        FROM       h
        INNER JOIN m
        ON         h.foo = m.foo
 WHERE h.m_id IS NULL       
) new_data
ON (h.h_id = new_data.h_id)
WHEN MATCHED THEN
  UPDATE SET h.m_id = new_data.new_m_id;

答案 3 :(得分:0)

我会在迭代中更新表格,例如,根据where h.date_created > sysdate-30添加条件,在完成后我会运行相同的查询并将条件更改为:where h.date_created between sysdate-30 and sysdate-60等等。如果你不喜欢没有像date_created这样的列可能还有另一个可以过滤的列吗?例如:WHERE m.foo = h.foo AND m.foo between 1 and 10

只有plan的结果可以解释为什么这次更新的成本很高,但是一个有根据的猜测是两个表都非常大,并且有很多NULL个值以及很多匹配(m.foo = h.foo)......

答案 4 :(得分:0)

尝试无证件提示/ * + BYPASS_UJVC * /。如果可行,请在m.foo上添加UNIQUE / PK约束。

相关问题