Oracle 19c入门学习教程,从入门到精通,Oracle优化SQL语句 — 语法知识点与使用方法详解(16)

Oracle优化SQL语句 --- 语法知识点与使用方法详解


一、环境准备(Oracle数据库安装简要说明)

注意:Oracle数据库安装较为复杂,以下为简化版安装流程(以 Oracle Database 21c Express Edition 为例)。

1. 下载 Oracle Database 21c XE

2. 安装步骤(以 Windows 为例)

  1. 以管理员身份运行安装程序 OracleXE21c_Win64.exe
  2. 设置 SYS / SYSTEM 用户密码(记住该密码)
  3. 选择安装路径(默认即可)
  4. 安装完成后,服务自动启动:
    • OracleServiceXE
    • OracleXETNSListener

3. 验证安装

bash 复制代码
# 打开命令行
sqlplus sys/your_password@//localhost:1521/XE as sysdba

若成功进入 SQL> 提示符,说明安装成功。

4. 创建测试用户(可选)

sql 复制代码
-- 以 sys 用户登录后执行
CREATE USER test_user IDENTIFIED BY test123;
GRANT CONNECT, RESOURCE, DBA TO test_user;

二、常规SQL语句优化

1. 不用"*"代替所有列名

✅ 原理:
  • SELECT * 会读取所有列,增加 I/O 和网络传输开销。
  • 明确指定列可利用覆盖索引,提升性能。
📌 案例代码:
sql 复制代码
-- ❌ 不推荐
SELECT * FROM employees;

-- ✅ 推荐:只查询需要的字段
SELECT employee_id, first_name, last_name, salary
FROM employees;

2. 用 TRUNCATE 代替 DELETE(清空整表时)

✅ 原理:
  • DELETE 是 DML,逐行删除并写日志,可回滚,但慢。
  • TRUNCATE 是 DDL,直接释放数据段,不可回滚,速度快,不触发触发器。
⚠️ 注意:
  • TRUNCATE 不能带 WHERE 条件。
  • 需要 DROP ANY TABLE 权限(通常 DBA 或表所有者)。
📌 案例代码:
sql 复制代码
-- 删除所有员工记录(可回滚,慢)
DELETE FROM employees_temp;

-- 清空整个表(不可回滚,快)
TRUNCATE TABLE employees_temp;

3. 在确保完整性的情况下多用 COMMIT

✅ 原理:
  • 频繁 COMMIT 可释放 UNDO 段,减少锁争用,避免 ORA-1555 错误。
  • 但不要过度提交(影响事务一致性)。
📌 案例代码(批量插入 + 分批提交):
sql 复制代码
DECLARE
  i NUMBER := 0;
BEGIN
  FOR rec IN (SELECT level AS id, 'User_' || level AS name FROM dual CONNECT BY level <= 10000)
  LOOP
    INSERT INTO users (user_id, user_name) VALUES (rec.id, rec.name);
    i := i + 1;
    
    -- 每 1000 行提交一次
    IF MOD(i, 1000) = 0 THEN
      COMMIT;
      DBMS_OUTPUT.PUT_LINE('Committed ' || i || ' records');
    END IF;
  END LOOP;
  COMMIT; -- 提交剩余记录
END;
/

4. 尽量减少表的查询次数

✅ 原理:
  • 多次访问同一表会增加逻辑读和物理读。
  • 使用子查询、连接或分析函数合并操作。
📌 案例代码:
sql 复制代码
-- ❌ 两次查询 employees 表
SELECT COUNT(*) FROM employees WHERE department_id = 10;
SELECT AVG(salary) FROM employees WHERE department_id = 10;

-- ✅ 一次查询完成
SELECT COUNT(*), AVG(salary)
FROM employees
WHERE department_id = 10;

5. 用 [NOT] EXISTS 代替 [NOT] IN

✅ 原理:
  • NOT IN 在子查询含 NULL 时返回空结果(逻辑陷阱)。
  • EXISTS 使用半连接(semi-join),效率更高。
📌 案例代码:
sql 复制代码
-- ❌ NOT IN:若子查询有 NULL,结果为空!
SELECT employee_id
FROM employees
WHERE department_id NOT IN (
  SELECT department_id
  FROM departments
  WHERE location_id = 1700
);

-- ✅ 使用 NOT EXISTS(安全且高效)
SELECT e.employee_id
FROM employees e
WHERE NOT EXISTS (
  SELECT 1
  FROM departments d
  WHERE d.department_id = e.department_id
    AND d.location_id = 1700
);

三、表连接优化

1. 驱动表的选择

✅ 原理:
  • 驱动表(Driving Table)是嵌套循环连接中先被扫描的表。
  • 应选择结果集小的表作为驱动表。
📌 案例(使用 /*+ LEADING */ 提示):
sql 复制代码
-- 假设 departments 表小,employees 表大
SELECT /*+ LEADING(d) */ e.first_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE d.location_id = 1700;

2. WHERE 子句的连接顺序

✅ 原理:
  • 虽然 CBO(Cost-Based Optimizer)会重排,但清晰的写法有助于阅读和调试。
  • 先写连接条件,再写过滤条件。
📌 案例:
sql 复制代码
-- 推荐写法
SELECT e.first_name, j.job_title
FROM employees e
JOIN jobs j ON e.job_id = j.job_id
WHERE e.salary > 5000
  AND j.job_title LIKE '%Manager%';

四、合理使用索引

1. 何时使用索引?

  • 列出现在 WHEREJOINORDER BYGROUP BY 中。
  • 高选择性列(如主键、唯一ID)。
  • 避免在低选择性列(如性别)上建单列索引。

2. 索引列和表达式的选择

❌ 错误:对列使用函数 → 索引失效
sql 复制代码
-- 索引无法使用
SELECT * FROM employees WHERE UPPER(last_name) = 'KING';
✅ 正确:使用函数索引 或 改写条件
sql 复制代码
-- 方式1:创建函数索引
CREATE INDEX idx_emp_upper_lastname ON employees(UPPER(last_name));

-- 方式2:避免函数(如果业务允许)
SELECT * FROM employees WHERE last_name = 'King'; -- 假设数据规范

3. 选择复合索引主列

✅ 原理:
  • 复合索引 (col1, col2, col3) 只在 WHERE 包含 col1 时有效(最左前缀原则)。
📌 案例:
sql 复制代码
-- 创建复合索引
CREATE INDEX idx_emp_dept_sal ON employees(department_id, salary);

-- ✅ 能用索引
SELECT * FROM employees WHERE department_id = 10 AND salary > 5000;

-- ✅ 能用索引(只用到第一列)
SELECT * FROM employees WHERE department_id = 10;

-- ❌ 无法使用索引
SELECT * FROM employees WHERE salary > 5000;

4. 避免全表扫描大表

  • 对大表(>百万行)的查询必须有索引支持。
  • 使用 EXPLAIN PLAN 验证是否走索引。

5. 监视索引是否被使用

sql 复制代码
-- 开启索引监控(11g 后已弃用,改用 AWR 或 V$SEGMENT_STATISTICS)
-- 替代方案:查询执行计划或使用 SQL Monitor

-- 查看索引统计信息
SELECT index_name, table_name, num_rows, last_analyzed
FROM user_indexes
WHERE table_name = 'EMPLOYEES';

五、优化器的使用

1. 运行 EXPLAIN PLAN

📌 案例:
sql 复制代码
-- 生成执行计划
EXPLAIN PLAN FOR
SELECT e.first_name, 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);
输出解读关键字段:
  • OPERATION: 访问方式(TABLE ACCESS FULL / INDEX RANGE SCAN)
  • COST: 估算代价
  • ROWS: 估算返回行数

2. Oracle 中 SQL 执行计划管理(SPM)

适用于防止执行计划突变(如统计信息更新后变差)

sql 复制代码
-- 加载 SQL 到 SPM Baseline
DECLARE
  l_plans_loaded PLS_INTEGER;
BEGIN
  l_plans_loaded := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(
    sql_id => 'abc123xyz'  -- 从 V$SQL 获取
  );
END;
/

六、数据库和 SQL 重演

1. 数据库重演(Database Replay)

捕获生产负载,在测试环境重放(需 Enterprise Edition)

2. SQL 重演(SQL Performance Analyzer, SPA)

比较变更前后 SQL 性能(如升级、参数调整)

sql 复制代码
-- 创建 SPA 任务(示例)
DECLARE
  l_task_name VARCHAR2(30);
BEGIN
  l_task_name := DBMS_SQLPA.CREATE_ANALYSIS_TASK(
    sqlset_name => 'my_sqlset',
    task_name   => 'spa_task_01'
  );
  
  DBMS_SQLPA.EXECUTE_ANALYSIS_TASK(
    task_name => l_task_name,
    execution_type => 'TEST EXECUTE'
  );
END;
/

七、Oracle 性能顾问

1. SQL 调优顾问(SQL Tuning Advisor)

sql 复制代码
-- 创建调优任务
DECLARE
  l_task_name VARCHAR2(30);
BEGIN
  l_task_name := DBMS_SQLTUNE.CREATE_TUNING_TASK(
    sql_text => 'SELECT * FROM employees WHERE salary > 10000',
    task_name => 'tune_emp_salary'
  );
  
  DBMS_SQLTUNE.EXECUTE_TUNING_TASK(task_name => 'tune_emp_salary');
END;
/

-- 查看建议
SELECT DBMS_SQLTUNE.REPORT_TUNING_TASK('tune_emp_salary') FROM dual;

2. SQL 访问顾问(SQL Access Advisor)

建议创建索引、物化视图等

sql 复制代码
DECLARE
  l_task_name VARCHAR2(30);
  l_wkld_name VARCHAR2(30);
BEGIN
  -- 创建工作负载(可来自 SQL Cache)
  l_wkld_name := DBMS_ADVISOR.CREATE_FILE(
    directory => 'DATA_PUMP_DIR',
    filename  => 'workload.txt'
  );

  l_task_name := DBMS_ADVISOR.CREATE_TASK('SQL Access Advisor', 'access_task_01');
  
  DBMS_ADVISOR.ADD_STS_REF(l_task_name, 'MY_WORKLOAD_STS');
  DBMS_ADVISOR.EXECUTE_TASK(l_task_name);
END;
/

八、综合性案例

场景:优化一个慢查询报表

原始 SQL(性能差):

sql 复制代码
SELECT *
FROM employees e,
     departments d,
     locations l
WHERE e.department_id = d.department_id(+)
  AND d.location_id = l.location_id(+)
  AND e.salary > (SELECT AVG(salary) FROM employees)
  AND UPPER(e.last_name) LIKE '%A%';

优化步骤:

步骤1:避免 SELECT * 和外连接写法
sql 复制代码
-- 改为 ANSI JOIN,明确字段
SELECT e.employee_id, e.first_name, e.last_name, e.salary,
       d.department_name, l.city
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
LEFT JOIN locations l ON d.location_id = l.location_id
WHERE e.salary > (SELECT AVG(salary) FROM employees)
  AND UPPER(e.last_name) LIKE '%A%';
步骤2:创建函数索引
sql 复制代码
CREATE INDEX idx_emp_upper_lastname ON employees(UPPER(last_name));
步骤3:物化子查询(避免重复计算)
sql 复制代码
WITH avg_sal AS (SELECT AVG(salary) AS avg_salary FROM employees)
SELECT e.employee_id, e.first_name, e.last_name, e.salary,
       d.department_name, l.city
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
LEFT JOIN locations l ON d.location_id = l.location_id
CROSS JOIN avg_sal
WHERE e.salary > avg_sal.avg_salary
  AND UPPER(e.last_name) LIKE '%A%';
步骤4:验证执行计划
sql 复制代码
EXPLAIN PLAN FOR
-- 上述 WITH 查询

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

预期结果:使用 INDEX RANGE SCAN on idx_emp_upper_lastname,子查询只执行一次。


总结

优化点 关键操作
列选择 避免 *,只选必要字段
删除数据 大量清空用 TRUNCATE
事务控制 批量操作分批 COMMIT
子查询 EXISTS 替代 IN
索引 高选择性列、复合索引最左前缀、避免函数
执行计划 EXPLAIN PLAN 验证
自动优化 使用 SQL Tuning Advisor

通过以上方法,可显著提升 Oracle SQL 性能,降低系统资源消耗。

相关推荐
calvinpaean2 小时前
Metric3D Towards Zero-shot Metric 3D Prediction from A Single Image 论文学习
学习·3d
Engineer邓祥浩2 小时前
设计模式学习(22) 23-20 解释器模式
学习·设计模式·解释器模式
zpedu2 小时前
什么是CISA证书?有啥作用
学习
ooope2 小时前
求资源网站?涵盖影视、学习、软件等多领域的资源?
学习
2601_949720262 小时前
flutter_for_openharmony手语学习app实战+个人中心实现
学习·flutter
冰语竹2 小时前
Android学习-随笔(安装后设置路径)
android·学习
芯思路2 小时前
STM32开发学习笔记之七【LCD显示图片】
笔记·stm32·学习
问道飞鱼2 小时前
【大模型学习】提示词工程(Prompt Engineering)技术深度报告
学习·prompt·提示词
hssfscv2 小时前
Javaweb学习笔记——后端实战7 springAOP
笔记·后端·学习