2

我正在使用这样的查询执行更新:

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都有NULLm_id
  • m大约是 ~500,000 行
  • m_idon table是h指向表的索引外键idm
  • idon tablem是主键
  • 有索引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

对于EXPLAIN PLAN这个提到的 ROWID 查找和索引使用,它也持续了几个小时没有完成。我也一直认为这样的查询会导致对外部查询谓词的每个结果都执行子查询,所以无论如何我预计这种重写的性能会很差。

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

编辑:

我也从像这样的简单计数查询中获得了糟糕的表现:

SELECT COUNT(*)
FROM   h
WHERE  m_id IS NULL

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

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

4

5 回答 5

3

几点:

  • Oracle 不索引NULL(它将索引NULL作为全局非空元组的一部分的 a,但仅此而已)。

  • Oracle 选择 a 是HASH JOIN因为hm. 这可能是性能方面的最佳选择。

  • 第二个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 一起分析您的具体案例,以便他/她可以从已删除的行中回收空间并降低高水位线
于 2012-09-20T07:48:17.330 回答
2

据我记得, aWHERE m_id IS NULL执行全表扫描,因为 NULL 值不能被索引。

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

您可以尝试将虚拟列集添加为非空值 if m_id IS NULL,并索引此列,并在 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)

也可能有帮助。

于 2012-09-20T05:37:59.210 回答
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;
于 2012-09-20T08:48:29.443 回答
0

尝试未记录的提示 /*+ BYPASS_UJVC */。如果可行,请在 m.foo 上添加一个 UNIQUE/PK 约束。

于 2012-09-20T06:57:16.467 回答
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)...

于 2012-09-20T05:27:29.150 回答