在提到SQL语句的执行计划时,我们常常提到驱动表,那么,什么是驱动表,驱动表一定是表吗?所谓驱动表,又称为外层表,就是在嵌套循环连接和哈希连接中,用来最先获得数据,并以此表的数据为依据,逐步获得其他表的数据,直至最终查询到所有满足条件的数据的第一个表。排序合并连接由于不存在优先访问那张表的顺序问题,因此也没有驱动表的概念。值得注意的是,驱动表并不一定指的是表,而是一个满足条件的记录的集合,Oracle依据这个集合,与其他的表的数据进行连接,这个集合叫做ROW SOURCE,即数据行源,顾名思义,就是由某个表中满足条件的数据行,组成子集合后,再以此子集合作为连接其他表的数据来源。这个子集合,才是真正的驱动表,有时候为了简洁,直接将最先按照条件或得子集合的那张表叫做驱动表。我们常说,驱动表一定是小表,指的是根据条件获得的子集合一定要小,而不是说实体表本身一定要小,大表如果获得的子集合小,一样可以简称这个大表为驱动表。 那么,究竟怎么写SQL,才能让Oracle按照我们的意愿,指定某个表作为驱动表呢?在采用RBO作为优化模式的情况下,SQL语句中表的书写顺序,以及WHERE条件的书写顺序非常重要,Oracle基本上按照这个顺序选择驱动表,但是在CBO模式下,Oracle会根据数据库中的统计信息,以及WHERE条件中的预估结果集,自己识别驱动表,如果统计信息不准确,或者我们的SQL写法不当,都会让CBO错误的选择驱动表。因此,在CBO模式下,表的顺序,WHERE条件的顺序,已不太重要,我们在写SQL时,非常重要的一点就是:根据业务逻辑,确定需要获得信息的主表,然后最大可能的在这个主表上增加限定条件,使得从该表上查询获得的数据最少,再根据这些数据上的字段,关联到其他表,在关联其他表时,最好选择与其他表的主键字段进行比较,或者与已经索引的字段进行比较,这样一来,就有意识地将业务需求的主表,作为驱动表处理了,Oracle也会在选择最优执行计划时,比较容易的找到驱动表。如果WHERE条件过于复杂,或者业务上获得信息的主表并不容易确定,我们可以根据业务的实际情况,评估关联各表的数据量和数据增长量,并分析关键条件字段的区分度,考虑在区分度高的字段,或者区分度高的组合字段上创建索引,以最大限度的降低某个表的结果集,增加其作为驱动表的机会。此外,如果由于WHERE条件不太明确等因素,导致Oracle在选择执行计划时,可能错误的选择驱动表,我们可以添加提示,固定leading表,则Oracle会按照我们的指定要求选择驱动表
1、 根据业务逻辑需求,有意识的给出驱动表的限定条件。
对于测试库的表Client_info和Staff_info两张表
例:业务需求,通过已知客户的客户号,查找该客户的联系业务员的姓名。
分析:这里已知条件为客户的客户号,很显然涉及到的主表是客户表,那么驱动表最好是client_info表,因为通过客户的客户号查询出来结果集以后,再根据这个结果集中的conta_agent,通过与staff_info的empno相等,直接利用staff_info的主键就可以快速获得数据,因此,就要千方百计限制通过客户的客户号查询出来的结果集。分析client_info表发现,这个字段为该表的主键,结果集本身就很小,因此SQL直接写成如下的方式即可:
Select b.emp_name from client_info a,staff_info b
Where a.clientno=:b1
And a.conta_agent=b.empno;
但是,万一很不幸,用户不是要求根据客户姓名来查找联系业务员,而是要求根据客户类型来查找来联系业务员姓名,这么一来,通过客户类型从client_info获得的数据量就很大,这个时候以client_info为驱动表就可能效率很不好,换作以staff_info表作为驱动的表的话,也需要全表扫描staff_info或者驱动数据,效率也不会很高。
这个时候,就需从业务的角度,分析能否尽可能的限定查询条件,例如查询某个客户类型下,特定证件号码的联系业务员(这种情况下,倾向staff_info表就作为驱动表);或某个客户类型下,特定服务业务员的某类客户的联系业务员(倾向以client_info为驱动表),一方面使得用户的查询更有实际意义,另一方面也能减少最终查询到的数据量,提高SQL效率。这是需要和用户沟通,也是需要我们在写SQL时尽量考虑的地方。
Select b.emp_name from client_info a,staff_info b
Where a.client_type=:b1
And b.emp_idno=:b2
And a.conta_agent=b.empno; ---staff_info为驱动表
Select b.emp_name from client_info a,staff_info b
Where a.client_type=:b1
And a.serve_agent=:b2
And a.conta_agent=b.empno; ---client_info为驱动表
2、 增加hint,强制某个表为驱动表
对于上面提到的情况,如果用户一定要求根据客户类型来查找联系业务员姓名,经分
析根据客户类型获得的数据结果集,可能比staff_info的业务员数据量要大得多,即使全表扫描staff_info表,效率也比先获得clinet_info的conta_agent,再查询联系业务员姓名要好的话,我们可以采用如下提示固定驱动表:
Select /*+ leading(b) full(b) use_hash(b,a)*/ emp_name from client_info a,staff_info b
Where a.client_type=:b1
And b.empno=a.conta_agent;
注意:分析思路如此,并不代表一定和实际的运行状况相同,SQL写好以后,最好看看执行计划,并带入实际的数据值进行测试验证。此外,示例仅为了说明问题,抛砖引玉,不一定与符合实际。