2

早上好,

在我昨天在两个体面大小的结果集(每个<50k结果)之间编写的查询中,我的部分 JOIN 是检查数据是否匹配或为空的子句(下面的简化版本):

SELECT a JOIN b ON a.class = b.class OR (a.class is null AND b.class is null)

然而,我注意到一个以使用 OR 语句为中心的严重性能问题。我使用以下方法解决了这个问题:

SELECT a JOIN b ON NVL(a.class, 'N/A') = NVL(b.class, 'N/A')

第一个查询的运行时间长得令人无法接受,而第二个查询要快几个数量级(>45 分钟 vs. <1)。由于更多的比较,我希望 OR 运行得更慢,但是在这个特定的数据集中,a.class = b.class = null 的情况相对较少。

什么会导致表演时间如此显着增加?Oracle SQL 是否不像许多其他语言那样短路布尔比较?有没有办法挽救第二个查询的第一个查询(用于一般 SQL 而不仅仅是 Oracle)?

4

5 回答 5

4

您将返回一个带有任何具有空类的记录的叉积。这对你的结果好吗?

我在 11gR2 中创建了两个示例查询:

WITH a as 
(select NULL as class, 5 as columna from dual
 UNION
 select NULL as class, 7 as columna from dual
 UNION
 select NULL as class, 9 as columna from dual
 UNION
 select 'X' as class, 3 as columna from dual
 UNION
 select 'Y' as class, 2 as columna from dual),
 b as 
 (select NULL as class, 2 as columnb from dual
 UNION
 select NULL as class, 15 as columnb from dual
 UNION
 select NULL as class, 5 as columnb from dual
 UNION
 select 'X' as class, 7 as columnb from dual
 UNION
 select 'Y' as class, 9 as columnb from dual)
    SELECT * from a JOIN b ON (a.class = b.class 
                              OR (a.class is null AND b.class is null))

当我在这个查询上运行 EXPLAIN PLAN 时,它表明表(在我的例子中是内联视图)是通过 NESTED LOOPS 连接的。NESTED LOOPS 连接通过扫描一个表的第一行,然后扫描另一个表的每一行以查找匹配项,然后扫描第一个表的第二行,在第二个表上查找匹配项等操作。因为您不是直接比较JOIN 的 OR 部分中的任何一个表,优化器都必须使用 NESTED LOOPS。

在幕后,它可能看起来像:

  • 获取表 A 的第 1 行。如果类为空,则将表 A 中的这一行包含在结果集中。
  • 虽然仍在表 A 的第 1 行,但在表 B 中搜索类为空的所有行。
  • 对表 A 第 1 行和表 B 中找到的所有行执行叉积
  • 在结果集中包含这些行
  • 获取表 A,第 2 行。如果类为空,则将表 A 中的这一行包含在结果集中。
  • .... ETC

当我将 SELECT 语句更改为 时SELECT * FROM a JOIN b ON NVL(a.class, 'N/A') = NVL(b.class, 'N/A'),EXPLAIN 表示使用了 HASH JOIN。散列连接本质上生成小表的每个连接键的散列,然后扫描大表,在小表中找到匹配的每一行的散列。在这种情况下,由于它是一个简单的 Equijoin,优化器可以毫无问题地散列驱动表的每一行。

在幕后,它可能看起来像:

  • 遍历表 A,将 NULL 类值转换为“N/A”
  • 散列表 A 的每一行。
  • 哈希表 A 现在位于临时空间或内存中。
  • 扫描表 B,将 NULL 类值转换为“N/A”,然后计算值的哈希值。哈希表中的查找哈希,如果存在,则将表 A 和 B 中的连接行包含在结果集中。
  • 继续扫描 B.

如果您对查询运行 EXPLAIN PLAN,您可能会发现类似的结果。

即使最终结果是相同的,由于您没有在第一个查询中使用“OR”连接表,因此优化器无法使用更好的连接方法。如果驱动表很大,或者如果您强制对大型辅助表进行全表扫描,则嵌套循环可能会非常慢。

您可以使用 ANSICOALESCE函数来模拟其他数据库系统中的 NVL oracle 函数。这里真正的问题是你试图加入一个 NULL 值,在那里你真的应该有一个“NO CLASS”或其他一些识别“null”类的方法,在 null = nothing 而不是 null = unknown 的意义上.

在评论中回答您的问题的附录:

对于空查询,SQL 引擎将执行以下操作:

  1. 从表 A 中读取第 1 行,类为空,转换为“N/A”。
  2. 表 B 有 3 行,其类为空,将每个空转换为“N/A”。
  3. 由于第一行与所有 3 行匹配,因此将 3 行添加到我们的结果集中,一个用于 A1B1、A1B2、A1B3。
  4. 从表 A 中读取第 2 行,类为空,转换为 'N/A'/
  5. 表 B 有 3 行,其类为空,将每个空转换为“N/A”。
  6. 由于第二行匹配所有 3 行,因此将 3 行添加到我们的结果集中,A2B1、A2B2、A2B3 各行。
  7. 从表 A 中读取第 3 行,类为空,转换为“N/A”/
  8. 表 B 有 3 行,其类为空,将每个空转换为“N/A”。
  9. 由于第三行与所有 3 行匹配,因此将 3 行添加到我们的结果集中,A3B1、A3B2、A3B3 各行。10.. 第 4 行和第 5 行不为空,因此不会在连接的这一部分中处理它们。

对于“N/A”查询,SQL 引擎将执行以下操作:

  1. 从表 A 中读取第 1 行,类为空,转换为“N/A”,散列该值。
  2. 从表 A 中读取第 2 行,类为空,转换为“N/A”,散列该值。
  3. 从表 A 中读取第 3 行,类为空,转换为“N/A”,散列该值。
  4. 从表 A 中读取第 4 行,类不为空,散列此值。
  5. 从表 A 中读取第 5 行,类不为空,散列此值。
  6. 哈希表 C 现在在内存中。
  7. 从表 B 中读取第 1 行,类为空,转换为“N/A”,对值进行哈希处理。
  8. 将哈希值与内存中的哈希表进行比较,为每个匹配添加一行到结果集中。找到 3 行,A1、A2 和 A3。结果添加了 A1B1、A2B1、A3B1。
  9. 从表 B 中读取第 2 行,类为空,转换为“N/A”,散列值。
  10. 将哈希值与内存中的哈希表进行比较,为每个匹配添加一行到结果集中。找到 3 行,A1、A2 和 A3。结果添加了 A1B2、A2B2、A3B2。
  11. 从表 B 中读取第 3 行,类为空,转换为“N/A”,对值进行哈希处理。
  12. 将哈希值与内存中的哈希表进行比较,为每个匹配添加一行到结果集中。找到 3 行,A1、A2 和 A3。结果添加了 A1B3、A2B3、A3B3。
于 2012-05-04T17:16:13.860 回答
1

在第一种情况下,因为每个 null 都不同,所以数据库不使用优化(对于a从表中检查每一行的每一行b)。

在第二种情况下,数据库首先将所有空值更改为“N/A”,然后仅比较a.classand b.class,使用优化

在 Oracle 中比较空值非常耗时。Null 是未定义的值 - 一个 null 与另一个 null 不同。比较两个几乎相同的查询的结果:

select 1 from dual where null is null

select 1 from dual where null = null

只有带有特殊is null子句的第一个查询返回正确答案。因此,空值不能被索引。

于 2012-05-04T14:51:32.867 回答
-1

试试这个:

SELECT a from Table1 a JOIN JTable1 b ON a.class = b.class
where a.class is null
union all
SELECT a from Table1 a JOIN JTable1 b ON a.class = b.class
where b.class is null

应该更快

于 2012-05-04T14:56:23.793 回答
-1

解释很简单:第一个必须在join操作中使用嵌套循环,在使用OR操作时总是会发生这种情况。第二个必须使用哈希连接操作,它比前一个更快。

于 2013-10-12T02:59:19.727 回答
-2

你为什么不让它变得更容易一些。像

SELECT * FROM a,b WHERE a.class(+)=b.class(+)

我认为它更具可读性。

于 2012-05-04T15:07:10.743 回答