1.1.1 联接消除
联接消除(Join
Elimination)的目的是移除冗余的联接,换句话说,是为了在即使SQL语句明确要求的情况下也能够避免执行联接。对于查询优化器来讲,决定实现这种查询转换是否合理的关键信息,是外键的可用性是强制的还是被标记为RELY的。此外,从11.2版本开始,还会将基于主键的自联接纳入考虑范围之内。这种基于启发式的查询转换在使用包含联接的视图时尤其有用。注意,无论如何,联接消除也可以应用于没有视图的SQL语句。
来看下面这个基于join_elimination.sql脚本的例子。下面的SQL语句定义了一个视图。注意,在这两张表之间存在着父-子关系。实际上是t2表用它的t1_id列引用了t1表的主键:
CREATE VIEW v AS
SELECT ti.id AS t1_id, t1.n AS t1_n, t2.id
AS t2_id, t2.n AS t2_n
FROM
t1,t2
WHERE t1.id = t2.t1_id;
当执行简单的SELECT *
FROM v语句时,可以执行简单视图合并,然后这个查询就会转化如下:
SELECT ti.id AS t1_id, t1.n AS t1_n, t2.id
AS t2_id, t2.n AS t2_n
FROM t1,t2
WHERE ti.id = t2.t1_id;
但是,如下一个例子所演示的,仅当引用在子表中定义的列时(例如,SELECT
t2_id,t2_n FROM v),查询优化器才能消除与父表的联接。这种转换能够实现是因为,外键约束保证t2表中所有的记录一定引用t1表中的一条记录且只引用一条:
SELECT tz.id AS t2_id, t2.n AS t2_n FROM
t2;
1.1.1 联接因式分解
这种自11.2版本开始可用的联接因式分解(Join Factorization)的目的,是识别出正在处理的联合查询的一部分是否可以在各个组成查询中共享,进而避免重复的数据访问和联接。实际上,没有这种查询转换,所有的组件查询在应用集合运算符之前都得单独执行。这是一种基于成本的查询转换,查询优化器只有在基于UNION ALL集合运算符的联合查询中会应用它。
下面的例子来自join_factorization.sql脚本。注意这两个组件查询不仅是访问同一张表,它们还都在相同的表(t2)中施加了一个限制条件。没有这种查询转换,两个组件查询都会单独执行,两个查询中的表都会被访问两次:
SELECT * FROM t1,t2
WHERE ti.id = t2.id AND t2.id< 10
UNION ALL
SELECT * FROM t1,t2
WHERE ti.id = t2.id AND t2.id > 990;
为避免重复处理访问每张表两次的工作,联接因式分解可以转换这个查询,如下面的例子所示。因为表t1被因式分解了,所以它只需访问一次。根据表的大小以及所选择的用于从中抽取数据的访问路径的不同,在I/O和CPU使用方面节省的成本可能会非常显著:
SELECT t1.*, vw_jf.*
FROM t1,(SELECT * FROM t2 WHERE id < 10
UNION ALL
SELECT * FROM t2 WHERE id >990) vw_jf
WHERE ti.id = vw_jf.id;
1.1.1 外联接转内联接
外联接转内联接(Outer
Join to Inner Join)的目的是将不必要的外联接转化为内联接。这样做是因为外联接可能会阻止查询优化器选择某种特定的联接方法或联接顺序。这是一种基于启发式的查询转换。
下面的例子基于outer_to_inner.sql脚本,演示了这种查询转换。注意,限制条件(t2.id IS NOT
NULL)与外联接条件(ti.id
=t2.t1_id(+))有冲突:
SELECT * FROM t1,t2
WHERE ti.id = t2.t1_id(+) AND t2.id IS NOT
NULL;
查询转换移除了外联接运算符以及多余的谓词,并产生了以下查询:
SELECT * from t1,t2
WHERE t1.id = t2.t1_id;