外观
Oracle执行计划分析
执行计划是Oracle数据库优化器为SQL语句生成的执行方案,它描述了Oracle将如何执行SQL语句,包括表访问顺序、访问方法、连接方法等。理解和分析执行计划是Oracle性能优化的核心技能,本文将详细介绍Oracle执行计划的组成、查看方法、解读技巧和优化策略。
执行计划概述
什么是执行计划?
执行计划是Oracle优化器基于SQL语句的结构、表的统计信息、索引信息和系统参数等,生成的一种执行方案。它包含了Oracle执行SQL语句的详细步骤,如:
- 表的访问顺序
- 表的访问方法(全表扫描、索引扫描等)
- 表之间的连接方法(嵌套循环、哈希连接、排序合并连接等)
- 排序操作
- 聚合操作
- 过滤条件
执行计划的重要性
- 帮助理解SQL语句的执行过程
- 识别性能瓶颈
- 指导SQL优化
- 验证优化效果
- 预测SQL语句的性能
优化器类型
Oracle有两种优化器类型:
1. 基于规则的优化器(RBO)
基于一组固定的规则生成执行计划,不考虑表的统计信息和数据分布。RBO已在Oracle 10g中被弃用,不再推荐使用。
2. 基于成本的优化器(CBO)
基于表的统计信息、索引信息和系统参数等,计算不同执行计划的成本,选择成本最低的执行计划。CBO是Oracle当前推荐使用的优化器。
执行计划的组成
1. 操作类型
执行计划中的每个节点代表一个操作,常见的操作类型包括:
| 操作类型 | 描述 |
|---|---|
TABLE ACCESS FULL | 全表扫描 |
TABLE ACCESS BY INDEX ROWID | 通过索引ROWID访问表 |
INDEX FULL SCAN | 全索引扫描 |
INDEX RANGE SCAN | 索引范围扫描 |
INDEX UNIQUE SCAN | 索引唯一扫描 |
INDEX SKIP SCAN | 索引跳跃扫描 |
NESTED LOOPS | 嵌套循环连接 |
HASH JOIN | 哈希连接 |
SORT MERGE JOIN | 排序合并连接 |
SORT | 排序操作 |
AGGREGATE | 聚合操作 |
FILTER | 过滤操作 |
VIEW | 视图操作 |
2. 访问路径
访问路径是Oracle访问表中数据的方法,常见的访问路径包括:
全表扫描(TABLE ACCESS FULL)
Oracle扫描表中的所有行,适合以下情况:
- 表较小
- 查询需要访问表中大部分行
- 没有合适的索引
- 表的统计信息不准确
索引唯一扫描(INDEX UNIQUE SCAN)
Oracle使用唯一索引访问单行数据,适合以下情况:
- 查询条件包含唯一索引的所有列
- 查询条件使用等值比较
索引范围扫描(INDEX RANGE SCAN)
Oracle使用索引访问多行数据,适合以下情况:
- 查询条件包含索引的前导列
- 查询条件使用范围比较(<, >, BETWEEN, LIKE等)
索引全扫描(INDEX FULL SCAN)
Oracle扫描整个索引,但不访问表,适合以下情况:
- 查询只包含索引列
- 索引包含查询所需的所有列(覆盖索引)
索引跳跃扫描(INDEX SKIP SCAN)
Oracle跳过索引的前导列,直接扫描后续列,适合以下情况:
- 索引的前导列基数较低
- 查询条件包含索引的非前导列
3. 连接方法
连接方法是Oracle连接多个表的方法,常见的连接方法包括:
嵌套循环连接(NESTED LOOPS)
Oracle先扫描驱动表,然后对于驱动表中的每一行,扫描连接表。适合以下情况:
- 驱动表较小
- 连接表有合适的索引
- 查询返回少量行
哈希连接(HASH JOIN)
Oracle先对驱动表进行哈希运算,然后扫描连接表,进行哈希匹配。适合以下情况:
- 两个表都较大
- 至少有一个表较小,可以放入内存
- 查询返回大量行
排序合并连接(SORT MERGE JOIN)
Oracle先对两个表进行排序,然后合并排序后的结果。适合以下情况:
- 两个表都需要排序
- 连接条件使用范围比较
- 没有合适的索引
4. 成本和Cardinality
执行计划中的成本(Cost)表示Oracle估计的执行操作所需的资源量,Cardinality表示Oracle估计的返回行数。
- Cost:成本越低,执行效率越高
- Cardinality:Cardinality越接近实际行数,执行计划越准确
如何查看执行计划
1. 使用EXPLAIN PLAN FOR
这是最基本的查看执行计划的方法,适用于所有Oracle版本。
sql
-- 生成执行计划
EXPLAIN PLAN FOR
SELECT e.first_name, e.last_name, e.salary, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.salary > 10000;
-- 查看执行计划
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- 查看更详细的执行计划
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, NULL, 'ALL'));2. 使用DBMS_XPLAN.DISPLAY_CURSOR
这是查看已执行SQL语句执行计划的方法,适用于Oracle 10g及以上版本。
sql
-- 执行SQL语句
SELECT e.first_name, e.last_name, e.salary, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.salary > 10000;
-- 查看最近执行的SQL语句的执行计划
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALL'));
-- 使用SQL_ID和CHILD_NUMBER查看特定SQL语句的执行计划
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('sql_id', child_number, 'ALL'));3. 使用SQL Developer
SQL Developer是Oracle官方提供的免费工具,可以直观地查看执行计划。
步骤:
- 打开SQL Developer
- 连接到数据库
- 编写SQL语句
- 点击"执行计划"按钮(或按F10)
- 查看可视化的执行计划
4. 使用SQL*Plus AUTOTRACE
SQL*Plus的AUTOTRACE功能可以在执行SQL语句的同时显示执行计划。
sql
-- 启用AUTOTRACE
SET AUTOTRACE ON;
-- 执行SQL语句
SELECT e.first_name, e.last_name, e.salary, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.salary > 10000;
-- 只显示执行计划,不显示查询结果
SET AUTOTRACE TRACEONLY;5. 使用V$SQL_PLAN视图
V$SQL_PLAN视图包含了所有已执行SQL语句的执行计划。
sql
-- 查找SQL_ID
SELECT sql_id, sql_text
FROM v$sql
WHERE sql_text LIKE '%SELECT e.first_name, e.last_name%';
-- 查看执行计划
SELECT *
FROM v$sql_plan
WHERE sql_id = 'sql_id'
ORDER BY child_number, id;执行计划的解读
1. 执行计划的结构
执行计划通常以树状结构显示,每个节点代表一个操作,父节点依赖于子节点。执行顺序是从最内层的叶子节点开始,向外层的父节点执行。
示例执行计划:
Plan hash value: 3956160932
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 30 | 2100 | 6 (0)| 00:00:01 |
| 1 | MERGE JOIN | | 30 | 2100 | 6 (0)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 27 | 351 | 2 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | DEPT_ID_PK | 27 | | 1 (0)| 00:00:01 |
|* 4 | SORT JOIN | | 30 | 1050 | 4 (0)| 00:00:01 |
|* 5 | TABLE ACCESS FULL | EMPLOYEES | 30 | 1050 | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
5 - filter("E"."SALARY">10000)2. 解读步骤
- 查看执行计划的ID:ID越大的操作,执行顺序越靠前
- 查看操作类型:了解Oracle使用的访问方法和连接方法
- 查看行数(Rows):了解Oracle估计的返回行数
- 查看成本(Cost):了解Oracle估计的执行成本
- 查看谓词信息(Predicate Information):了解Oracle使用的过滤条件和连接条件
- 查看表访问顺序:了解Oracle访问表的顺序
3. 执行顺序
执行计划的执行顺序是从最内层的叶子节点开始,向外层的父节点执行。在示例执行计划中,执行顺序为:
- 执行ID=3:对DEPT_ID_PK索引进行全索引扫描
- 执行ID=2:通过索引ROWID访问DEPARTMENTS表
- 执行ID=5:对EMPLOYEES表进行全表扫描,过滤SALARY>10000的行
- 执行ID=4:对EMPLOYEES表的结果进行排序
- 执行ID=1:使用排序合并连接连接EMPLOYEES和DEPARTMENTS表
- 执行ID=0:执行SELECT语句,返回结果
常见执行计划问题
1. 全表扫描
问题:Oracle对大表进行全表扫描,导致执行时间过长。
原因:
- 没有合适的索引
- 查询条件不包含索引的前导列
- 索引统计信息不准确
- 表的统计信息不准确
解决方案:
- 添加合适的索引
- 重写SQL,包含索引的前导列
- 更新表和索引的统计信息
- 调整优化器参数
2. 索引失效
问题:Oracle没有使用预期的索引,导致执行效率低下。
原因:
- 查询条件使用了函数或表达式
- 查询条件使用了不等于(!=, <>, NOT)
- 查询条件使用了IS NULL或IS NOT NULL
- 索引的统计信息不准确
- 表的统计信息不准确
解决方案:
- 避免在查询条件中使用函数或表达式
- 重写SQL,使用等值比较
- 添加函数索引
- 更新表和索引的统计信息
3. 嵌套循环连接性能问题
问题:Oracle使用嵌套循环连接连接两个大表,导致执行时间过长。
原因:
- 驱动表太大
- 连接表没有合适的索引
- 优化器选择了错误的驱动表
解决方案:
- 添加合适的索引
- 强制使用哈希连接或排序合并连接
- 重写SQL,调整表的连接顺序
- 更新表的统计信息
4. 排序操作
问题:Oracle执行了大量的排序操作,导致执行时间过长。
原因:
- 查询使用了ORDER BY或GROUP BY
- 查询使用了DISTINCT
- 查询使用了UNION或INTERSECT
- 连接方法使用了排序合并连接
解决方案:
- 添加合适的索引,避免排序
- 重写SQL,减少排序操作
- 使用并行查询
- 调整SORT_AREA_SIZE参数
如何优化执行计划
1. 添加合适的索引
索引是优化执行计划的最有效方法之一,添加合适的索引可以显著提高查询性能。
sql
-- 添加索引
CREATE INDEX emp_salary_dept_idx ON employees(salary, department_id);2. 更新统计信息
统计信息是Oracle优化器生成执行计划的重要依据,不准确的统计信息会导致优化器生成糟糕的执行计划。
sql
-- 更新表的统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES', CASCADE => TRUE);
-- 更新索引的统计信息
EXEC DBMS_STATS.GATHER_INDEX_STATS('HR', 'EMP_SALARY_DEPT_IDX');
-- 更新模式的统计信息
EXEC DBMS_STATS.GATHER_SCHEMA_STATS('HR', CASCADE => TRUE);3. 重写SQL语句
重写SQL语句可以改变优化器的执行计划,提高查询性能。
避免在查询条件中使用函数
sql
-- 不好的写法:在查询条件中使用函数
SELECT * FROM employees WHERE TO_CHAR(hire_date, 'YYYY') = '2023';
-- 好的写法:避免使用函数
SELECT * FROM employees WHERE hire_date BETWEEN TO_DATE('2023-01-01', 'YYYY-MM-DD') AND TO_DATE('2023-12-31', 'YYYY-MM-DD');使用绑定变量
sql
-- 不好的写法:使用常量
SELECT * FROM employees WHERE department_id = 60;
-- 好的写法:使用绑定变量
SELECT * FROM employees WHERE department_id = :dept_id;避免使用SELECT *
sql
-- 不好的写法:使用SELECT *
SELECT * FROM employees WHERE department_id = 60;
-- 好的写法:只选择需要的列
SELECT first_name, last_name, salary FROM employees WHERE department_id = 60;4. 调整优化器参数
调整优化器参数可以改变优化器的行为,生成更优的执行计划。
sql
-- 设置优化器模式
ALTER SESSION SET optimizer_mode = ALL_ROWS;
-- 设置直方图收集
ALTER TABLE employees MODIFY (salary) INVISIBLE;
EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES', METHOD_OPT => 'FOR COLUMNS salary SIZE 254');5. 使用并行查询
对于大型查询,可以使用并行查询提高执行效率。
sql
-- 启用并行查询
SELECT /*+ PARALLEL(4) */ * FROM employees WHERE department_id = 60;
-- 设置表的并行度
ALTER TABLE employees PARALLEL 4;执行计划的高级分析
1. 查看实际执行计划
使用DBMS_XPLAN.DISPLAY_CURSOR可以查看SQL语句的实际执行计划,包括实际返回行数、实际执行时间等。
sql
-- 查看实际执行计划
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST'));2. 比较执行计划
使用DBMS_XPLAN.DISPLAY_CURSOR可以比较不同SQL语句的执行计划,或同一SQL语句的不同执行计划。
sql
-- 比较执行计划
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('sql_id1', child_number1, 'ALL'));
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('sql_id2', child_number2, 'ALL'));3. 查看执行计划的历史记录
使用AWR报告可以查看SQL语句的执行计划历史记录,了解执行计划的变化。
sql
-- 生成AWR报告
@$ORACLE_HOME/rdbms/admin/awrrpt.sql版本差异
Oracle 12c
- 引入了自适应执行计划
- 增强了DBMS_XPLAN功能
- 支持实时执行计划监控
Oracle 18c
- 增强了自适应执行计划的功能
- 引入了SQL计划管理
- 支持自动执行计划捕获
Oracle 19c
- 长期支持版本
- 增强了SQL计划管理的功能
- 支持自动执行计划演进
Oracle 21c
- 支持执行计划的AI优化
- 增强了自适应执行计划的功能
- 支持执行计划的云原生监控
常见问题(FAQ)
Q: 如何确定执行计划的执行顺序?
A: 执行计划的执行顺序是从最内层的叶子节点开始,向外层的父节点执行。ID越大的操作,执行顺序越靠前。
Q: 为什么Oracle选择了全表扫描而不是索引扫描?
A: 可能的原因包括:
- 表较小,全表扫描比索引扫描更高效
- 查询需要访问表中大部分行
- 没有合适的索引
- 表的统计信息不准确
- 索引的选择性较低
Q: 如何查看SQL语句的实际执行时间?
A: 可以使用以下方法:
- 使用SQL*Plus的SET TIMING ON命令
- 使用DBMS_XPLAN.DISPLAY_CURSOR查看实际执行时间
- 使用AWR报告查看SQL语句的执行时间
Q: 如何优化嵌套循环连接?
A: 可以通过以下方法优化:
- 确保连接表有合适的索引
- 选择较小的表作为驱动表
- 考虑使用哈希连接或排序合并连接
- 更新表的统计信息
Q: 如何查看优化器生成执行计划的过程?
A: 可以使用10053事件查看优化器生成执行计划的过程。
sql
-- 启用10053事件
ALTER SESSION SET EVENTS '10053 trace name context forever, level 1';
-- 执行SQL语句
SELECT * FROM employees WHERE department_id = 60;
-- 禁用10053事件
ALTER SESSION SET EVENTS '10053 trace name context off';Q: 如何确保执行计划的稳定性?
A: 可以通过以下方法确保执行计划的稳定性:
- 使用SQL计划基线
- 使用存储大纲
- 使用绑定变量
- 定期更新统计信息
- 避免在查询条件中使用函数
总结
执行计划是Oracle性能优化的核心,理解和分析执行计划可以帮助开发人员和DBA识别性能瓶颈,优化SQL语句,提高数据库性能。本文介绍了执行计划的组成、查看方法、解读技巧和优化策略,包括常见的访问路径、连接方法、执行计划问题和解决方案。
在实际应用中,应结合实际情况选择合适的优化方法,如添加索引、更新统计信息、重写SQL语句、调整优化器参数等。同时,应定期监控执行计划的变化,确保SQL语句始终使用最优的执行计划。
随着Oracle版本的更新,执行计划的功能不断增强,如自适应执行计划、SQL计划管理、AI优化等,开发人员和DBA应关注新版本的特性,提高性能优化的效率和效果。
