Oracle 19c入门学习教程,从入门到精通,Oracle数据库控制 —— 事务与并发控制详解(14)

Oracle数据库控制 ------ 事务与并发控制详解


一、环境准备:Oracle 安装简要说明(延续第13章)

本章依赖已安装的 Oracle Database(如 21c XE),无需额外安装组件。

若尚未安装,请参考第13章"环境准备"部分完成 Oracle 安装并确保可使用 sqlplus 连接。

验证安装:

bash 复制代码
sqlplus sys/your_password@localhost:1521/XE as sysdba

创建测试用户(用于后续案例):

sql 复制代码
-- 以 SYSDBA 身份执行
CREATE USER app_user IDENTIFIED BY AppPass123;
GRANT CREATE SESSION, CREATE TABLE, UNLIMITED TABLESPACE TO app_user;

二、核心语法知识点详解与案例


1. 事务概述(Transaction)

概念:
  • 事务 是一组逻辑操作单元,具有 ACID 特性:
    • Atomicity(原子性):全部成功或全部失败。
    • Consistency(一致性):事务前后数据满足业务规则。
    • Isolation(隔离性):并发事务互不干扰。
    • Durability(持久性):提交后结果永久保存。
Oracle 事务特点:
  • 自动开启:执行第一条 DML(INSERT/UPDATE/DELETE)时自动开始。
  • 显式结束 :通过 COMMITROLLBACK 结束。
  • 隐式提交:执行 DDL(如 CREATE TABLE)或退出会话时自动提交。

2. 操作事务

2.1 提交事务(COMMIT)
sql 复制代码
COMMIT [WORK] [COMMENT 'text'];
  • WORK 可选(兼容 SQL 标准)。
  • COMMENT 用于分布式事务(极少用)。
案例:
sql 复制代码
-- 开启事务(自动)
INSERT INTO employees (id, name) VALUES (101, 'Alice');
UPDATE departments SET head = 'Alice' WHERE dept_id = 10;

-- 提交更改
COMMIT;  -- 所有修改永久生效
2.2 回滚事务(ROLLBACK)
sql 复制代码
ROLLBACK [WORK] [TO SAVEPOINT savepoint_name];
案例:
sql 复制代码
-- 插入数据
INSERT INTO employees VALUES (102, 'Bob');

-- 设置保存点
SAVEPOINT sp1;

-- 再插入
INSERT INTO employees VALUES (103, 'Charlie');

-- 回滚到保存点(仅撤销 Charlie 的插入)
ROLLBACK TO sp1;

-- 完全回滚(撤销 Bob 和 Charlie)
ROLLBACK;

SAVEPOINT 允许部分回滚,提高事务灵活性。

2.3 自动回滚
  • 会话异常断开(如网络中断) → Oracle 自动回滚未提交事务。
  • 执行 DDL(如 CREATE TABLE) → 隐式提交当前事务。

3. 并发控制与锁机制

3.1 为何加锁?
  • 防止多个事务同时修改同一数据导致 不一致
  • 保证 隔离性(Isolation)。

常见并发问题:

问题 说明
脏读 读到未提交的数据
不可重复读 同一事务内多次读取结果不同
幻读 新插入的行"凭空出现"

🔒 Oracle 默认隔离级别为 READ COMMITTED,可避免脏读,但可能出现不可重复读和幻读。


4. 加锁的方法

Oracle 自动管理大多数锁(DML 锁),但也支持手动控制。

4.1 行级锁(Row-Level Locking)
  • 执行 UPDATE / DELETE 时自动对涉及行加 排他锁(X 锁)
  • 其他会话可读,但不能修改被锁定的行。
案例(会话 A):
sql 复制代码
-- 会话 A
UPDATE employees SET salary = 8000 WHERE id = 101;
-- 此时 id=101 的行被锁定(未 COMMIT 前)
会话 B 尝试修改同一行:
sql 复制代码
-- 会话 B(将阻塞,直到会话 A COMMIT 或 ROLLBACK)
UPDATE employees SET salary = 9000 WHERE id = 101;

⏳ 会话 B 会等待(默认无超时),可通过 ALTER SESSION SET ddl_lock_timeout = 10; 设置 DDL 等待时间(对 DML 无效)。

4.2 表级锁(Table Locks)
  • 手动加表锁(较少用,通常由 Oracle 自动升级)。
语法:
sql 复制代码
LOCK TABLE table_name IN lock_mode MODE [NOWAIT];

常见模式:

模式 说明
ROW SHARE 允许多个会话并发读写(默认 DML 行为)
SHARE 禁止其他会话修改表(用于只读场景)
EXCLUSIVE 禁止任何其他 DML/DDL
案例:
sql 复制代码
-- 锁定 employees 表为共享模式(禁止结构修改)
LOCK TABLE employees IN SHARE MODE;

-- 锁定为排他模式(禁止任何其他访问)
LOCK TABLE employees IN EXCLUSIVE MODE NOWAIT;  -- NOWAIT:不等待,直接报错

EXCLUSIVE 模式会阻塞所有其他 DML,慎用!

4.3 SELECT FOR UPDATE(显式加锁)
  • 在查询时锁定选中行,防止其他会话修改。
语法:
sql 复制代码
SELECT ... FROM table_name WHERE ... FOR UPDATE [OF column_list] [NOWAIT | WAIT n];
案例:
sql 复制代码
-- 锁定部门 10 的所有员工记录
SELECT * FROM employees WHERE dept_id = 10 FOR UPDATE;

-- 仅锁定 salary 列(语义上,实际仍锁整行)
SELECT id, salary FROM employees WHERE id = 101 FOR UPDATE OF salary NOWAIT;

✅ 常用于"先查后改"场景,避免竞态条件。


5. 死锁(Deadlock)

5.1 死锁的产生
  • 两个或多个事务互相等待对方释放锁,形成循环等待。
经典场景:
  • 会话 A :更新 emp(id=1) → 尝试更新 dept(id=1)
  • 会话 B :更新 dept(id=1) → 尝试更新 emp(id=1)
  • 双方都在等对方释放锁 → 死锁!
5.2 Oracle 如何处理死锁?
  • Oracle 自动检测死锁(通过等待图)。
  • 终止其中一个事务 (报错 ORA-00060),让另一个继续。
模拟死锁(需两个会话):

会话 A:

sql 复制代码
UPDATE employees SET salary = 7000 WHERE id = 101;
-- 不提交
UPDATE departments SET budget = 100000 WHERE dept_id = 10;

会话 B:

sql 复制代码
UPDATE departments SET budget = 110000 WHERE dept_id = 10;
-- 不提交
UPDATE employees SET salary = 7500 WHERE id = 101;

→ 其中一个会话将报错:

复制代码
ORA-00060: deadlock detected while waiting for resource
5.3 死锁的预防

最佳实践

  1. 按固定顺序访问表 :如总是先 employeesdepartments
  2. 减少事务持有锁的时间 :尽快 COMMIT
  3. 避免交互式事务:不要在事务中等待用户输入。
  4. 使用 SELECT FOR UPDATE NOWAIT:快速失败而非无限等待。

三、综合性实战案例

场景:银行转账系统(高并发安全)

业务需求:
  • 用户 A 向用户 B 转账 500 元。
  • 必须保证:A 扣款成功 ⇨ B 加款成功(原子性)。
  • 高并发下不能超扣或重复转账。
表结构:
sql 复制代码
CREATE TABLE accounts (
    account_id NUMBER PRIMARY KEY,
    balance    NUMBER(10,2) NOT NULL
);

INSERT INTO accounts VALUES (1, 1000.00);  -- A
INSERT INTO accounts VALUES (2, 2000.00);  -- B
COMMIT;

安全转账存储过程(推荐方式)

sql 复制代码
CREATE OR REPLACE PROCEDURE transfer_funds(
    p_from_id   IN NUMBER,
    p_to_id     IN NUMBER,
    p_amount    IN NUMBER
)
AS
    v_from_bal NUMBER;
    v_to_bal   NUMBER;
BEGIN
    -- 1. 显式锁定两账户(按 ID 升序,避免死锁)
    SELECT balance INTO v_from_bal 
    FROM accounts 
    WHERE account_id = p_from_id 
    FOR UPDATE;  -- 锁定转出账户

    SELECT balance INTO v_to_bal 
    FROM accounts 
    WHERE account_id = p_to_id 
    FOR UPDATE;  -- 锁定转入账户

    -- 2. 检查余额
    IF v_from_bal < p_amount THEN
        RAISE_APPLICATION_ERROR(-20001, 'Insufficient funds');
    END IF;

    -- 3. 执行转账
    UPDATE accounts SET balance = balance - p_amount WHERE account_id = p_from_id;
    UPDATE accounts SET balance = balance + p_amount WHERE account_id = p_to_id;

    -- 4. 提交(自动释放锁)
    COMMIT;
    DBMS_OUTPUT.PUT_LINE('Transfer successful.');

EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        RAISE;
END;
/

✅ 关键点:

  • 使用 FOR UPDATE 显式加锁。
  • account_id 升序访问(统一顺序防死锁)。
  • 异常时回滚。

多会话并发测试

会话 1:

sql 复制代码
EXEC transfer_funds(1, 2, 500);

会话 2(同时执行):

sql 复制代码
EXEC transfer_funds(2, 1, 300);

→ Oracle 会串行执行,不会出现余额错误或死锁(因访问顺序一致)。


四、监控与诊断工具

1. 查看当前锁信息

sql 复制代码
-- 查看被阻塞的会话
SELECT sid, serial#, username, blocking_session
FROM v$session
WHERE blocking_session IS NOT NULL;

-- 查看锁详情
SELECT s.sid, s.serial#, s.username, l.type, l.lmode, l.request, o.object_name
FROM v$lock l
JOIN v$session s ON l.sid = s.sid
LEFT JOIN dba_objects o ON l.id1 = o.object_id
WHERE l.type IN ('TM', 'TX');

2. 强制终止会话(解决长时间阻塞)

sql 复制代码
-- 终止 SID=123, SERIAL#=456 的会话
ALTER SYSTEM KILL SESSION '123,456';

五、总结与最佳实践

主题 建议
事务 尽量短小,及时 COMMIT/ROLLBACK
优先依赖 Oracle 自动行锁,必要时用 FOR UPDATE
死锁 统一访问顺序 + 快速失败(NOWAIT)
并发 避免长事务,合理设计索引减少锁范围
监控 定期检查 v$lockv$session

💡 Oracle 的并发控制高度自动化,开发者应聚焦于 事务边界设计访问顺序规范,而非手动加锁。


✅ 本章覆盖 Oracle 事务控制、锁机制、死锁处理全流程,适用于 OLTP 高并发场景开发与调优。

相关推荐
码农多耕地呗2 小时前
mysql之深入理解b+树原理
数据库·b树·mysql
踢足球09292 小时前
寒假打卡:2026-01-26
数据库
漂洋过海的鱼儿2 小时前
Qt--元对象系统
开发语言·数据库·qt
沧澜sincerely2 小时前
分组数据【GROUP BY 与 HAVING的使用】
数据库·sql·group by·having
2301_811232982 小时前
使用Flask快速搭建轻量级Web应用
jvm·数据库·python
其美杰布-富贵-李2 小时前
Spring Event 学习笔记
笔记·学习·spring·事件消息
予枫的编程笔记2 小时前
【Redis实战进阶篇】高并发下数据安全与性能平衡?Redis准存储三大核心场景实战指南
数据库·redis·缓存·高并发优化·电商实战·redis准存储·redis pipeline
jiunian_cn2 小时前
【Redis】Redis基本全局命令
数据库·redis·缓存
m0_561359672 小时前
高级爬虫技巧:处理JavaScript渲染(Selenium)
jvm·数据库·python