Skip to content

Oracle SQL 书写规范

SQL 书写规范概述

良好的 SQL 书写规范不仅能够提高代码的可读性和可维护性,还能够提高查询性能,减少系统资源消耗。在 Oracle 数据库中,SQL 语句的编写方式直接影响优化器生成的执行计划,进而影响查询性能。因此,制定和遵循 SQL 书写规范对于 DBA 和开发人员来说至关重要。

基础书写规范

1. 命名规范

  • 表名和列名:使用有意义的名称,避免使用缩写和简写
  • 统一命名风格:选择一种命名风格并保持一致,推荐使用下划线分隔的小写命名(snake_case)
  • 避免保留字:不要使用 Oracle 保留字作为表名、列名或其他对象名
  • 长度限制:表名和列名长度不应超过 30 个字符

示例

sql
-- 推荐
SELECT employee_id, first_name, last_name, hire_date FROM employees WHERE department_id = 10;

-- 不推荐
SELECT empid, fname, lname, hdate FROM emp WHERE deptid = 10;

2. 格式规范

  • 大写关键字:SQL 关键字(如 SELECT、FROM、WHERE 等)使用大写
  • 缩进:使用 4 个空格进行缩进,提高代码可读性
  • 换行:每个主要子句(SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY)单独一行
  • 逗号位置:将逗号放在列名之前,便于添加、删除或注释列
  • 条件格式:每个条件单独一行,使用适当的缩进

示例

sql
-- 推荐
SELECT
    e.employee_id
    ,e.first_name
    ,e.last_name
    ,d.department_name
    ,j.job_title
FROM
    employees e
    JOIN departments d ON e.department_id = d.department_id
    JOIN jobs j ON e.job_id = j.job_id
WHERE
    e.hire_date > TO_DATE('2020-01-01', 'YYYY-MM-DD')
    AND e.salary > 5000
    AND d.location_id = 1700
ORDER BY
    e.last_name
    ,e.first_name;

-- 不推荐
select e.employee_id,e.first_name,e.last_name,d.department_name,j.job_title from employees e join departments d on e.department_id=d.department_id join jobs j on e.job_id=j.job_id where e.hire_date>to_date('2020-01-01','YYYY-MM-DD') and e.salary>5000 and d.location_id=1700 order by e.last_name,e.first_name;

3. 注释规范

  • 单行注释:使用 -- 注释单行
  • 多行注释:使用 /* */ 注释多行或复杂逻辑
  • 注释内容:注释应清晰、简洁,说明 SQL 的目的和逻辑
  • 避免过度注释:只注释复杂或难以理解的代码,不要注释显而易见的内容

示例

sql
-- 查询 2020 年以后入职的员工信息
SELECT
    employee_id
    ,first_name
    ,last_name
    ,hire_date
FROM
    employees
WHERE
    hire_date > TO_DATE('2020-01-01', 'YYYY-MM-DD');

/*
 * 复杂查询示例:
 * 查询部门平均工资超过 8000 的部门
 * 并按平均工资降序排列
 */
SELECT
    d.department_id
    ,d.department_name
    ,ROUND(AVG(e.salary), 2) AS avg_salary
FROM
    employees e
    JOIN departments d ON e.department_id = d.department_id
GROUP BY
    d.department_id
    ,d.department_name
HAVING
    AVG(e.salary) > 8000
ORDER BY
    avg_salary DESC;

性能优化规范

1. 避免全表扫描

  • 使用索引:为查询条件中的列创建适当的索引
  • 避免使用函数:不要在 WHERE 子句的索引列上使用函数
  • 避免 IS NULL/IS NOT NULL:在索引列上使用 IS NULL/IS NOT NULL 可能导致全表扫描
  • 合理使用 LIKE:避免使用 LIKE '%value%',这会导致全表扫描

示例

sql
-- 推荐:使用索引列,避免函数
SELECT * FROM employees WHERE hire_date > TO_DATE('2020-01-01', 'YYYY-MM-DD');

-- 不推荐:在索引列上使用函数
SELECT * FROM employees WHERE TRUNC(hire_date) > TO_DATE('2020-01-01', 'YYYY-MM-DD');

-- 推荐:使用 LIKE 'value%',可以使用索引
SELECT * FROM employees WHERE last_name LIKE 'S%';

-- 不推荐:使用 LIKE '%value%',会导致全表扫描
SELECT * FROM employees WHERE last_name LIKE '%mith%';

2. 优化 JOIN 操作

  • 使用明确的 JOIN 语法:使用 ANSI JOIN 语法,避免使用隐式 JOIN
  • JOIN 顺序:将返回结果集较小的表放在前面
  • 避免笛卡尔积:确保所有 JOIN 都有适当的连接条件
  • 使用适当的索引:为连接列创建索引

示例

sql
-- 推荐:使用 ANSI JOIN 语法
SELECT
    e.first_name
    ,e.last_name
    ,d.department_name
FROM
    employees e
    JOIN departments d ON e.department_id = d.department_id;

-- 不推荐:使用隐式 JOIN
SELECT
    e.first_name
    ,e.last_name
    ,d.department_name
FROM
    employees e
    ,departments d
WHERE
    e.department_id = d.department_id;

3. 优化子查询

  • 使用 EXISTS 代替 IN:对于大表,EXISTS 通常比 IN 更高效
  • 使用 JOIN 代替子查询:在很多情况下,JOIN 比子查询更高效
  • 避免相关子查询:相关子查询会逐行执行,性能较差
  • 使用 WITH 子句:对于复杂查询,使用 WITH 子句提高可读性和性能

示例

sql
-- 推荐:使用 EXISTS
SELECT * FROM employees e WHERE EXISTS (SELECT 1 FROM departments d WHERE e.department_id = d.department_id AND d.location_id = 1700);

-- 不推荐:使用 IN
SELECT * FROM employees e WHERE e.department_id IN (SELECT department_id FROM departments WHERE location_id = 1700);

-- 推荐:使用 WITH 子句
WITH dept_ids AS (
    SELECT department_id FROM departments WHERE location_id = 1700
)
SELECT * FROM employees e WHERE e.department_id IN (SELECT department_id FROM dept_ids);

4. 优化聚合操作

  • 使用适当的 GROUP BY:只在必要的列上使用 GROUP BY
  • 避免 HAVING 子句:尽可能使用 WHERE 子句代替 HAVING 子句
  • 使用 ROLLUP/CUBE:对于需要多级汇总的查询,使用 ROLLUP 或 CUBE
  • 使用窗口函数:对于复杂的分析查询,使用窗口函数提高性能

示例

sql
-- 推荐:使用 WHERE 子句过滤数据
SELECT department_id, AVG(salary) AS avg_salary FROM employees WHERE salary > 5000 GROUP BY department_id;

-- 不推荐:使用 HAVING 子句过滤数据
SELECT department_id, AVG(salary) AS avg_salary FROM employees GROUP BY department_id HAVING AVG(salary) > 5000;

-- 使用窗口函数计算累计和
SELECT
    employee_id
    ,first_name
    ,salary
    ,SUM(salary) OVER (ORDER BY employee_id) AS cumulative_salary
FROM
    employees;

5. 优化排序操作

  • 避免不必要的排序:只在必要时使用 ORDER BY
  • 使用索引排序:确保 ORDER BY 子句中的列与索引顺序一致
  • 限制排序数据量:使用 ROWNUM 或 FETCH FIRST 限制排序数据量
  • 优化 UNION 操作:如果不需要去重,使用 UNION ALL 代替 UNION

示例

sql
-- 推荐:使用索引排序
SELECT employee_id, first_name, last_name FROM employees ORDER BY employee_id;

-- 不推荐:不使用索引排序
SELECT employee_id, first_name, last_name FROM employees ORDER BY hire_date;

-- 推荐:使用 FETCH FIRST 限制排序数据量
SELECT employee_id, first_name, last_name FROM employees ORDER BY salary DESC FETCH FIRST 10 ROWS ONLY;

-- 推荐:使用 UNION ALL 代替 UNION(如果不需要去重)
SELECT employee_id, first_name FROM employees WHERE department_id = 10 UNION ALL SELECT employee_id, first_name FROM employees WHERE department_id = 20;

生产环境规范

1. 使用绑定变量

  • 避免硬解析:使用绑定变量代替字面量,减少硬解析
  • 使用游标变量:对于动态 SQL,使用游标变量减少硬解析
  • 设置 cursor_sharing:考虑将 cursor_sharing 设置为 FORCE 或 SIMILAR(但需谨慎)

示例

sql
-- 推荐:使用绑定变量
DECLARE
    v_dept_id NUMBER := 10;
BEGIN
    FOR rec IN (SELECT employee_id, first_name, last_name FROM employees WHERE department_id = v_dept_id) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.first_name || ' ' || rec.last_name);
    END LOOP;
END;
/

-- 不推荐:使用字面量
SELECT employee_id, first_name, last_name FROM employees WHERE department_id = 10;

2. 限制返回数据量

  • 只查询需要的列:避免使用 SELECT *,只查询必要的列
  • 使用 ROWNUM 或 FETCH FIRST:限制返回的行数
  • 使用分页查询:对于 Web 应用,使用分页查询减少数据传输

示例

sql
-- 推荐:只查询需要的列
SELECT employee_id, first_name, last_name FROM employees WHERE department_id = 10;

-- 不推荐:使用 SELECT *
SELECT * FROM employees WHERE department_id = 10;

-- 推荐:使用 FETCH FIRST 限制返回行数
SELECT employee_id, first_name, last_name FROM employees ORDER BY salary DESC FETCH FIRST 10 ROWS ONLY;

-- 分页查询示例
SELECT employee_id, first_name, last_name
FROM (
    SELECT
        employee_id
        ,first_name
        ,last_name
        ,ROW_NUMBER() OVER (ORDER BY employee_id) AS rn
    FROM
        employees
) WHERE rn BETWEEN 11 AND 20;

3. 避免长时间运行的查询

  • 拆分复杂查询:将复杂查询拆分为多个简单查询
  • 使用并行查询:对于大型查询,考虑使用并行查询
  • 设置合理的超时:为查询设置合理的超时时间
  • 监控长查询:定期监控和分析长时间运行的查询

示例

sql
-- 使用并行查询(适用于大型查询)
SELECT /*+ PARALLEL(4) */ employee_id, first_name, last_name FROM employees;

4. 事务管理规范

  • 短事务原则:事务应尽可能短,减少锁持有时间
  • 避免在事务中执行 DDL:DDL 操作会隐式提交事务
  • 使用显式提交:使用显式的 COMMIT 或 ROLLBACK,避免隐式提交
  • 设置合理的隔离级别:根据业务需求选择适当的隔离级别

示例

sql
-- 推荐:短事务
BEGIN
    UPDATE employees SET salary = salary * 1.1 WHERE employee_id = 100;
    COMMIT;
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        RAISE;
END;
/

19c 和 21c 新特性规范

Oracle 19c 新特性

  1. SQL:1999 语法增强:支持更多 SQL:1999 语法
  2. JSON 支持增强:使用 JSON_PATH 访问 JSON 数据
  3. 自动索引:利用自动索引功能优化查询
  4. 实时统计信息:使用 DBMS_STATS.GATHER_TABLE_STATS 的 OPTIONS => 'GATHER AUTO' 选项

示例

sql
-- 使用 JSON_PATH 访问 JSON 数据
SELECT
    json_column.name
    ,json_column.age
FROM
    json_table
WHERE
    json_value(json_column, '$.department') = 'IT';

Oracle 21c 新特性

  1. SQL Macros:使用 SQL Macros 简化复杂查询
  2. JSON 增强:支持 JSON 类型和更多 JSON 函数
  3. 批量数据加载:使用 INSERT ALL 或 MERGE 进行批量数据加载
  4. 轻量级连接:使用轻量级连接减少连接开销

示例

sql
-- 使用 SQL Macros
CREATE OR REPLACE FUNCTION get_employees(p_dept_id NUMBER) RETURN VARCHAR2 SQL_MACRO IS
BEGIN
    RETURN 'SELECT * FROM employees WHERE department_id = p_dept_id';
END;
/

-- 使用 SQL Macros 查询
SELECT * FROM get_employees(10);

常见问题 (FAQ)

为什么要使用绑定变量?

使用绑定变量可以减少硬解析,提高系统性能。硬解析是指 Oracle 对每条 SQL 语句进行语法分析、语义分析、生成执行计划等操作,这些操作会消耗大量的 CPU 和内存资源。使用绑定变量可以让 Oracle 重用执行计划,减少硬解析的开销。

为什么要避免使用 SELECT *?

避免使用 SELECT * 的原因包括:

  • 增加网络传输量:查询不必要的列会增加网络传输量
  • 增加 I/O 操作:查询不必要的列会增加 I/O 操作
  • 影响索引使用:如果只查询索引列,Oracle 可以只扫描索引,不访问表
  • 代码可维护性差:如果表结构发生变化,SELECT * 可能导致应用程序错误

如何优化 IN 子查询?

优化 IN 子查询的方法包括:

  • 使用 EXISTS 代替 IN
  • 使用 JOIN 代替 IN 子查询
  • 确保子查询返回的结果集较小
  • 使用 WITH 子句缓存子查询结果

什么时候使用并行查询?

并行查询适用于以下情况:

  • 大型表的全表扫描
  • 大型索引的创建或重建
  • 复杂的聚合查询
  • 数据仓库环境中的大型查询

如何避免死锁?

避免死锁的方法包括:

  • 保持事务简短
  • 以相同的顺序访问资源
  • 使用适当的锁级别
  • 避免长时间持有锁
  • 使用 NOWAIT 或 WAIT 子句

19c 和 21c 在 SQL 书写方面有什么新特性?

Oracle 19c 和 21c 在 SQL 书写方面的新特性包括:

  • SQL:1999 语法增强
  • JSON 支持增强
  • 自动索引
  • 实时统计信息
  • SQL Macros
  • 批量数据加载增强
  • 轻量级连接

总结

良好的 SQL 书写规范对于提高数据库性能、减少系统资源消耗和提高代码可维护性至关重要。DBA 和开发人员应该遵循 SQL 书写规范,包括命名规范、格式规范、性能优化规范和生产环境规范。

在实际工作中,应该根据具体的业务需求和系统环境,选择合适的 SQL 书写方式。同时,应该定期监控和分析 SQL 语句的执行情况,不断优化和改进 SQL 书写规范。

随着 Oracle 版本的升级,新的 SQL 特性和优化技术不断出现,DBA 和开发人员应该及时学习和掌握这些新特性,提高 SQL 编写的效率和质量。