oracle完全存在性判断逻辑

40阅读 0评论2025-04-26 dingjun123
分类:Oracle

 对于完全存在性判断,子查询非相关的。。。三大数据库的CBO都是差不多的处理逻辑:
 非相关的希望单独执行一次即可,然后结果缓存下来,对于存在性判断的非相关子查询,希望只执行一次只查询一条。
 
 pgsql的select * from a where exists(select 1 from b where a.id=b.id);这种是判断a.id is not null+子表b有数据则执行的逻辑,
 pg这种判断比oracle,mysql要弱点,它可能走nested loops semi+物化,循环驱动物化表多次,虽然物化表{BANNED}最佳多只存一行(因为存在性判断),
 oracle走FILTER没有问题,MySQL走hash join semi也没有问题,子表b只执行一次只查一行,也{BANNED}最佳多被驱动一次。
 
 ==================Oracle
 这是完全存在性判断,子查询只要有一条,就执行主表条件,子查询没有结果就不执行。这种完全存在性判断,就算子查询走全表也没有关系,
 union all的2个分支,只要找到一条即可,而且子查询只执行一次。
 
 select * from a1 where exists(select 1 from b1 union all select 1 from b2);
 
 Elapsed: 00:00:00.00


执行计划走FILTER,按照FILTER逻辑是主表的每一行驱动子查询(可能有CACHE减少驱动次数),如果这样理解,那么主表有99行,子查询应该驱动执行99次,而且
全表扫描,那么效率必然很低,实际上呢?


见ID=1的FILTER谓词:   1 - filter( EXISTS ( (SELECT 1 FROM "B1" "B1") UNION ALL  (SELECT 1  FROM "B2" "B2")))
这里不像一般的FILTER是有系统绑定变量:B1的,所以,这个FILTER实际上是个存在性判断,O这个计划显示的容易误导人,应该是
子查询找1条,驱动主表,这样就容易理解了




点击(此处)折叠或打开

  1. Execution Plan
  2. ----------------------------------------------------------
  3. Plan hash value: 1160945571

  4. ----------------------------------------------------------------------------
  5. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
  6. ----------------------------------------------------------------------------
  7. | 0 | SELECT STATEMENT | | 99 | 10494 | 9 (0)| 00:00:01 |
  8. |* 1 | FILTER | | | | | |####实际上是假的FILTER
  9. | 2 | TABLE ACCESS FULL | A1 | 99 | 10494 | 3 (0)| 00:00:01 |
  10. | 3 | UNION-ALL | | | | | |
  11. | 4 | TABLE ACCESS FULL| B1 | 99 | | 3 (0)| 00:00:01 |
  12. | 5 | TABLE ACCESS FULL| B2 | 99 | | 3 (0)| 00:00:01 |
  13. ----------------------------------------------------------------------------

  14. Predicate Information (identified by operation id):
  15. ---------------------------------------------------

  16.    1 - filter( EXISTS ( (SELECT 1 FROM "B1" "B1") UNION ALL (SELECT 1
  17.               FROM "B2" "B2")))


  18. Statistics
  19. ----------------------------------------------------------
  20.           0 recursive calls
  21.           0 db block gets
  22.          14 consistent gets
  23.           0 physical reads
  24.           0 redo size
  25.        8764 bytes sent via SQL*Net to client
  26.         503 bytes received via SQL*Net from client
  27.           8 SQL*Net roundtrips to/from client
  28.           0 sorts (memory)
  29.           0 sorts (disk)
  30.          99 rows processed



可以通过真实执行计划看到,子查询Starts{BANNED}最佳多是1,而且B1,B2只找了B1,而且只找到一条,则直接查询A1。所以虽然是FILTER,但是实际执行逻辑
是完全存在性判断,是先执行子查询判断,再执行主表的。

点击(此处)折叠或打开

  1. select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
  2.  
  3. Plan hash value: 1160945571

  4. --------------------------------------------------------------------------------------
  5. | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
  6. --------------------------------------------------------------------------------------
  7. | 0 | SELECT STATEMENT | | 1 | | 99 |00:00:00.01 | 14 |
  8. |* 1 | FILTER | | 1 | | 99 |00:00:00.01 | 14 |
  9. | 2 | TABLE ACCESS FULL | A1 | 1 | 99 | 99 |00:00:00.01 | 11 |
  10. | 3 | UNION-ALL | | 1 | | 1 |00:00:00.01 | 3 |
  11. | 4 | TABLE ACCESS FULL| B1 | 1 | 99 | 1 |00:00:00.01 | 3 |
  12. | 5 | TABLE ACCESS FULL| B2 | 0 | 99 | 0 |00:00:00.01 | 0 |
  13. --------------------------------------------------------------------------------------

  14. Predicate Information (identified by operation id):
  15. ---------------------------------------------------

  16.    1 - filter( IS NOT NULL)

实际的逻辑应该是这样的,B2的starts=0,因为B1找到了1条:  

点击(此处)折叠或打开

  1. select a1.* from a1,(select 1 from (select 1 from b1 union all select 1 from b2) where rownum=1);
  2.     

  3. 99 rows selected.

  4. Elapsed: 00:00:00.00

  5. Execution Plan
  6. ----------------------------------------------------------
  7. Plan hash value: 822935806

  8. -------------------------------------------------------------------------------
  9. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
  10. -------------------------------------------------------------------------------
  11. | 0 | SELECT STATEMENT | | 99 | 10494 | 9 (0)| 00:00:01 |
  12. | 1 | MERGE JOIN CARTESIAN | | 99 | 10494 | 9 (0)| 00:00:01 |
  13. | 2 | VIEW | | 1 | | 6 (0)| 00:00:01 |
  14. |* 3 | COUNT STOPKEY | | | | | |
  15. | 4 | VIEW | | 198 | | 6 (0)| 00:00:01 |
  16. | 5 | UNION-ALL | | | | | |
  17. | 6 | TABLE ACCESS FULL| B1 | 99 | | 3 (0)| 00:00:01 |
  18. | 7 | TABLE ACCESS FULL| B2 | 99 | | 3 (0)| 00:00:01 |
  19. | 8 | BUFFER SORT | | 99 | 10494 | 9 (0)| 00:00:01 |
  20. | 9 | TABLE ACCESS FULL | A1 | 99 | 10494 | 3 (0)| 00:00:01 |
  21. -------------------------------------------------------------------------------

  22. Predicate Information (identified by operation id):
  23. ---------------------------------------------------

  24.    3 - filter(ROWNUM=1)



  25. PLAN_TABLE_OUTPUT
  26. --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  27. SQL_ID gqacy9u017x5g, child number 0
  28. -------------------------------------
  29.       select a1.* from a1,(select 1 from (select 1 from b1 union all
  30. select 1 from b2) where rownum=1)

  31. Plan hash value: 822935806

  32. --------------------------------------------------------------------------------------------------------------------
  33. | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
  34. --------------------------------------------------------------------------------------------------------------------
  35. | 0 | SELECT STATEMENT | | 1 | | 99 |00:00:00.01 | 7 | | | |
  36. | 1 | MERGE JOIN CARTESIAN | | 1 | 99 | 99 |00:00:00.01 | 7 | | | |
  37. | 2 | VIEW | | 1 | 1 | 1 |00:00:00.01 | 3 | | | |
  38. |* 3 | COUNT STOPKEY | | 1 | | 1 |00:00:00.01 | 3 | | | |
  39. | 4 | VIEW | | 1 | 198 | 1 |00:00:00.01 | 3 | | | |
  40. | 5 | UNION-ALL | | 1 | | 1 |00:00:00.01 | 3 | | | |
  41. | 6 | TABLE ACCESS FULL| B1 | 1 | 99 | 1 |00:00:00.01 | 3 | | | |
  42. | 7 | TABLE ACCESS FULL| B2 | 0 | 99 | 0 |00:00:00.01 | 0 | | | |
  43. | 8 | BUFFER SORT | | 1 | 99 | 99 |00:00:00.01 | 4 | 20480 | 20480 |18432 (0)|
  44. | 9 | TABLE ACCESS FULL | A1 | 1 | 99 | 99 |00:00:00.01 | 4 | | | |
  45. --------------------------------------------------------------------------------------------------------------------

  46. Predicate Information (identified by operation id):
  47. ---------------------------------------------------

  48.    3 - filter(ROWNUM=1)

这种存在性判断,子查询条件where a1.object_id=a1.object_id (一样的),只找a1的object_id  is not null + 表b1有数据即可,
使用FILTER存在性检查,同样这种a1和b1没有关联的,对于子查询b1只找1次,1条即可。


oracle这种还是比较智能的,后面可以看看pg,mysql的,会转为nested loops semi,会执行多次b1的扫描,不够优化,当然他们会使用materilization技术提高效率。


点击(此处)折叠或打开

  1. select * from a1 where exists (select 1 from b1 where a1.object_id=a1.object_id);
  2. select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

  3. PLAN_TABLE_OUTPUT
  4. --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  5. SQL_ID 4snt03yzrpmcq, child number 0
  6. -------------------------------------
  7. select * from a1 where exists (select 1 from b1 where
  8. a1.object_id=a1.object_id)

  9. Plan hash value: 1803926644

  10. -------------------------------------------------------------------------------------
  11. | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
  12. -------------------------------------------------------------------------------------
  13. | 0 | SELECT STATEMENT | | 1 | | 99 |00:00:00.01 | 13 |
  14. |* 1 | FILTER | | 1 | | 99 |00:00:00.01 | 13 |
  15. |* 2 | TABLE ACCESS FULL| A1 | 1 | 99 | 99 |00:00:00.01 | 11 |
  16. | 3 | TABLE ACCESS FULL| B1 | 1 | 1 | 1 |00:00:00.01 | 2 |
  17. -------------------------------------------------------------------------------------

  18. Predicate Information (identified by operation id):
  19. ---------------------------------------------------

  20.    1 - filter( IS NOT NULL)
  21.    2 - filter("A1"."OBJECT_ID" IS NOT NULL

等价于这种写法,执行计划与上面的完全一样,像pg改成这种才能扫描b1一次。   


点击(此处)折叠或打开

  1. select * from a1
  2. where exists (select 1 from b1)
  3. and a1.object_id is not null;

  4. PLAN_TABLE_OUTPUT
  5. --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  6. SQL_ID cwt341hjrrppu, child number 0
  7. -------------------------------------
  8. select * from a1 where exists (select 1 from b1) and a1.object_id is
  9. not null

  10. Plan hash value: 1803926644

  11. -------------------------------------------------------------------------------------
  12. | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
  13. -------------------------------------------------------------------------------------
  14. | 0 | SELECT STATEMENT | | 1 | | 99 |00:00:00.01 | 13 |
  15. |* 1 | FILTER | | 1 | | 99 |00:00:00.01 | 13 |
  16. |* 2 | TABLE ACCESS FULL| A1 | 1 | 99 | 99 |00:00:00.01 | 11 |
  17. | 3 | TABLE ACCESS FULL| B1 | 1 | 1 | 1 |00:00:00.01 | 2 |
  18. -------------------------------------------------------------------------------------

  19. Predicate Information (identified by operation id):
  20. ---------------------------------------------------

  21.    1 - filter( IS NOT NULL)
  22.    2 - filter("A1"."OBJECT_ID" IS NOT NULL)






上一篇:oracle,mysql组合索引前导列范围条件且包含等值条件,可能效率只有范围的更高
下一篇:oracle使用扩展列统计信息解决复杂谓词cardinality估算不准的问题