【Oracle数据库指南】第12篇:PL/SQL高级特性——异常处理与游标深度解析
上一篇【第11篇】PL/SQL子程序——存储过程与函数的创建与应用
下一篇【第13篇】Oracle实例架构深度解析——SGA、进程与实例启动全流程
摘要
本文深入讲解PL/SQL的两大高级特性:异常处理机制和游标操作。涵盖预定义异常、非预定义异常、用户自定义异常的捕获与传播,以及隐式游标、显式游标、游标FOR循环、REF游标的全面使用。通过健壮的数据处理程序实战案例,帮助读者编写出生产级别的PL/SQL代码。
一、异常处理基础
PL/SQL的异常处理机制能优雅地捕获运行时错误,防止程序因错误而崩溃,并提供有意义的错误信息。
1.1 异常的分类
Oracle中的异常分为三类:
| 类型 | 说明 | 示例 |
|---|---|---|
| 预定义异常 | Oracle预先命名的常见错误 | NO_DATA_FOUND、TOO_MANY_ROWS |
| 非预定义异常 | 有错误号但Oracle未命名 | ORA-02291(外键违约) |
| 用户自定义异常 | 程序员定义的业务规则异常 | 薪资超出范围、状态不合法 |
1.2 异常处理结构
BEGIN
-- 正常执行代码
...
EXCEPTION
WHEN 异常名1 THEN
-- 处理异常1
WHEN 异常名2 OR 异常名3 THEN
-- 处理异常2或3
WHEN OTHERS THEN
-- 处理所有其他异常(捕获网)
END;
二、预定义异常
Oracle定义了约20个常用预定义异常,直接使用其名称即可:
DECLARE
v_emp employees%ROWTYPE;
v_salary employees.salary%TYPE;
BEGIN
-- NO_DATA_FOUND:SELECT INTO没有返回行
BEGIN
SELECT * INTO v_emp FROM employees WHERE employee_id = 99999;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('员工不存在');
END;
-- TOO_MANY_ROWS:SELECT INTO返回多行
BEGIN
SELECT salary INTO v_salary FROM employees WHERE department_id = 60;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('返回多行,请使用游标');
END;
-- ZERO_DIVIDE:除以零
BEGIN
DECLARE v_result NUMBER;
BEGIN
v_result := 100 / 0;
EXCEPTION
WHEN ZERO_DIVIDE THEN
DBMS_OUTPUT.PUT_LINE('除以零错误');
END;
END;
-- VALUE_ERROR:数值转换错误或超出范围
BEGIN
DECLARE v_n NUMBER;
BEGIN
v_n := TO_NUMBER('ABC'); -- 非数字字符串转NUMBER
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('数值转换错误');
END;
END;
-- INVALID_NUMBER:SQL中的数值转换错误
-- DUP_VAL_ON_INDEX:违反UNIQUE约束
-- ROWTYPE_MISMATCH:游标变量类型不匹配
-- CURSOR_ALREADY_OPEN:游标已打开再次打开
-- INVALID_CURSOR:无效游标操作
-- LOGIN_DENIED:登录失败
-- NOT_LOGGED_ON:未登录
-- PROGRAM_ERROR:内部PL/SQL错误
-- TIMEOUT_ON_RESOURCE:等待资源超时
-- STORAGE_ERROR:内存不足
-- SELF_IS_NULL:对象类型的NULL实例调用方法
END;
/
三、非预定义异常
-- 步骤1:声明一个EXCEPTION类型变量
-- 步骤2:用PRAGMA EXCEPTION_INIT将错误号与变量关联
-- 步骤3:在EXCEPTION段捕获
DECLARE
-- 声明非预定义异常
ex_fk_violation EXCEPTION;
ex_not_null_vio EXCEPTION;
-- 关联Oracle错误号
PRAGMA EXCEPTION_INIT(ex_fk_violation, -2291); -- ORA-02291: integrity constraint violated
PRAGMA EXCEPTION_INIT(ex_not_null_vio, -1400); -- ORA-01400: cannot insert NULL
BEGIN
-- 尝试插入违反外键约束的数据
INSERT INTO employees(employee_id, last_name, email, hire_date, job_id, department_id)
VALUES(300, 'TestUser', 'TEST', SYSDATE, 'IT_PROG', 99999); -- 部门99999不存在
COMMIT;
EXCEPTION
WHEN ex_fk_violation THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('错误:违反外键约束(所引用的部门不存在)');
WHEN ex_not_null_vio THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('错误:必填字段不能为空');
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('未知错误:' || SQLCODE || ' - ' || SQLERRM);
END;
/
四、用户自定义异常
-- 用于实现业务规则验证,让错误信息更有意义
DECLARE
-- 声明用户自定义异常
ex_salary_too_low EXCEPTION;
ex_salary_too_high EXCEPTION;
ex_invalid_dept EXCEPTION;
v_new_salary NUMBER := 1000; -- 测试数据
v_dept_id NUMBER := 60;
v_emp_count NUMBER;
BEGIN
-- 业务规则1:薪资不能低于最低工资标准
IF v_new_salary < 2000 THEN
RAISE ex_salary_too_low;
END IF;
-- 业务规则2:薪资不能超过最高薪资的150%
IF v_new_salary > 24000 * 1.5 THEN
RAISE ex_salary_too_high;
END IF;
-- 业务规则3:部门人数不能超过20人
SELECT COUNT(*) INTO v_emp_count FROM employees WHERE department_id = v_dept_id;
IF v_emp_count >= 20 THEN
RAISE ex_invalid_dept;
END IF;
-- 如果通过所有验证,执行更新
DBMS_OUTPUT.PUT_LINE('验证通过,执行更新...');
EXCEPTION
WHEN ex_salary_too_low THEN
DBMS_OUTPUT.PUT_LINE('业务错误:薪资(' || v_new_salary || ')低于最低标准2000元');
WHEN ex_salary_too_high THEN
DBMS_OUTPUT.PUT_LINE('业务错误:薪资(' || v_new_salary || ')超出允许范围');
WHEN ex_invalid_dept THEN
DBMS_OUTPUT.PUT_LINE('业务错误:部门' || v_dept_id || '员工人数已达上限');
END;
/
五、RAISE_APPLICATION_ERROR
RAISE_APPLICATION_ERROR用于在存储过程/函数中抛出自定义错误,可以传递给调用方(包括客户端应用程序)。
-- 语法:RAISE_APPLICATION_ERROR(error_number, message [, keep_errors])
-- error_number:必须在-20999到-20000之间
-- message:错误描述(最长2048字节)
CREATE OR REPLACE PROCEDURE transfer_funds(
p_from_acct IN NUMBER,
p_to_acct IN NUMBER,
p_amount IN NUMBER
)
IS
v_balance NUMBER;
BEGIN
-- 验证金额
IF p_amount <= 0 THEN
RAISE_APPLICATION_ERROR(-20010, '转账金额必须大于0,当前:' || p_amount);
END IF;
-- 查询余额
SELECT balance INTO v_balance FROM accounts WHERE account_id = p_from_acct;
-- 验证余额充足
IF v_balance < p_amount THEN
RAISE_APPLICATION_ERROR(-20011,
'账户' || p_from_acct || '余额不足。' ||
'当前余额:' || v_balance || ',' ||
'转账金额:' || p_amount);
END IF;
-- 执行转账
UPDATE accounts SET balance = balance - p_amount WHERE account_id = p_from_acct;
UPDATE accounts SET balance = balance + p_amount WHERE account_id = p_to_acct;
COMMIT;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20012, '账户' || p_from_acct || '不存在');
WHEN OTHERS THEN
ROLLBACK;
RAISE; -- 重新抛出原始异常
END transfer_funds;
/
六、SQLCODE和SQLERRM
DECLARE
v_emp_id NUMBER := 99999;
BEGIN
UPDATE employees SET salary = salary * 1.1 WHERE employee_id = v_emp_id;
IF SQL%ROWCOUNT = 0 THEN
RAISE_APPLICATION_ERROR(-20013, '员工' || v_emp_id || '不存在');
END IF;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
-- SQLCODE:当前错误的Oracle错误号(负数)
-- SQLERRM:当前错误的错误消息
DBMS_OUTPUT.PUT_LINE('错误代码:' || SQLCODE);
DBMS_OUTPUT.PUT_LINE('错误信息:' || SQLERRM);
DBMS_OUTPUT.PUT_LINE('错误详情:' || SQLERRM(SQLCODE));
-- 记录到错误日志表
INSERT INTO error_log(log_time, error_code, error_msg, module_name)
VALUES(SYSDATE, SQLCODE, SUBSTR(SQLERRM, 1, 512), 'salary_update');
COMMIT; -- 注意:在EXCEPTION中的COMMIT只提交此INSERT
RAISE; -- 重新抛出(如果需要调用层继续处理)
END;
/
七、游标(Cursor)
游标是Oracle用于处理SELECT语句返回的多行数据的机制。
7.1 隐式游标
Oracle为每条DML语句(INSERT/UPDATE/DELETE)和SELECT INTO语句自动创建隐式游标,通过SQL%属性访问。
DECLARE
v_updated_rows NUMBER;
BEGIN
UPDATE employees
SET salary = salary * 1.10
WHERE department_id = 60;
-- SQL%ROWCOUNT:受影响的行数
v_updated_rows := SQL%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE('更新了 ' || v_updated_rows || ' 行');
-- SQL%FOUND:是否找到至少一行(TRUE/FALSE/NULL)
IF SQL%FOUND THEN
COMMIT;
DBMS_OUTPUT.PUT_LINE('更新已提交');
ELSE
DBMS_OUTPUT.PUT_LINE('没有符合条件的记录');
END IF;
-- SQL%NOTFOUND:没有找到行(SQL%FOUND的反值)
-- SQL%ISOPEN:对于隐式游标始终为FALSE(已关闭)
END;
/
7.2 显式游标
当需要处理多行数据时,使用显式游标。
DECLARE
-- 声明游标(DECLARE区)
CURSOR c_employees IS
SELECT employee_id, last_name, salary, department_id
FROM employees
WHERE department_id IN (60, 80, 90)
ORDER BY department_id, salary DESC;
-- 声明游标记录变量
v_emp_rec c_employees%ROWTYPE;
v_count NUMBER := 0;
BEGIN
-- 打开游标(分配内存,执行SELECT)
OPEN c_employees;
-- 循环获取数据
LOOP
-- FETCH:获取当前行并推进游标
FETCH c_employees INTO v_emp_rec;
-- 检查是否还有数据
EXIT WHEN c_employees%NOTFOUND; -- 等同于 NOT c_employees%FOUND
v_count := v_count + 1;
DBMS_OUTPUT.PUT_LINE(
v_count || '. ' ||
RPAD(v_emp_rec.last_name, 15) ||
' 部门:' || v_emp_rec.department_id ||
' 薪资:¥' || TO_CHAR(v_emp_rec.salary, '99,999')
);
END LOOP;
-- 关闭游标(释放资源)
CLOSE c_employees;
DBMS_OUTPUT.PUT_LINE('共 ' || v_count || ' 条记录');
EXCEPTION
WHEN OTHERS THEN
-- 确保游标被关闭
IF c_employees%ISOPEN THEN
CLOSE c_employees;
END IF;
RAISE;
END;
/
7.3 带参数的游标
DECLARE
-- 带参数的游标
CURSOR c_dept_emp(p_dept_id NUMBER, p_min_salary NUMBER DEFAULT 0) IS
SELECT employee_id, last_name, salary
FROM employees
WHERE department_id = p_dept_id
AND salary >= p_min_salary
ORDER BY salary DESC;
v_emp c_dept_emp%ROWTYPE;
BEGIN
-- 打开带参数的游标
OPEN c_dept_emp(60, 5000); -- IT部门,薪资>=5000
LOOP
FETCH c_dept_emp INTO v_emp;
EXIT WHEN c_dept_emp%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_emp.last_name || ': ¥' || v_emp.salary);
END LOOP;
CLOSE c_dept_emp;
DBMS_OUTPUT.PUT_LINE('---');
-- 同一游标用不同参数再次打开
OPEN c_dept_emp(80); -- 销售部门,薪资>=0(使用默认值)
LOOP
FETCH c_dept_emp INTO v_emp;
EXIT WHEN c_dept_emp%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_emp.last_name || ': ¥' || v_emp.salary);
END LOOP;
CLOSE c_dept_emp;
END;
/
7.4 游标FOR循环(推荐!最简洁的方式)
-- 游标FOR循环自动处理:OPEN、FETCH、%NOTFOUND检查、CLOSE
-- 是处理多行查询的首选方式
-- 方式1:使用已声明的游标
DECLARE
CURSOR c_emp IS
SELECT employee_id, last_name, salary FROM employees WHERE department_id = 60;
BEGIN
FOR emp_rec IN c_emp LOOP
DBMS_OUTPUT.PUT_LINE(emp_rec.last_name || ': ¥' || emp_rec.salary);
END LOOP;
-- 不需要OPEN/CLOSE,完全自动
END;
/
-- 方式2:直接在FOR LOOP中写SQL(最简洁!)
BEGIN
FOR emp_rec IN (
SELECT e.employee_id, 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
ORDER BY e.salary DESC
) LOOP
DBMS_OUTPUT.PUT_LINE(
emp_rec.department_name || ' | ' ||
emp_rec.last_name || ' | ¥' || emp_rec.salary
);
END LOOP;
END;
/
7.5 游标的UPDATE/DELETE(用于行级操作)
DECLARE
CURSOR c_emp_update IS
SELECT employee_id, salary, commission_pct
FROM employees
WHERE department_id = 80
FOR UPDATE; -- 声明游标用于更新,加行锁
BEGIN
FOR emp IN c_emp_update LOOP
-- 根据不同条件更新不同的行
IF emp.commission_pct >= 0.25 THEN
UPDATE employees
SET salary = emp.salary * 1.15
WHERE CURRENT OF c_emp_update; -- 更新游标当前行(最安全)
ELSIF emp.commission_pct >= 0.15 THEN
UPDATE employees
SET salary = emp.salary * 1.10
WHERE CURRENT OF c_emp_update;
ELSE
UPDATE employees
SET salary = emp.salary * 1.05
WHERE CURRENT OF c_emp_update;
END IF;
END LOOP;
COMMIT;
DBMS_OUTPUT.PUT_LINE('薪资更新完成');
END;
/
7.6 REF游标(动态游标)
REF游标可以在运行时绑定不同的查询,实现动态查询。
-- 声明REF游标类型
DECLARE
TYPE emp_cur_type IS REF CURSOR RETURN employees%ROWTYPE; -- 强类型(指定返回类型)
TYPE weak_cur_type IS REF CURSOR; -- 弱类型(任何查询都可以)
c_emp emp_cur_type;
c_any weak_cur_type;
v_emp employees%ROWTYPE;
v_dept_id NUMBER := 60;
v_query VARCHAR2(500);
BEGIN
-- 使用强类型REF游标
OPEN c_emp FOR
SELECT * FROM employees WHERE department_id = v_dept_id;
LOOP
FETCH c_emp INTO v_emp;
EXIT WHEN c_emp%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_emp.last_name || ': ¥' || v_emp.salary);
END LOOP;
CLOSE c_emp;
-- 动态SQL与弱类型REF游标
v_query := 'SELECT * FROM employees WHERE department_id = :dept AND salary > :sal';
OPEN c_any FOR v_query USING 60, 5000; -- 绑定变量
LOOP
FETCH c_any INTO v_emp;
EXIT WHEN c_any%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_emp.last_name || ' (动态查询)');
END LOOP;
CLOSE c_any;
END;
/
八、综合实战案例——健壮的批量数据处理程序
SET SERVEROUTPUT ON SIZE 1000000
CREATE OR REPLACE PROCEDURE process_salary_review(
p_review_year IN NUMBER DEFAULT EXTRACT(YEAR FROM SYSDATE)
)
IS
-- 异常声明
ex_invalid_year EXCEPTION;
ex_budget_exceeded EXCEPTION;
-- 游标:查询需要调薪的员工
CURSOR c_emp_review IS
SELECT
e.employee_id,
e.last_name,
e.salary,
e.hire_date,
e.department_id,
ROUND(MONTHS_BETWEEN(SYSDATE, e.hire_date) / 12, 1) AS years_served,
d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.salary < (SELECT PERCENTILE_CONT(0.6) WITHIN GROUP (ORDER BY salary)
FROM employees) -- 薪资在60百分位以下
AND NOT EXISTS ( -- 当年未调薪
SELECT 1 FROM salary_history sh
WHERE sh.employee_id = e.employee_id
AND EXTRACT(YEAR FROM sh.review_date) = p_review_year
)
ORDER BY e.department_id, e.salary ASC;
-- 统计变量
v_processed_count NUMBER := 0;
v_success_count NUMBER := 0;
v_skip_count NUMBER := 0;
v_total_increase NUMBER := 0;
v_budget_used NUMBER := 0;
v_budget_max CONSTANT NUMBER := 200000; -- 年度预算上限
-- 单员工处理变量
v_raise_pct NUMBER;
v_new_salary NUMBER;
v_increase_amount NUMBER;
BEGIN
-- 验证输入年份
IF p_review_year < 2000 OR p_review_year > EXTRACT(YEAR FROM SYSDATE) + 1 THEN
RAISE ex_invalid_year;
END IF;
DBMS_OUTPUT.PUT_LINE('==============================================');
DBMS_OUTPUT.PUT_LINE('薪资调整处理程序');
DBMS_OUTPUT.PUT_LINE('处理年份:' || p_review_year);
DBMS_OUTPUT.PUT_LINE('处理时间:' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
DBMS_OUTPUT.PUT_LINE('预算上限:¥' || TO_CHAR(v_budget_max, '999,999'));
DBMS_OUTPUT.PUT_LINE('==============================================');
-- 遍历需要调薪的员工
FOR emp IN c_emp_review LOOP
v_processed_count := v_processed_count + 1;
-- 根据工龄确定涨薪比例
IF emp.years_served >= 10 THEN
v_raise_pct := 0.15;
ELSIF emp.years_served >= 7 THEN
v_raise_pct := 0.12;
ELSIF emp.years_served >= 5 THEN
v_raise_pct := 0.10;
ELSIF emp.years_served >= 3 THEN
v_raise_pct := 0.08;
ELSE
v_raise_pct := 0.05;
END IF;
v_new_salary := ROUND(emp.salary * (1 + v_raise_pct));
v_increase_amount := v_new_salary - emp.salary;
-- 检查预算
IF v_budget_used + v_increase_amount * 12 > v_budget_max THEN
v_skip_count := v_skip_count + 1;
DBMS_OUTPUT.PUT_LINE(
'[跳过] ' || RPAD(emp.last_name, 12) ||
' 预算已用:¥' || TO_CHAR(v_budget_used, '999,999')
);
RAISE ex_budget_exceeded; -- 预算耗尽,停止处理
END IF;
-- 执行调薪
BEGIN
UPDATE employees
SET salary = v_new_salary
WHERE employee_id = emp.employee_id;
-- 记录调薪历史
INSERT INTO salary_history(employee_id, old_salary, new_salary,
raise_pct, review_date, review_year)
VALUES(emp.employee_id, emp.salary, v_new_salary,
v_raise_pct, SYSDATE, p_review_year);
v_budget_used := v_budget_used + v_increase_amount * 12;
v_success_count := v_success_count + 1;
v_total_increase := v_total_increase + v_increase_amount;
DBMS_OUTPUT.PUT_LINE(
'[成功] ' || RPAD(emp.last_name, 12) ||
' 部门:' || RPAD(emp.department_name, 12) ||
' 工龄:' || emp.years_served || '年' ||
' ¥' || TO_CHAR(emp.salary, '99,999') ||
' → ¥' || TO_CHAR(v_new_salary, '99,999') ||
' (+' || ROUND(v_raise_pct * 100) || '%)'
);
EXCEPTION
WHEN OTHERS THEN
-- 单个员工处理失败不影响其他员工
v_skip_count := v_skip_count + 1;
DBMS_OUTPUT.PUT_LINE(
'[失败] ' || emp.last_name ||
' 错误:' || SUBSTR(SQLERRM, 1, 100)
);
END;
END LOOP;
COMMIT;
-- 输出汇总
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('==============================================');
DBMS_OUTPUT.PUT_LINE('处理完成汇总');
DBMS_OUTPUT.PUT_LINE('总候选人数:' || v_processed_count);
DBMS_OUTPUT.PUT_LINE('成功调薪:' || v_success_count || ' 人');
DBMS_OUTPUT.PUT_LINE('跳过/失败:' || v_skip_count || ' 人');
DBMS_OUTPUT.PUT_LINE('月薪增加总量:¥' || TO_CHAR(v_total_increase, '999,999'));
DBMS_OUTPUT.PUT_LINE('预计年度预算使用:¥' || TO_CHAR(v_budget_used, '999,999'));
DBMS_OUTPUT.PUT_LINE('==============================================');
EXCEPTION
WHEN ex_invalid_year THEN
DBMS_OUTPUT.PUT_LINE('错误:无效的年份 ' || p_review_year);
WHEN ex_budget_exceeded THEN
COMMIT; -- 提交已处理的
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('预算已耗尽,已处理 ' || v_success_count || ' 人后停止');
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('严重错误,已回滚所有操作:' || SQLERRM);
RAISE;
END process_salary_review;
/
九、常见问题
Q1:游标FOR循环和普通LOOP的性能差异?
游标FOR循环默认使用批量取数(BULK COLLECT),每次获取多行,性能通常优于逐行FETCH。在处理大数据量时,可以使用显式的BULK COLLECT进一步优化:
DECLARE
TYPE emp_tab IS TABLE OF employees%ROWTYPE;
v_emps emp_tab;
v_limit CONSTANT NUMBER := 1000;
CURSOR c_emp IS SELECT * FROM employees WHERE department_id = 60;
BEGIN
OPEN c_emp;
LOOP
FETCH c_emp BULK COLLECT INTO v_emps LIMIT v_limit; -- 每次取1000行
EXIT WHEN v_emps.COUNT = 0;
FOR i IN 1..v_emps.COUNT LOOP
-- 处理v_emps(i)
DBMS_OUTPUT.PUT_LINE(v_emps(i).last_name);
END LOOP;
END LOOP;
CLOSE c_emp;
END;
/
Q2:WHEN OTHERS THEN RAISE 有什么用?
EXCEPTION
WHEN OTHERS THEN
-- 记录日志
INSERT INTO error_log VALUES(SYSDATE, SQLCODE, SQLERRM, 'module_name');
COMMIT;
RAISE; -- 重新抛出,让上层调用者也能感知到错误
-- 如果不RAISE,错误被吞掉,调用者以为执行成功了!
十、总结
本文系统讲解了PL/SQL高级特性:
- 异常处理:预定义、非预定义、用户自定义异常,RAISE_APPLICATION_ERROR,SQLCODE/SQLERRM
- 隐式游标:SQL%ROWCOUNT、SQL%FOUND等属性
- 显式游标:声明、OPEN、FETCH、CLOSE、带参数游标
- 游标FOR循环:最简洁、推荐的多行处理方式
- REF游标:动态查询的游标类型
- BULK COLLECT:批量取数优化
至此,PL/SQL基础三篇(基础语法、子程序、异常与游标)已全部完成,下一个系列将深入Oracle体系结构的内部机制。
上一篇【第11篇】PL/SQL子程序——存储过程与函数的创建与应用
下一篇【第13篇】Oracle实例架构深度解析——SGA、进程与实例启动全流程
参考资料
- 《Oracle 11g数据库管理员指南》— 刘宪军著
- Oracle官方文档:Database PL/SQL Language Reference - Exception Handling
- Oracle官方文档:Database PL/SQL Language Reference - Cursors
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)