外连接常见误区——别以为把on条件写到子查询里就会快了

  昨天工作中一个同事A写的存储过程被配置调度的另一位同事B建议要求修改,将"LEFT JOIN ... ON 条件集"里的非等值关联条件写到一个子查询里(之前他是写到子查询里,后来模仿我的代码习惯写的),而他这么改后存储过程似乎跑得更快了,于是同事A跟我说同事B让我也改一下。我说完全没必要改,这么改根本不能提速,SQL执行计划都是同一个,怎么就性能优化了呢?!

  这个问题被好多不懂装懂的“前辈”以讹传讹,误导了多少大好青年。分析、测试、解决问题要讲原理,不能单凭某人的只言片语就照他说的做。下面说一下我是如何进行SQL优化的:

SQL优化主要要看执行计划,对数据库基本原理和代数优化也要有一定的了解。先按SQL语法的解析顺序开始看起,WITH AS->FROM ->JOIN->ON->WHERE->GROUP BY->HAVING->SELECT->ORDER BY。

然后基于表的索引、分区等信息把SQL语句中的条件代码最优化,之后测试性能是否满足业务要求。测试时候也不能在生产或预生产环境测试,干扰太大。虽然没做过测试岗位,但也能理解个大概,正确的测试理念是在完全没有其他外界干扰的测试环境中单独测试程序的性能、稳定性、准确性。像同事A这样,在一个外界干扰因素繁多且如此之大的环境,测试了一下改动后的存过,发现跑得快了,就说是性能有提升,我也是醉了。

如果性能仍不能满足要求,有相关权限的可以用trace文件追踪一下,毕竟某些情况下(https://mp.csdn.net/postedit/86289881),SQL执行计划会失真,与实际执行差别很大。此外,还可以借助AWR报告,分析程序运行过程中数据库的整体性能耗费,找出关键原因。

对于我要说的主要问题——外连接误区,分析过程如下:

1、先看FROM后面都接了哪些表,根据表的数据量判断表的顺序是否会影响性能,子查询(视图)是否数据量太大占用太多的TEMP表空间、代码太长等都是需要考虑的问题。

2、如果涉及多表关联,建议用(INNER) JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN接ON的形式写,不要用WHERE COLUMN_A = COLUMN_B(+)的形式。前者是标准SQL语法兼容所有关系型DBMS,更利于阅读代码,后者一方面不利于阅读代码,一方面属于Oracle的语法,在数据迁移时会不兼容其他数据库平台。

写LEFT/RIGHT JOIN ... ON时要注意ON条件的写法。所有JOIN中ON条件里正常的话应该是代表两张表之间的关联关系,可等值、非等值,在INNER JOIN中是个特例,条件写在ON中和WHERE中都是一样的,但在OUTER JOIN中就不行了。因为OUTER JOIN的结果集要比INNER JOIN大,比如一个查询:

SELECT *
  FROM A
  LEFT JOIN B
    ON A.DEPT_ID = B.DEPT_ID
   AND B.DEPT_LVL > 4
 WHERE B.CITY = 'SH';

ON条件中代表TA和TB的关联关系的仅仅是A.DEPT_ID = B.DEPT_ID,而后面那个B.DEPT_LVL > 4满不满足我都要把它查出来,不谈WHERE条件的话,不管A表怎么LEFT JOIN TAB_B表,最终的结果永远是A表的全集,A有多少种记录,结果集就有多少种记录(当B表存在多条数据可以与A表关联时会出现结果集比A表数据量多的情况),至于结果集中包含的B表字段,满足ON里所有条件(A.DEPT_ID = B.DEPT_ID AND B.DEPT_LVL > 4)的为B表中该字段的值,不满足的为NULL。

WHERE 条件用来筛选JOIN操作后的结果集。所以加了WHERE条件的LEFT JOIN会导致最终结果集不再是A的全集,而是满足WHERE条件的A的子集。

上面那个查询等同于下面这个查询,Oracle在语法解析时会自动将非关联条件作为限制B表的查询条件,看到的执行计划是同一个,耗费、字节、IO、CPU耗费、过滤谓词自然也是一样的。没有现成环境,工作数据库涉及到保密,不方便展示。不信的可以自己试试!

SELECT *
  FROM A
  LEFT JOIN (SELECT * FROM B WHERE DEPT_LVL > 4) C
    ON A.DEPT_ID = C.DEPT_ID
 WHERE C.CITY = 'SH';

既然同样的执行过程,为什么非要多打一些代码搞个子查询(视图)出来呢?!

你可能感兴趣的:(Oracle)