此脚本不依赖统计信息。

    两表关联返回少量数据应该走嵌套循环,如果返回大量数据,应该走 HASH 连接,或者是排序合并连接。如果一个 SQL 语句返回行数较多(大于 1 万行),SQL 的执行计划在最后几步(Id<=5)走了嵌套循环,我们可以判定该执行计划中的嵌套循环是有问题的,应该走 HASH 连接。以下脚本抓出返回行数较多的嵌套循环 SQL。

    1. select *
    2. from (select parsing_schema_name schema,
    3. sql_id,
    4. sql_text,
    5. rows_processed / executions rows_processed
    6. from v$sql
    7. where parsing_schema_name = 'SCOTT'
    8. and executions > 0
    9. and rows_processed / executions > 10000
    10. order by 4 desc) a
    11. where a.sql_id in (select sql_id
    12. from v$sql_plan
    13. where operation like '%NESTED LOOPS%'
    14. and id <= 5);

    在 scott 账户中分别创建 a 表和 b 表以及一个索引。

    1. create table a as select * from dba_objects;
    1. Table created.
    1. create table b as select * from dba_objects;
    1. Table created.
    1. create index idx_b on b(object_id);
    1. Index created.

    运行如下 SQL 并且查看执行计划。

    1. select /*+ use_nl(a,b) */ * from a,b where a.object_id=b.object_id;
    1. 72695 rows selected.
    2. Execution Plan
    3. ----------------------------------------------------------
    4. Plan hash value: 2104163270
    5. -------------------------------------------------------------------------------------
    6. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
    7. -------------------------------------------------------------------------------------
    8. | 0 | SELECT STATEMENT | | 60140 | 23M| 120K (1)| 00:24:07 |
    9. | 1 | NESTED LOOPS | | | | | |
    10. | 2 | NESTED LOOPS | | 60140 | 23M| 120K (1)| 00:24:07 |
    11. | 3 | TABLE ACCESS FULL | A | 60140 | 11M| 187 (2)| 00:00:03 |
    12. |* 4 | INDEX RANGE SCAN | IDX_B | 1 | | 1 (0)| 00:00:01 |
    13. | 5 | TABLE ACCESS BY INDEX ROWID| B | 1 | 207 | 2 (0)| 00:00:01 |
    14. -------------------------------------------------------------------------------------
    15. Predicate Information (identified by operation id):
    16. ---------------------------------------------------
    17. 4 - access("A"."OBJECT_ID"="B"."OBJECT_ID")
    18. Note
    19. -----
    20. - dynamic sampling used for this statement (level=2)
    21. Statistics
    22. ----------------------------------------------------------
    23. 632 recursive calls
    24. 0 db block gets
    25. 22985 consistent gets
    26. 1196 physical reads
    27. 0 redo size
    28. 6085032 bytes sent via SQL*Net to client
    29. 53725 bytes received via SQL*Net from client
    30. 4848 SQL*Net roundtrips to/from client
    31. 0 sorts (memory)
    32. 0 sorts (disk)
    33. 72695 rows processed

    我们可以使用脚本将错误的嵌套循环抓出。

    1. select *
    2. from (select parsing_schema_name schema,
    3. sql_id,
    4. sql_text,
    5. rows_processed / executions rows_processed
    6. from v$sql
    7. where parsing_schema_name = 'SCOTT'
    8. and executions > 0
    9. and rows_processed / executions > 10000
    10. order by 4 desc) a
    11. where a.sql_id in (select sql_id
    12. from v$sql_plan
    13. where operation like '%NESTED LOOPS%'
    14. and id <= 5);
    1. SCHEMA SQL_ID SQL_TEXT ROWS_PROCESSED
    2. --------------- --------------- ------------------------------ --------------
    3. SCOTT 4dwp5u34yv7mj select /*+ use_nl(a,b) */ * 72695
    4. from a,b where a.object_id=b.object_id