Skip to content

Oracle 执行计划分析

执行计划是Oracle数据库执行SQL语句的详细步骤和策略,是SQL优化的核心。了解和分析执行计划对于DBA优化SQL语句、提高数据库性能至关重要。本文将详细介绍Oracle 19c和21c环境下执行计划的生成、解读和优化方法,结合实际案例和最佳实践,帮助DBA掌握执行计划分析的核心技能。

执行计划基础

执行计划的定义与作用

执行计划是Oracle优化器(Optimizer)根据SQL语句、数据库统计信息和系统资源状况生成的最优执行策略,它决定了:

  • 表的访问方式(全表扫描、索引扫描等)
  • 表的连接方式(嵌套循环、哈希连接、排序合并连接等)
  • 表的连接顺序
  • 排序操作的位置和方式
  • 过滤条件的应用时机
  • 并行执行的策略和并行度

执行计划的核心作用

  • 帮助DBA理解SQL语句的执行过程
  • 识别SQL语句中的性能瓶颈
  • 验证SQL优化的效果
  • 确保执行计划的稳定性

优化器类型与工作原理

1. 优化器类型

优化器类型描述适用场景
CBO(Cost-Based Optimizer)基于成本的优化器,根据统计信息计算执行计划的成本,选择成本最低的执行计划所有生产环境,Oracle 10g及以上版本默认使用
RBO(Rule-Based Optimizer)基于规则的优化器,根据固定规则生成执行计划,不考虑数据分布已过时,Oracle 10g起不再推荐使用

2. CBO工作原理

CBO的工作过程包括:

  1. 解析(Parsing):检查SQL语法和语义,生成解析树
  2. 查询转换(Query Transformation):将解析树转换为等价的查询树,包括视图合并、子查询展开等
  3. 计划生成(Plan Generation):生成多个可能的执行计划
  4. 成本计算(Cost Calculation):计算每个执行计划的成本,包括I/O成本、CPU成本和内存成本
  5. 计划选择(Plan Selection):选择成本最低的执行计划

执行计划生成方法

1. EXPLAIN PLAN命令

EXPLAIN PLAN是最常用的生成执行计划的方法,适用于分析未执行的SQL语句。

基本用法

sql
-- 生成执行计划
EXPLAIN PLAN FOR
SELECT /*+ gather_plan_statistics */ * 
FROM employees WHERE department_id = 100;

-- 查看基本执行计划
SELECT * FROM TABLE(dbms_xplan.display());

-- 查看包含谓词和统计信息的执行计划
SELECT * FROM TABLE(dbms_xplan.display(format => 'ALL'));

-- 查看包含成本和投影信息的执行计划
SELECT * FROM TABLE(dbms_xplan.display(format => 'ALL_COSTS +PROJECTION'));

-- 查看包含Outline信息的执行计划
SELECT * FROM TABLE(dbms_xplan.display(format => 'ALL +OUTLINE'));

2. DBMS_XPLAN.DISPLAY_CURSOR

DISPLAY_CURSOR用于查看已经执行过的SQL语句的实际执行计划,包含实际执行统计信息。

基本用法

sql
-- 查找SQL_ID
SELECT sql_id, sql_text, executions, 
       elapsed_time/1000000 avg_elapsed_seconds
FROM v$sql
WHERE sql_text LIKE '%SELECT * FROM employees WHERE department_id =%'
ORDER BY last_active_time DESC;

-- 查看实际执行计划
SELECT * FROM TABLE(dbms_xplan.display_cursor(
    sql_id => '&sql_id',
    cursor_child_no => 0,
    format => 'ALLSTATS LAST +ADAPTIVE +PEEKED_BINDS +OUTLINE +PROJECTION'
));

3. SQL Monitor

SQL Monitor用于实时监控长时间运行的SQL语句,并提供详细的执行计划和统计信息。

基本用法

sql
-- 生成SQL Monitor报告(文本格式)
SELECT dbms_sql_monitor.report_sql_monitor(
    sql_id => '&sql_id',
    type => 'TEXT',
    report_level => 'ALL'
) FROM dual;

-- 生成HTML格式的SQL Monitor报告
SELECT dbms_sql_monitor.report_sql_monitor(
    sql_id => '&sql_id',
    type => 'HTML',
    report_level => 'ALL'
) FROM dual;

-- 在SQL*Plus中生成HTML报告并保存到文件
SET LONG 1000000
SET LONGCHUNKSIZE 1000000
SET PAGESIZE 0
SET LINESIZE 1000
SET TRIMSPOOL ON
SET FEEDBACK OFF
SPOOL sql_monitor_report.html
SELECT dbms_sql_monitor.report_sql_monitor(sql_id => '&sql_id', type => 'HTML') FROM dual;
SPOOL OFF

4. AUTOTRACE命令

AUTOTRACE是SQL*Plus的内置命令,用于在执行SQL语句的同时显示执行计划和统计信息。

基本用法

sql
-- 启用AUTOTRACE,显示执行计划和统计信息
SET autotrace ON;

-- 只显示执行计划,不执行SQL语句
SET autotrace EXPLAIN;

-- 只显示统计信息,不显示执行计划
SET autotrace STAT;

-- 禁用AUTOTRACE
SET autotrace OFF;

-- 执行SQL并查看执行计划和统计信息
SELECT * FROM employees WHERE department_id = 100;

执行计划详细解读

1. 执行计划基本结构

一个典型的执行计划包含以下信息:

Plan hash value: 3956160932

--------------------------------------------------------------------------
| Id  | Operation         | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |           |     6 |   690 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMPLOYEES |     6 |   690 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("DEPARTMENT_ID"=100)

关键字段解读

  • Id:操作的唯一标识符,按执行顺序排列
  • Operation:操作类型(如TABLE ACCESS FULL、INDEX RANGE SCAN等)
  • Name:操作的对象名称(表名或索引名)
  • Rows:估计的返回行数(基数)
  • Bytes:估计的返回字节数
  • Cost (%CPU):操作的成本估算(CPU成本占比)
  • Time:估计的执行时间
  • Predicate Information:谓词信息,包括访问谓词(Access Predicates)和过滤谓词(Filter Predicates)

2. 常见操作类型分析

表访问操作

操作类型描述性能影响优化建议
TABLE ACCESS FULL全表扫描,读取表中的所有行高I/O消耗,性能差考虑添加索引、分区或优化WHERE条件
TABLE ACCESS BY INDEX ROWID通过索引ROWID访问表性能较好检查索引覆盖性,考虑复合索引
TABLE ACCESS BY INDEX ROWID BATCHED批量通过索引ROWID访问表,减少I/O次数性能优于普通ROWID访问适合大量索引行的访问
TABLE ACCESS CLUSTER簇表访问适合经常关联查询的表确保簇表设计合理
TABLE ACCESS HASH哈希表访问适合大表的快速访问确保哈希分布均匀

索引访问操作

操作类型描述适用场景优化建议
INDEX UNIQUE SCAN索引唯一扫描,返回单行主键或唯一索引的精确查询理想情况,无需优化
INDEX RANGE SCAN索引范围扫描,返回多行范围查询(如BETWEEN、>、<等)确保索引选择性高,避免大范围扫描
INDEX FULL SCAN索引全扫描,读取整个索引适合小索引,或需要排序的查询考虑索引覆盖,避免回表
INDEX FAST FULL SCAN索引快速全扫描,使用多块读适合大索引,不需要排序的查询确保索引包含所有需要的列
INDEX SKIP SCAN索引跳跃扫描,适合复合索引前缀列选择性低的情况复合索引前缀列选择性低,后缀列选择性高的查询考虑调整复合索引顺序或添加新索引
INDEX DESCENDING SCAN索引降序扫描ORDER BY DESC查询确保索引顺序与ORDER BY子句匹配

连接操作

操作类型描述适用场景性能特点
NESTED LOOPS外层循环的每一行与内层循环进行匹配小结果集连接,内层表有高效索引低内存消耗,适合OLTP系统
HASH JOIN将较小的表构建哈希表,然后与较大的表进行匹配大结果集连接,无合适索引的情况高CPU消耗,适合数据仓库
SORT MERGE JOIN先对两个表进行排序,然后合并结果有序数据连接,或需要排序的查询高I/O和CPU消耗,适合有序数据
CARTESIAN PRODUCT两个表的所有行进行匹配应避免使用性能极差,会产生大量结果集

3. 执行计划问题识别

常见执行计划问题

  1. 全表扫描代替索引扫描

    • 原因:缺少合适索引、索引失效、统计信息过时
    • 识别:执行计划中出现不必要的TABLE ACCESS FULL
    • 解决:添加合适索引、更新统计信息、优化WHERE子句
  2. 低效的连接方式

    • 原因:表连接顺序不合理、缺少连接索引
    • 识别:大表连接使用NESTED LOOPS,或不必要的SORT MERGE JOIN
    • 解决:优化连接顺序、添加连接索引、调整优化器参数
  3. 不必要的排序操作

    • 原因:ORDER BY/GROUP BY子句缺少对应索引
    • 识别:执行计划中出现不必要的SORT操作
    • 解决:添加排序索引、优化排序字段
  4. 执行计划不稳定

    • 原因:绑定变量窥探、统计信息波动、数据分布不均匀
    • 识别:同一SQL出现不同的plan_hash_value
    • 解决:使用执行计划基线、SQL Profile、调整绑定变量使用方式
  5. 估计行数与实际行数差异大

    • 原因:统计信息过时或不准确
    • 识别:执行计划中的Rows值与实际返回行数差异超过10倍
    • 解决:更新统计信息,考虑使用直方图

Oracle 19c与21c执行计划新特性

Oracle 19c增强特性

  1. 自适应执行计划增强

    • 支持动态调整执行计划
    • 实时收集执行统计信息
    • 自动调整表连接方式和顺序
  2. 执行计划管理增强

    • 自动捕获重复执行的SQL
    • 自动演进执行计划基线
    • 支持并行查询的执行计划管理
  3. SQL Monitor增强

    • 支持实时查看SQL执行进度
    • 提供更详细的操作统计信息
    • 支持ADDM SQL分析
  4. 自动索引

    • 自动识别缺少的索引
    • 自动创建和测试索引
    • 自动监控索引使用情况
    • 自动禁用或删除未使用的索引

Oracle 21c增强特性

  1. 实时执行计划

    • 支持查看SQL执行过程中的实时执行计划
    • 提供更详细的操作级统计信息
    • 支持实时调整执行计划
  2. 机器学习优化

    • 机器学习驱动的执行计划选择
    • 自适应统计信息收集
    • 智能索引建议
    • 预测性执行计划优化
  3. 增强的SQL Profile

    • 支持更复杂的执行计划调整
    • 提供更详细的优化建议
    • 支持自动创建和管理
  4. 执行计划可视化增强

    • 更直观的执行计划图形化展示
    • 支持交互式执行计划分析
    • 提供更详细的性能指标

执行计划优化实战案例

案例1:全表扫描优化

问题现象

sql
SELECT * FROM orders WHERE customer_id = 12345 AND order_date > '2023-01-01';
-- 执行时间:5.2秒

执行计划

--------------------------------------------------------------------------
| Id  | Operation         | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |        |  5000 |  1.5M |   120   (2)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| ORDERS |  5000 |  1.5M |   120   (2)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("CUSTOMER_ID"=12345 AND "ORDER_DATE">TO_DATE('2023-01-01','yyyy-mm-dd'))

分析过程

  1. orders表有100万行数据,customer_id和order_date列单独有索引
  2. WHERE子句同时使用了customer_id和order_date,但没有复合索引
  3. customer_id的选择性高,order_date的选择性中等

优化方案

sql
-- 创建复合索引
CREATE INDEX orders_cust_date_idx ON orders(customer_id, order_date);

优化后执行计划

---------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name                | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                     |  5000 |  1.5M |     8   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| ORDERS              |  5000 |  1.5M |     8   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | ORDERS_CUST_DATE_IDX|  5000 |       |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("CUSTOMER_ID"=12345 AND "ORDER_DATE">TO_DATE('2023-01-01','yyyy-mm-dd'))

优化效果:执行时间从5.2秒降低到0.02秒,成本从120降低到8。

案例2:执行计划不稳定优化

问题现象: 同一SQL在不同时间执行,执行时间从0.1秒到10秒不等。

分析过程

  1. 查看AWR历史,发现有多个不同的plan_hash_value
  2. 分析执行计划:
    • 高效计划:使用INDEX RANGE SCAN,成本10
    • 低效计划:使用TABLE ACCESS FULL,成本1000
  3. 检查统计信息:表统计信息最近更新,但数据分布不均匀
  4. 发现问题:SQL使用了绑定变量,导致绑定变量窥探(Bind Variable Peeking)

优化方案

sql
-- 使用SQL计划基线锁定高效执行计划
EXEC DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(
    sql_id => '&sql_id', 
    plan_hash_value => &good_plan_hash_value
);

-- 或使用SQL Profile
EXEC DBMS_SQLTUNE.CREATE_SQL_PROFILE(
    sql_id => '&sql_id', 
    plan_hash_value => &good_plan_hash_value
);

-- 或禁用绑定变量窥探
ALTER SYSTEM SET optimizer_use_skew_only_hints = TRUE;

优化效果:执行计划稳定,始终使用高效的索引扫描,执行时间稳定在0.1秒左右。

案例3:统计信息过时优化

问题现象

sql
SELECT * FROM large_table WHERE status = 'ACTIVE';
-- 执行时间:8.5秒

执行计划

---------------------------------------------------------------------------
| Id  | Operation         | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |              |  1000 |   200K|    20   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| LARGE_TABLE  |  1000 |   200K|    20   (0)| 00:00:01 |
---------------------------------------------------------------------------

分析过程

  1. 执行计划估计返回1000行,但实际返回100万行
  2. 检查统计信息:最后收集时间为3个月前
  3. 表数据量从100万增长到1000万行,统计信息严重过时

优化方案

sql
-- 收集表和索引统计信息,包括直方图
EXEC DBMS_STATS.GATHER_TABLE_STATS(
    ownname => 'SCHEMA_NAME',
    tabname => 'LARGE_TABLE',
    estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
    method_opt => 'FOR ALL COLUMNS SIZE AUTO',
    degree => 8,
    cascade => TRUE
);

优化后执行计划

sql
-- 优化后,优化器选择使用索引而不是全表扫描
---------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name                | Rows    | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                     |  1000K  |   200M|   500   (1)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| LARGE_TABLE         |  1000K  |   200M|   500   (1)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | LARGE_TABLE_STATUS_IDX|  1000K  |       |   100   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------

优化效果:执行时间从8.5秒降低到0.8秒,虽然成本从20增加到500,但实际执行时间大幅降低,因为优化器现在有了准确的统计信息。

执行计划优化最佳实践

1. 建立执行计划管理体系

  • 执行计划基线管理:为关键SQL建立执行计划基线,确保执行计划稳定
  • 定期审查执行计划:每周审查Top SQL的执行计划,确保没有退化
  • 监控执行计划变化:使用AWR跟踪plan_hash_value变化,及时发现执行计划退化
  • 建立执行计划仓库:保存关键SQL的历史执行计划,便于分析变化趋势

2. 优化器统计信息管理

  • 定期更新统计信息:根据数据变化频率,设置合适的统计信息收集周期
  • 使用直方图:对于数据分布不均匀的列,创建直方图
  • 考虑增量统计信息:对于大分区表,使用增量统计信息减少收集时间
  • 使用动态采样:对于临时表或统计信息缺失的表,启用动态采样

3. 索引设计最佳实践

  • 遵循索引设计原则:选择高选择性列、考虑查询模式
  • 复合索引顺序:将选择性高的列放在前面,匹配查询条件顺序
  • 避免过度索引:索引数量不宜超过表列数的20%
  • 考虑索引覆盖:包含查询所需的所有列,避免回表
  • 定期维护索引:重建碎片化索引,收集统计信息

4. SQL设计最佳实践

  • **避免SELECT ***:只选择需要的列
  • 使用绑定变量:减少硬解析,提高执行计划复用
  • 优化WHERE子句:将高选择性条件放在前面,避免在WHERE子句中使用函数
  • 合理使用JOIN:限制JOIN表数量,优化JOIN顺序
  • 避免使用NOT IN和OR:考虑使用NOT EXISTS或UNION

5. 执行计划分析工具使用

  • 结合多种工具:使用EXPLAIN PLAN、DISPLAY_CURSOR、SQL Monitor等多种工具综合分析
  • 关注实际执行统计:优先查看包含实际执行统计的执行计划
  • 分析谓词信息:重点关注Access Predicates和Filter Predicates
  • 使用SQL Tuning Advisor:获取自动优化建议
  • 利用Oracle Enterprise Manager:使用OEM的SQL优化功能,如SQL Tuning Sets

常见问题(FAQ)

Q1: 如何获取真实的执行计划?

A1: 获取真实执行计划的方法:

  1. 使用/*+ gather_plan_statistics */提示,然后查看TABLE(dbms_xplan.display_cursor(format => 'ALLSTATS LAST'))
  2. 使用SQL Monitor报告,它包含真实的执行统计信息
  3. 对于已执行的SQL,查看v$sql_planv$sql_plan_statistics_all视图

Q2: 执行计划中的Cost值代表什么?

A2: Cost值是优化器根据统计信息计算的相对成本,包括I/O成本、CPU成本和内存成本。Cost值越低,执行计划越高效。但Cost值只是优化器的估计,实际执行时间还受系统资源状况影响。

Q3: 如何判断执行计划是否最优?

A3: 判断执行计划是否最优的方法:

  1. 比较不同执行计划的Cost值,选择Cost最低的计划
  2. 检查是否使用了合适的索引和连接方式
  3. 验证实际执行时间是否符合预期
  4. 确保执行计划稳定,没有频繁变化

Q4: 如何处理执行计划退化?

A4: 处理执行计划退化的方法:

  1. 检查统计信息是否准确,必要时更新统计信息
  2. 检查索引是否失效或被删除
  3. 使用执行计划基线锁定高效执行计划
  4. 使用SQL Profile微调执行计划
  5. 考虑重写SQL语句

Q5: 如何优化复杂SQL的执行计划?

A5: 优化复杂SQL执行计划的方法:

  1. 分解复杂SQL为多个简单SQL
  2. 使用临时表或物化视图
  3. 考虑使用提示(Hints)引导优化器
  4. 使用SQL Tuning Advisor获取优化建议
  5. 考虑重构数据模型

Q6: 如何监控执行计划的稳定性?

A6: 监控执行计划稳定性的方法:

  1. 定期生成AWR报告,比较不同时间段的Top SQL执行计划
  2. 使用dba_hist_sqlstat视图跟踪同一SQL的plan_hash_value变化
  3. 配置OEM的执行计划变化告警
  4. 建立执行计划基线,监控基线的使用情况

Q7: 如何使用提示(Hints)优化执行计划?

A7: 使用提示优化执行计划的建议:

  1. 尽量避免使用提示,优先通过索引和统计信息优化
  2. 仅在必要时使用提示,如执行计划严重退化时
  3. 使用具体的提示,如INDEXHASH_JOINLEADING
  4. 定期审查提示的有效性,避免因数据变化导致提示失效

Q8: Oracle 21c的自动索引功能值得使用吗?

A8: Oracle 21c的自动索引功能对于大多数环境是值得使用的,它可以:

  1. 自动识别缺少的索引
  2. 自动创建和测试索引
  3. 自动监控索引使用情况
  4. 自动禁用或删除未使用的索引

但在使用前需要:

  1. 评估系统资源状况,自动索引会消耗一定的CPU和I/O资源
  2. 监控自动索引的创建和使用情况
  3. 对关键业务SQL,仍建议手动设计索引

总结

执行计划分析是Oracle数据库性能优化的核心技能,需要DBA掌握多种工具和方法,结合实际生产环境进行综合分析。Oracle 19c和21c提供了丰富的执行计划分析工具和新特性,DBA应充分利用这些特性,建立完善的执行计划管理体系。

在实际运维中,DBA应:

  1. 定期监控和分析执行计划
  2. 为关键SQL建立执行计划基线
  3. 优化器统计信息管理
  4. 合理设计和维护索引
  5. 优化SQL语句设计
  6. 关注Oracle新版本的执行计划特性

通过持续的学习和实践,DBA可以不断提高执行计划分析能力,优化数据库性能,为业务提供高效、稳定的数据库服务。