Oracle关于半连接SQL执行计划的执行路径一些有趣的实验
Oracle关于半连接SQL执行计划的执行路径一些有趣的实验
从摩天轮的问答里边看到的一个问题,https://www.modb.pro/issue/34573
大概有这么条SQL(包括环境构造语句),如何强制走出nested loops的执行路径。
create table tb1 as select * from dba_objects; create table tb2 as select * from dba_objects; create index idx_tb1 on tb1(object_id); create index idx_tb2 on tb2(object_id); create table t99 as select * from (select * from tb1 order by dbms_random.value) where rownum<=99; create view v_t1t2 as select * from tb1 union all select * from tb2; exec DBMS_STATS.GATHER_TABLE_STATS (user, 'TB1'); exec DBMS_STATS.GATHER_TABLE_STATS (user, 'TB2'); exec DBMS_STATS.GATHER_TABLE_STATS (user, 'T99'); --原生SQL: select a.owner,a.object_id from v_t1t2 a where exists (select 1 from t99 b where a.object_id=b.object_id);
默认SQL会走出”HASH JOIN RIGHT SEMI“的执行路径,https://www.cnblogs.com/PiscesCanon/p/18130472
----------------------------------------------------------------------------------------- | Id | Operation | Name | E-Rows | Cost (%CPU)| OMem | 1Mem | Used-Mem | ----------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | 447 (100)| | | | |* 1 | HASH JOIN RIGHT SEMI| | 196 | 447 (1)| 2293K| 2293K| 1528K (0)| | 2 | VIEW | VW_SQ_1 | 99 | 2 (0)| | | | | 3 | TABLE ACCESS FULL | T99 | 99 | 2 (0)| | | | | 4 | VIEW | V_T1T2 | 109K| 444 (1)| | | | | 5 | UNION-ALL | | | | | | | | 6 | TABLE ACCESS FULL| TB1 | 54626 | 222 (1)| | | | | 7 | TABLE ACCESS FULL| TB2 | 54627 | 222 (1)| | | | -----------------------------------------------------------------------------------------
如果强制使用/*+ nl_sj */,并不能走出嵌套循环半连接,优化器会自动给你转成内连接并使用了hash join。
这里我觉得优化器是有问题的,既然我使用/*+ nl_sj */强制你走嵌套循环半连接,你不应该给我转内连接。
select a.owner,a.object_id from v_t1t2 a where exists (select /*+ unnest nl_sj */ 1 from t99 b where a.object_id=b.object_id); ----------------------------------------------------------------------------------------- | Id | Operation | Name | E-Rows | Cost (%CPU)| OMem | 1Mem | Used-Mem | ----------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | 448 (100)| | | | |* 1 | HASH JOIN | | 194 | 448 (1)| 2293K| 2293K| 1540K (0)| | 2 | VIEW | VW_SQ_1 | 99 | 2 (0)| | | | | 3 | HASH UNIQUE | | 98 | | 2063K| 2063K| 1322K (0)| | 4 | TABLE ACCESS FULL| T99 | 99 | 2 (0)| | | | | 5 | VIEW | V_T1T2 | 109K| 444 (1)| | | | | 6 | UNION-ALL | | | | | | | | 7 | TABLE ACCESS FULL| TB1 | 54626 | 222 (1)| | | | | 8 | TABLE ACCESS FULL| TB2 | 54627 | 222 (1)| | | | -----------------------------------------------------------------------------------------
其实也是有办法避免内连接的转换的,后续也进行了各种hint的实验,这里直接放结果了(情况4存在点问题),也可以去摩天轮的那个链接里边看。
原生SQL语句:
情况1:子查询不展开,只能走filter
select a.owner,a.object_id from v_t1t2 a where exists (select /*+ no_unnest */ 1 from t99 b where a.object_id=b.object_id); ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5463 | 117K| 197K (1)| 00:39:29 | |* 1 | FILTER | | | | | | | 2 | VIEW | V_T1T2 | 109K| 2347K| 445 (1)| 00:00:06 | | 3 | UNION-ALL | | | | | | | 4 | TABLE ACCESS FULL| TB1 | 54626 | 800K| 223 (1)| 00:00:03 | | 5 | TABLE ACCESS FULL| TB2 | 54627 | 800K| 223 (1)| 00:00:03 | |* 6 | TABLE ACCESS FULL | T99 | 1 | 5 | 2 (0)| 00:00:01 | -------------------------------------------------------------------------------
情况2:子查询展开,走 nested loops semi 执行路径,驱动表 v_t1t2 a
_cost_equality_semi_join=false避免了优化器改写为内连接的可能。
select /*+ opt_param('_cost_equality_semi_join' 'false') */ a.owner,a.object_id from v_t1t2 a where exists (select /*+ unnest nl_sj */ 1 from t99 b where a.object_id=b.object_id); -------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 196 | 8428 | 219K (1)| 00:43:51 | | 1 | NESTED LOOPS SEMI | | 196 | 8428 | 219K (1)| 00:43:51 | | 2 | VIEW | V_T1T2 | 109K| 3200K| 444 (1)| 00:00:06 | | 3 | UNION-ALL | | | | | | | 4 | TABLE ACCESS FULL| TB1 | 54626 | 800K| 222 (1)| 00:00:03 | | 5 | TABLE ACCESS FULL| TB2 | 54627 | 800K| 222 (1)| 00:00:03 | |* 6 | VIEW | VW_SQ_1 | 1 | 13 | 2 (0)| 00:00:01 | | 7 | TABLE ACCESS FULL | T99 | 99 | 495 | 2 (0)| 00:00:01 | --------------------------------------------------------------------------------
情况3:子查询展开,nested loops(被改为内连接),驱动表 VW_SQ_1 (b去重后的内联视图)
select /*+ leading("VW_SQ_1"@"SEL$04A8DF8C" A) use_nl(a) semi_to_inner("VW_SQ_1"@"SEL$04A8DF8C") */ a.owner,a.object_id from v_t1t2 a where exists (select /*+ unnest */ 1 from t99 b where a.object_id=b.object_id); ---------------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | Cost (%CPU)| A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | ---------------------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 43521 (100)| 196 |00:00:04.48 | 161K| | | | | 1 | NESTED LOOPS | | 1 | 194 | 43521 (1)| 196 |00:00:04.48 | 161K| | | | | 2 | VIEW | VW_SQ_1 | 1 | 99 | 2 (0)| 99 |00:00:00.01 | 3 | | | | | 3 | HASH UNIQUE | | 1 | 98 | | 99 |00:00:00.01 | 3 | 2063K| 2063K| 1368K (0)| | 4 | TABLE ACCESS FULL| T99 | 1 | 99 | 2 (0)| 99 |00:00:00.01 | 3 | | | | |* 5 | VIEW | V_T1T2 | 99 | 2 | 444 (1)| 196 |00:00:04.48 | 161K| | | | | 6 | UNION-ALL | | 99 | | | 10M|00:00:03.68 | 161K| | | | | 7 | TABLE ACCESS FULL| TB1 | 99 | 54626 | 222 (1)| 5407K|00:00:00.60 | 80593 | | | | | 8 | TABLE ACCESS FULL| TB2 | 99 | 54627 | 222 (1)| 5408K|00:00:00.60 | 80593 | | | | ---------------------------------------------------------------------------------------------------------------------------------- Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - SEL$2E82301F 2 - SEL$8F9407EC / VW_SQ_1@SEL$04A8DF8C 3 - SEL$8F9407EC 4 - SEL$8F9407EC / B@SEL$4 5 - SET$1 / A@SEL$1 6 - SET$1 7 - SEL$2 / TB1@SEL$2 8 - SEL$3 / TB2@SEL$3
情况4:子查询展开,原本想在情况3的被改写为内连接的 nested loops 下,驱动表改为 v_t1t2 a,但没成功,不知道是优化器本身不支持,还是我hint添加有问题。
只能走出:NESTED LOOPS SEMI select /*+ leading(A) use_nl("VW_SQ_1"@"SEL$04A8DF8C") semi_to_inner(@"SEL$2E82301F" "VW_SQ_1"@"SEL$04A8DF8C") */ a.owner,a.object_id from v_t1t2 a where exists (select /*+ unnest */ 1 from t99 b where a.object_id=b.object_id);
但是理论上来说内连接的NL驱动表和被驱动表是可以通过hint来指定的。
原生SQL等价于下边的内连接改写:这个改写后的SQL就可以随意指定NL的驱动表。
select /*+ leading(a) no_merge(a) no_push_pred(a) use_nl(b) */ a.owner,a.object_id from v_t1t2 a,(select distinct object_id from t99) b where a.object_id=b.object_id; select /*+ leading(b) no_merge(a) no_push_pred(a) use_nl(a) */ a.owner,a.object_id from v_t1t2 a,(select distinct object_id from t99) b where a.object_id=b.object_id;
情况5:子查询展开, hash join semi,驱动表v_t1t2 a
select /*+ opt_param('_cost_equality_semi_join' 'false') no_swap_join_inputs(@"SEL$2E82301F" "VW_SQ_1"@"SEL$04A8DF8C") */ a.owner,a.object_id from v_t1t2 a where exists (select /*+ unnest hash_sj */ 1 from t99 b where a.object_id=b.object_id); ------------------------------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem | ------------------------------------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | | 196 |00:00:00.10 | 1644 | 189 | | | | |* 1 | HASH JOIN SEMI | | 1 | 196 | 196 |00:00:00.10 | 1644 | 189 | 6700K| 2024K| 10M (0)| | 2 | VIEW | V_T1T2 | 1 | 109K| 109K|00:00:00.05 | 1628 | 189 | | | | | 3 | UNION-ALL | | 1 | | 109K|00:00:00.04 | 1628 | 189 | | | | | 4 | TABLE ACCESS FULL| TB1 | 1 | 54626 | 54626 |00:00:00.01 | 814 | 174 | | | | | 5 | TABLE ACCESS FULL| TB2 | 1 | 54627 | 54627 |00:00:00.01 | 814 | 15 | | | | | 6 | VIEW | VW_SQ_1 | 1 | 99 | 99 |00:00:00.01 | 16 | 0 | | | | | 7 | TABLE ACCESS FULL | T99 | 1 | 99 | 99 |00:00:00.01 | 16 | 0 | | | | ------------------------------------------------------------------------------------------------------------------------------
情况6:子查询展开, hash join right semi,驱动表 vw_sq_1 (b去重后的内联视图)
select /*+ opt_param('_cost_equality_semi_join' 'false') */ a.owner,a.object_id from v_t1t2 a where exists (select /*+ unnest hash_sj */ 1 from t99 b where a.object_id=b.object_id); --------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | --------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 196 |00:00:00.13 | 1645 | | | | |* 1 | HASH JOIN RIGHT SEMI| | 1 | 196 | 196 |00:00:00.13 | 1645 | 2293K| 2293K| 1427K (0)| | 2 | VIEW | VW_SQ_1 | 1 | 99 | 99 |00:00:00.01 | 3 | | | | | 3 | TABLE ACCESS FULL | T99 | 1 | 99 | 99 |00:00:00.01 | 3 | | | | | 4 | VIEW | V_T1T2 | 1 | 109K| 109K|00:00:00.09 | 1642 | | | | | 5 | UNION-ALL | | 1 | | 109K|00:00:00.07 | 1642 | | | | | 6 | TABLE ACCESS FULL| TB1 | 1 | 54626 | 54626 |00:00:00.01 | 821 | | | | | 7 | TABLE ACCESS FULL| TB2 | 1 | 54627 | 54627 |00:00:00.01 | 821 | | | | ---------------------------------------------------------------------------------------------------------------------
情况7:子查询展开,hash join(被改为内连接),驱动表 vw_sq_1 (b去重后的内联视图)
select a.owner,a.object_id from v_t1t2 a where exists (select /*+ unnest nl_sj */ 1 from t99 b where a.object_id=b.object_id); --------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | --------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 196 |00:00:00.13 | 1645 | | | | |* 1 | HASH JOIN | | 1 | 194 | 196 |00:00:00.13 | 1645 | 2293K| 2293K| 1393K (0)| | 2 | VIEW | VW_SQ_1 | 1 | 99 | 99 |00:00:00.01 | 3 | | | | | 3 | HASH UNIQUE | | 1 | 98 | 99 |00:00:00.01 | 3 | 2063K| 2063K| 1338K (0)| | 4 | TABLE ACCESS FULL| T99 | 1 | 99 | 99 |00:00:00.01 | 3 | | | | | 5 | VIEW | V_T1T2 | 1 | 109K| 109K|00:00:00.09 | 1642 | | | | | 6 | UNION-ALL | | 1 | | 109K|00:00:00.07 | 1642 | | | | | 7 | TABLE ACCESS FULL| TB1 | 1 | 54626 | 54626 |00:00:00.01 | 821 | | | | | 8 | TABLE ACCESS FULL| TB2 | 1 | 54627 | 54627 |00:00:00.01 | 821 | | | | ---------------------------------------------------------------------------------------------------------------------
情况8:子查询展开,hash join(被改为内连接),驱动表 v_t1t2 a
select /*+ LEADING(@"SEL$2E82301F" "VW_SQ_1"@"SEL$04A8DF8C") semi_to_inner(@"SEL$2E82301F" "VW_SQ_1"@"SEL$04A8DF8C") swap_join_inputs(a) */ a.owner,a.object_id from v_t1t2 a where exists (select /*+ unnest */ 1 from t99 b where a.object_id=b.object_id); ---------------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | Cost (%CPU)| A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | ---------------------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 666 (100)| 196 |00:00:00.16 | 1631 | | | | |* 1 | HASH JOIN | | 1 | 194 | 666 (1)| 196 |00:00:00.16 | 1631 | 6700K| 2024K| 10M (0)| | 2 | VIEW | V_T1T2 | 1 | 109K| 444 (1)| 109K|00:00:00.09 | 1628 | | | | | 3 | UNION-ALL | | 1 | | | 109K|00:00:00.07 | 1628 | | | | | 4 | TABLE ACCESS FULL| TB1 | 1 | 54626 | 222 (1)| 54626 |00:00:00.01 | 814 | | | | | 5 | TABLE ACCESS FULL| TB2 | 1 | 54627 | 222 (1)| 54627 |00:00:00.01 | 814 | | | | | 6 | VIEW | VW_SQ_1 | 1 | 99 | 2 (0)| 99 |00:00:00.01 | 3 | | | | | 7 | HASH UNIQUE | | 1 | 98 | | 99 |00:00:00.01 | 3 | 2063K| 2063K| 1355K (0)| | 8 | TABLE ACCESS FULL| T99 | 1 | 99 | 2 (0)| 99 |00:00:00.01 | 3 | | | | ---------------------------------------------------------------------------------------------------------------------------------- Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - SEL$2E82301F 2 - SET$1 / A@SEL$1 3 - SET$1 4 - SEL$2 / TB1@SEL$2 5 - SEL$3 / TB2@SEL$3 6 - SEL$8F9407EC / VW_SQ_1@SEL$04A8DF8C 7 - SEL$8F9407EC 8 - SEL$8F9407EC / B@SEL$4