早上好,
在一个查询中我昨天写了两个体面大小的结果集(每个结果大约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分钟与<1)。由于更多的比较,我希望OR运行得更慢,但是在这个特定数据集中a.class = b.class = null的情况相对较少。
什么会导致表现时间大幅增加? Oracle SQL是否不像许多其他语言那样短路布尔比较?有没有办法在第二个查询中抢救第一个查询(用于一般SQL而不仅仅是Oracle)?
答案 0 :(得分: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。
在幕后它可能看起来像:
当我将SELECT语句更改为SELECT * FROM a JOIN b ON NVL(a.class, 'N/A') = NVL(b.class, 'N/A')
时,EXPLAIN指示使用了HASH JOIN。散列连接实质上生成较小表的每个连接键的散列,然后扫描大表,在匹配的每一行的较小表中查找散列。在这种情况下,由于它是一个简单的Equijoin,优化器可以毫无问题地对驱动表的每一行进行散列。
在幕后它可能看起来像:
如果您对查询运行EXPLAIN PLAN,您可能会发现类似的结果。
即使最终结果是相同的,因为您没有使用&#34; OR&#34;加入第一个查询中的表,优化程序无法使用使用更好的连接方法。如果驱动表很大,或者如果要对大型辅助表强制进行全表扫描,则嵌套的LOOPS可能会非常慢。
您可以使用ANSI COALESCE
函数模拟其他数据库系统中的NVL oracle函数。这里真正的问题是你试图加入一个NULL值,你真的应该有一个&#34; NO CLASS&#34;或其他一些识别&#34; null&#34;在null的意义上的类=没有,而不是null = unknown。
在评论中回答您问题的附录:
对于空查询,SQL引擎将执行以下操作:
对于&#39; N / A&#39;查询,SQL引擎将执行以下操作:
答案 1 :(得分:1)
在第一种情况下,因为每个null都不同,所以数据库不使用优化(对于来自a
的每一行,检查表b
中的每一行)。
在第二种情况下,数据库首先将所有空值更改为“N / A”,然后仅使用优化来比较a.class
和b.class
比较Oracle中的空值非常耗时。 Null是未定义的值 - 一个null与其他null不同。 比较两个几乎相同的查询的结果:
select 1 from dual where null is null
select 1 from dual where null = null
只有带有特殊is null
子句的第一个查询才会返回正确答案。因此,无法索引空值。
答案 2 :(得分:0)
试试这个:
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
应该加快速度
答案 3 :(得分:-1)
解释很简单: 首先必须在连接操作中使用嵌套循环,当您使用OR操作时,始终会发生。 第二个必须使用散列连接操作,这比前一个快。
答案 4 :(得分:-2)
为什么不让它变得容易一点。 像
SELECT * 从a,b 哪里 的A.class(+)= b.class(+)
我认为它更具可读性。