SQL 优化是通过分析 SQL 执行逻辑、执行计划和数据库资源使用,调整 SQL 写法、索引结构、数据库参数和表设计,以最小的系统资源消耗获得最快的执行速度的系统性工程。它不是简单的 "加索引",而是从 SQL 解析、执行到数据返回的全链路优化,是解决 80% 以上数据库性能问题的核心手段。
1、SQL 优化基础
1.1、SQL 执行的完整流程
执行一条 SQL 需要经过 5 个阶段,每个阶段都可能成为性能瓶颈:
SQL 输入 → 解析阶段 → 优化阶段 → 执行阶段 → 数据返回
- 解析阶段 :检查 SQL 语法、语义和权限,生成解析树
- 硬解析:第一次执行 SQL,生成新的执行计划(消耗大量 CPU 和内存)
- 软解析:SQL 已在共享池中存在,直接复用执行计划(性能最优)
- 优化阶段:优化器根据统计信息生成最优执行计划
- 执行阶段:按照执行计划访问数据
- 数据返回:将结果集返回给客户端
核心原则:SQL 优化的本质是减少数据访问量和减少数据处理量。
1.2、硬解析 vs 软解析
| 对比维度 | 硬解析 | 软解析 |
|---|---|---|
| 执行计划生成 | 重新生成 | 直接复用 |
| CPU 消耗 | 极高 | 极低 |
| 共享池锁竞争 | 严重 | 无 |
| 适用场景 | 第一次执行 | 重复执行 |
优化目标:软解析率 > 99%。
1.3、执行计划:SQL 优化的核心
执行计划是 Oracle 优化器生成的 SQL 执行步骤,是 SQL 优化的唯一依据。
查看执行计划的方法:
sql
-- 方法1:EXPLAIN PLAN(查看预估执行计划)
EXPLAIN PLAN FOR SELECT * FROM emp WHERE empno=7369;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY());
-- 方法2:DBMS_XPLAN.DISPLAY_CURSOR(查看真实执行计划,最准确)
SELECT /*+ GATHER_PLAN_STATISTICS */ * FROM emp WHERE empno=7369;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(FORMAT=>'ALLSTATS LAST'));
-- 方法3:SQL*Plus AUTOTRACE
SET AUTOTRACE ON;
SELECT * FROM emp WHERE empno=7369;
执行计划关键信息解读:
- Id:执行步骤编号,从 0 开始,数字越小越先执行
- Operation:执行操作(如全表扫描、索引扫描、连接)
- Name:操作涉及的表或索引名称
- Rows:优化器预估的返回行数(基数)
- Bytes:预估的返回字节数
- Cost:优化器计算的执行成本(值越小越好)
- Time:预估的执行时间
2、SQL 优化方法论
2.1、第一步:定位 Top SQL
先从系统整体入手,找出消耗资源最多的 SQL:
sql
-- 从AWR中获取历史Top SQL(按CPU消耗排序)
SELECT
sql_id,
sql_text,
cpu_time/1000000 AS cpu_sec,
elapsed_time/1000000 AS elapsed_sec,
executions,
buffer_gets
FROM dba_hist_sqlstat
WHERE snap_id BETWEEN :start_snap AND :end_snap
ORDER BY cpu_time DESC;
-- 从ASH中获取当前正在执行的慢SQL
SELECT
sql_id,
sql_text,
session_id,
wait_event,
seconds_in_wait
FROM v$active_session_history
WHERE sample_time > SYSDATE-1/24
AND session_state='ON CPU'
ORDER BY seconds_in_wait DESC;
2.2、第二步:分析执行计划
拿到 SQL 后,第一步就是查看真实执行计划,重点关注:
- 全表扫描(TABLE ACCESS FULL):大表全表扫描通常是性能问题的根源
- 索引扫描类型:优先使用 INDEX UNIQUE SCAN,其次 INDEX RANGE SCAN,避免 INDEX FULL SCAN
- 连接方式:嵌套循环适合小表驱动大表,哈希连接适合大表连接
- 基数预估:如果预估行数和实际行数相差 10 倍以上,说明统计信息不准确
- 排序操作:避免不必要的排序(如 ORDER BY、GROUP BY、DISTINCT)
2.3、第三步:实施优化措施
根据执行计划分析结果,选择合适的优化手段:
- 索引优化:创建合适的索引,删除无用索引
- SQL 改写:优化 SQL 写法,避免低效语法
- 统计信息更新:确保表和索引的统计信息准确
- 参数调整:调整优化器相关参数
- 表结构优化:分区表、表压缩、数据类型优化
2.4、第四步:验证优化效果
优化后必须验证效果,对比优化前后的性能指标:
- 执行时间
- CPU 消耗
- 逻辑读(buffer gets)
- 物理读(physical reads)
- 执行计划是否符合预期
实例:某公司 ERP 慢查询优化
**S(Situation - 情境):**某公司核心 ERP 系统的生产订单查询功能突然变慢,原来 1 秒内返回的查询现在需要 30 秒以上,导致生产线调度系统无法正常工作。
**T(Task - 任务):**在 30 分钟内解决 SQL 性能问题,恢复系统正常运行,确保生产线不受影响。
A(Action - 行动):
1、定位 Top SQL,使用 ASH 找到消耗 CPU 最多的 SQL;
2、分析执行计划:查看执行计划发现,SQL 正在对 production_orders 表(5000 万行)进行全表扫描,成本高达 120 万。
3、查找问题根源:
order_date 和 status 列没有复合索引;
表的统计信息已经 3 个月没有更新;
优化器错误地选择了全表扫描;
4、实施优化:
sql
-- 1. 创建复合索引(order_date在前,status在后)
CREATE INDEX idx_prod_orders_date_status ON production_orders(order_date, status) ONLINE;
-- 2. 收集表的统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS('ERP', 'PRODUCTION_ORDERS', cascade=>TRUE);
5、验证效果:再次执行 SQL,执行时间从 30 秒缩短到 0.2 秒,数据库 CPU 使用率下降到 30%。
**R(Result - 结果):**SQL 执行时间从 30 秒缩短到 0.2 秒,性能大幅提升;数据库 CPU 使用率恢复正常;生产线调度系统恢复正常运行,没有影响生产进度;落地改进:建立 SQL 性能基线,定期检查和更新统计信息。