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 性能,降低系统资源消耗。

相关推荐
云小逸17 小时前
【nmap源码学习】 Nmap网络扫描工具深度解析:从基础参数到核心扫描逻辑
网络·数据库·学习
此刻你18 小时前
常用的 SQL 语句
数据库·sql·oracle
盐焗西兰花19 小时前
鸿蒙学习实战之路-Reader Kit构建阅读器最佳实践
学习·华为·harmonyos
深蓝海拓19 小时前
PySide6从0开始学习的笔记(二十七) 日志管理
笔记·python·学习·pyqt
慎独41320 小时前
科学赋能,让孩子专注高效爱上学习
学习
LGL6030A20 小时前
Java学习历程26——线程安全
java·开发语言·学习
山岚的运维笔记20 小时前
SQL Server笔记 -- 第14章:CASE语句
数据库·笔记·sql·microsoft·sqlserver
学历真的很重要20 小时前
【系统架构师】第二章 操作系统知识 - 第二部分:进程管理(详解版)
学习·职场和发展·系统架构·系统架构师
Nebula_g20 小时前
线程进阶: 无人机自动防空平台开发教程(更新)
java·开发语言·数据结构·学习·算法·无人机
星期五不见面21 小时前
机器人学习!(二)ROS2-节点(7)2026/02/03
学习