达梦数据库子查询
一、 子查询理论理解
1.1. 子查询分类
子查询是指嵌入SQL语句中的sql语句,作为整体SQL语句的一部分,根据子查询出现的位置不同,可以是分为如下部分:
1.查询项中的子查
2.from项中的查询
3.查询条件中的子查询
根据子查询和上层SQL语句的关系以及返回的数据行数,将子查询分为:
- 相关子查询:相关子查询是指和父查询存在关联条件的子查询,也就是和父查询的查询条件或结果相关的子查询。
- 非相关子查询:非相关子查询是指和父查询不存在关联条件的子查询,也就是父查询无论是什么样的条件,对子查询完全不影响的。
其中,在from项中的子查询,不存在相关子查询的方式,即使在from子句中可以看到相关子查询,那也是嵌套在子查询中的子查询,且不会出现在from子句后面作为子查询。
1.2. 子查询执行方式
- 非相关子查询是独立于外部查询的子查询,子查询无论是在查询项、from项、where条件中,都只执行一次,执行完毕后将值传递给外部查询。
- 相关子查询的执行依赖于外部查询的数据,外部父查询执行一次,子查询就执行一次,如果父查询的结果集比较大,那么子查询的执行次数就非常多,这个SQL语句的执行效率相对就会特别差。
在达梦数据库中,已经对子查询做了优化,以集合方式替代迭代的查询,在执行计划中,以SPL即生成临时表,再与父查询进行关联,但是如果父查询结果集大,执行效率仍然会比较低。
1.3. 子查询平坦化
由以上的说明中,达梦数据库默认对相关子查询进行了优化,即将子查询展开为与父查询非相关的手段,可以更加精准的方式对子查询的代价进行估算,并且以SPL临时表这种集合的方式与父查询进行关联,减少子查询的执行次数,进而提高整体SQL语句的执行效率,这个就称为子查询平坦化操作。
二、 达梦子查询验证
2.1. 非相关子查询
非相关子查询与父查询没有相关的关联条件,只执行一次,和普通的表连接方式基本无差别,这里不进行过多描述。
2.2. 相关子查询
2.2.1. 相关子查询平坦化
本节分别验证在select子句中和where子句中出现的子查询与父查询的关联方式。
Select条件中的子查询:
select t1.c1,(select c2 from t2 where t2.c1=t1.c1),t1.c2 from t1 where t1.c1>100;
查看其执行计划:
可以看到,对于select条件中的相关子查询,是先对t1表进行扫描,再与t1和t2两表hash join的结果进行合并得出结果。
Where条件中的子查询:
select * from t1 where t1.c1<100 and (select count(c1) from t2 where t2.c1=t1.c1) >0;
查看其执行计划:
可以看到,对于where条件中的执行计划,达梦优化器使用heap table这种方式对数据进行随机存取,加快数据处理速度,但是从执行计划的过程来看,还是对t2执行了两次索引扫描,对t1进行了索引扫描、二次回表并且也生成heap table。
从以上两个带有子查询的语句中,可以看到,在达梦数据库中,无论是select或者where条件中的相关子查询,都进行了平坦化处理,即,将子查询的结果集和父查询合并以两个表连接的方式替代迭代方式,在父查询的结果集较大的情况下,效率会提升。但是从以上的执行计划中可以看到,在达梦默认的进行子查询平坦化后,执行计划也相应的较为复杂,存在重复进行表扫描或者索引扫描的情况。
2.2.2. 行级子查询平坦化
达梦提供了ENABLE_RQ_TO_NONREF_SPL参数,以控制是否使用SPL的方式对相关子查询进行平坦化,可取值0、1、2:
ENABLE_RQ_TO_NONREF_SPL=0时,不进行特别处理;
ENABLE_RQ_TO_NONREF_SPL=1时,对查询项中出现的相关子查询表达式进行优化处理;
ENABLE_RQ_TO_NONREF_SPL=2时,对查询项和WHERE表达式中出现的相关子查询表达式进行优化处理。
ENABLE_RQ_TO_NONREF_SPL参数以行级处理方式实现相关子相关的平坦化,与普通的子查询平坦化机制不同,它不需要生成SPL的临时结果集,这种的工作方式是将相关列转为变量var,简化了执行计划。
分别在select和where条件中使用此参数,并取不同的值来对比此参数的作用。
Select语句中的0和1对比:
select /*+ ENABLE_RQ_TO_NONREF_SPL(0) */t1.c1,(select c2 from t2 where t2.c1=t1.c1),t1.c2 from t1 where t1.c1>100;
可以看出和不加任何参数的执行计划一致,此时更换为1,查看执行计划:
select /*+ ENABLE_RQ_TO_NONREF_SPL(1) */t1.c1,(select c2 from t2 where t2.c1=t1.c1),t1.c2 from t1 where t1.c1>100;
相较于之前的执行计划,在子查询的执行计划中,减少了两次全表扫描以及hash join的表连接,转而将相关的列以var的形式进行转换,相应的代价也有明显的减小。
Where条件中的0和2对比:
select/*+ ENABLE_RQ_TO_NONREF_SPL(0) */ * from t1 where t1.c1<10000 and (select count(c1) from t2 where t2.c1=t1.c1) >0;
和不加hint时的执行计划一致,此时更换为2,查看执行计划:
select/*+ ENABLE_RQ_TO_NONREF_SPL(2) */ * from t1 where t1.c1<10000 and (select count(c1) from t2 where t2.c1=t1.c1) >0;
从以上两个执行计划的对比可以看出,执行计划发生了比较大的变化,首先是不存在表连接操作,其次敢减少了一次索引全扫描,同时也是使用var对相关列进行转换,执行计划的代价也减少一半以上。
三、 测试结果
默认的,达梦对相关子查询是以集合的方式进行优化,即平坦化操作。但是在实际的使用过程中,可能会增加SQL语句的执行步骤,导致SQL语句执行效率虽然增加,但是效率还是不高的情况,这种情况下,使用参数ENABLE_RQ_TO_NONREF_SPL可以更优化的方式对相关子查询进行平坦化操作,减少执行步骤,提升效率。
达梦社区:https://eco.dameng.com