两个表关联没有连接条件的时候会产生笛卡儿积,这种表连接方式就叫笛卡儿连接。

    我们在测试账号 scott 中运行如下 SQL。

    1. set autot trace
    2. select * from emp, dept;
    1. 56 rows selected.
    2. Execution Plan
    3. ----------------------------------------------------------
    4. Plan hash value: 2034389985
    5. -----------------------------------------------------------------------------
    6. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
    7. -----------------------------------------------------------------------------
    8. | 0 | SELECT STATEMENT | | 56 | 3248 | 8 (0)| 00:00:01 |
    9. | 1 | MERGE JOIN CARTESIAN| | 56 | 3248 | 8 (0)| 00:00:01 |
    10. | 2 | TABLE ACCESS FULL | DEPT | 4 | 80 | 3 (0)| 00:00:01 |
    11. | 3 | BUFFER SORT | | 14 | 532 | 5 (0)| 00:00:01 |
    12. | 4 | TABLE ACCESS FULL | EMP | 14 | 532 | 1 (0)| 00:00:01 |
    13. -----------------------------------------------------------------------------

    执行计划中 MERGE JOIN CARTESIAN 就表示笛卡儿连接。笛卡儿连接会返回两个表的乘积。DEPT 有 4 行数据,EMP 有 14 行数据,两个表进行笛卡儿连接之后会返回 56 行数据。笛卡儿连接会对两表中其中一个表进行排序,执行计划中的 BUFFER SORT 就表示排序。

    在多表关联的时候,两个表没有直接关联条件,但是优化器错误地把某个表返回的 Rows 算为 1 行(注意必须是 1 行),这个时候也可能发生笛卡儿连接。例子如下。

    1. select * from table(dbms_xplan.display());
    1. PLAN_TABLE_OUTPUT
    2. -----------------------------------------------------------------------------
    3. Plan hash value: 710264295
    4. -----------------------------------------------------------------------------
    5. | Id | Operation | Name | Rows |
    6. -----------------------------------------------------------------------------
    7. | 0 | SELECT STATEMENT | | 1 |
    8. | 1 | WINDOW SORT | | 1 |
    9. |* 2 | TABLE ACCESS BY INDEX ROWID | F_AGT_GUARANTY_INFO_H | 1 |
    10. | 3 | NESTED LOOPS | | 1 |
    11. | 4 | NESTED LOOPS | | 1 |
    12. | 5 | MERGE JOIN CARTESIAN | | 1 |
    13. | 6 | TABLE ACCESS FULL | B_M_BUSINESS_CONTRACT | 1 |
    14. | 7 | BUFFER SORT | | 61507 |
    15. |* 8 | TABLE ACCESS FULL | F_AGT_GUARANTY_RELATIVE_H | 61507 |
    16. | 9 | TABLE ACCESS BY INDEX ROWID| F_CONTRACT_RELATIVE | 1 |
    17. |* 10 | INDEX UNIQUE SCAN | SYS_C0019578 | 1 |
    18. |* 11 | INDEX RANGE SCAN | SYS_C005707 | 1 |
    19. -----------------------------------------------------------------------------

    执行计划中 Id=6 的表和 Id=8 的表就是进行笛卡儿连接的。

    在这个执行计划中,为什么优化器会选择笛卡儿积连接呢?

    因为 Id=6 这个表返回的 Rows 被优化器错误地估算为 1 行,优化器认为 1 行的表与任意大小的表进行笛卡儿关联,数据也不会翻番,这是安全的。所以这里优化器选择了笛卡儿连接

    Id=6 这步是全表扫描,而且没过滤条件(因为没有*),优化器认为它只返回 1 行。大家请思考,全表扫描返回 1 行并且无过滤条件,这个可能吗?难道表里面真的就只有 1 行数据?这不符合常识。那么显然是 Id=6 的表没有收集统计信息,导致优化器默认地把该表算为 1 行(当时数据库没开启动态采样)。下面是上述执行计划的 SQL 语句。

    1. SELECT b.agmt_id
    2. b.corp_org
    3. b.cur_cd
    4. b.businesstype
    5. c.object_no
    6. c.guaranty_crsum
    7. row_number() overPARTITION BY b.agmt_id, b.corp_org, c.object_no
    8. ORDER BY b.agmt_id, b.corp_org, c.object_no row_no
    9. FROM b_m_business_contract b --合同表
    10. dwf.f_contract_relative c --合同关联表
    11. dwf.f_agt_guaranty_relative_h r --业务合同、担保合同与担保物关联表
    12. dwf.f_agt_guaranty_info_h g --担保物信息表
    13. WHERE b.corp_org = c.corp_org
    14. AND b.agmt_id = c.contract_seqno --业务合同号
    15. AND c.object_type = GuarantyContract
    16. AND r.start_dt <= DATE 2012-09-17 /*当天日期*/
    17. AND r.end_dt > DATE 2012-09-17 /*当天日期*/
    18. AND c.contract_seqno = r.object_no --业务合同号
    19. AND c.object_no = r.guaranty_no --担保合同编号
    20. AND c.corp_org = r.corp_org --企业法人编码
    21. AND r.object_type = BusinessContract
    22. AND r.agmt_id = g.agmt_id --担保物编号
    23. AND r.corp_org = g.corp_org --企业法人编码
    24. AND g.start_dt <= DATE 2012-09-17 /*当天日期*/
    25. AND g.end_dt > DATE 2012-09-17 /*当天日期*/
    26. AND g.guarantytype = 020010 --质押存款

    执行计划中进行笛卡儿关联的表就是 b 和 r,在 SQL 语句中 b 和 r 没有直接关联条件。

    如果两个表有直接关联条件,无法控制两个表进行笛卡儿连接。

    如果两个表没有直接关联条件,我们在编写 SQL 的时候将两个表依次放在 from 后面并且添加 HINT:ordered,就可以使两个表进行笛卡儿积关联。

    1. select /*+ ordered */
    2. a.ename, a.sal, a.deptno, b.dname, c.grade
    3. from dept b, salgrade c, emp a
    4. where a.deptno = b.deptno
    5. and a.sal between c.losal and c.hisal;
    1. 14 rows selected.
    2. Execution Plan
    3. ----------------------------------------------------------
    4. Plan hash value: 2197699399
    5. ----------------------------------------------------------------------------------
    6. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
    7. ----------------------------------------------------------------------------------
    8. | 0 | SELECT STATEMENT | | 1 | 65 | 12 (9)| 00:00:01 |
    9. |* 1 | HASH JOIN | | 1 | 65 | 12 (9)| 00:00:01 |
    10. | 2 | MERGE JOIN CARTESIAN| | 20 | 1040 | 8 (0)| 00:00:01 |
    11. | 3 | TABLE ACCESS FULL | DEPT | 4 | 52 | 3 (0)| 00:00:01 |
    12. | 4 | BUFFER SORT | | 5 | 195 | 5 (0)| 00:00:01 |
    13. | 5 | TABLE ACCESS FULL | SALGRADE | 5 | 195 | 1 (0)| 00:00:01 |
    14. | 6 | TABLE ACCESS FULL | EMP | 14 | 182 | 3 (0)| 00:00:01 |
    15. ----------------------------------------------------------------------------------
    16. Predicate Information (identified by operation id):
    17. ---------------------------------------------------
    18. 1 - access("A"."DEPTNO"="B"."DEPTNO")
    19. filter("A"."SAL">="C"."LOSAL" AND "A"."SAL"<="C"."HISAL")

    在 SQL 语句中,DEPT 与 SALGRADE 没有直接关联条件,HINT:ordered 表示根据 SQL 语句中 from 后面表的顺序依次关联。因为 DEPT 与 SALGRADE 没有直接关联条件,而且 SQL 语句中添加了 HINT:ordered,再有 SQL 语句中两个表是依次放在 from 后面的,所以 DEPT 与 SALGRADE 只能进行笛卡儿连接。

    思考:当执行计划中有笛卡儿连接应该怎么优化呢?

    首先应该检查表是否有关联条件,如果表没有关联条件,那么应该询问开发与业务人员为何表没有关联条件,是否为满足业务需求而故意不写关联条件。

    其次应该检查离笛卡儿连接最近的表是否真的返回 1 行数据,如果返回行数真的只有 1 行,那么走笛卡儿连接是没有问题的,如果返回行数超过 1 行,那就需要检查为什么 Rows 会估算错误,同时要纠正错误的 Rows。纠正错误的 Rows 之后,优化器就不会走笛卡儿连接了。

    我们可以使用 HINT /*+ opt_param('_optimizer_mjc_enabled', 'false')* / 禁止笛卡儿连接。