事务与并发控制

最后更新: 2026-04-15 作者: Oracle Team
页面目录

第八章:事务与并发控制

文档信息
  • 适用版本:Oracle 12c / 18c / 19c / 21c
  • 阅读时间:约 50 分钟
  • 前置知识:第七章触发器

8.1 事务基础

8.1.1 ACID 特性

┌─────────────────────────────────────────────────────────────────┐
│                      Oracle ACID 特性实现                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Atomicity (原子性)                                             │
│  ────────────────────                                           │
│  • 事务作为最小工作单元                                           │
│  • COMMIT: 所有操作永久保存                                       │
│  • ROLLBACK: 所有操作撤销                                        │
│  • 由 Undo Segment 实现                                         │
│                                                                 │
│  Consistency (一致性)                                           │
│  ────────────────────                                            │
│  • 事务前后数据保持一致状态                                       │
│  • 由约束、触发器、应用程序逻辑保证                                │
│  • SCN (System Change Number) 保证                              │
│                                                                 │
│  Isolation (隔离性)                                              │
│  ───────────────────                                             │
│  • 并发事务相互隔离                                              │
│  • 由锁机制 + MVCC 实现                                          │
│  • 支持多种隔离级别                                              │
│                                                                 │
│  Durability (持久性)                                            │
│  ─────────────────────                                           │
│  • 提交的事务永不丢失                                             │
│  • 由 Redo Log + 控制文件保证                                     │
│  • 写入磁盘后持久化                                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

8.1.2 事务控制语句

-- 开始事务(隐式)
-- 第一个 DML 语句自动开始事务

-- 提交事务
COMMIT;  -- 所有更改永久保存
COMMIT COMMENT 'Update employee salary';  -- 带注释提交
COMMIT WRITE NOWAIT;  -- 不等待写盘(性能优先)
COMMIT WRITE WAIT;    -- 等待写盘(默认)

-- 回滚事务
ROLLBACK;  -- 回滚所有未提交更改
ROLLBACK TO SAVEPOINT sp1;  -- 回滚到保存点

-- 创建保存点
SAVEPOINT sp1;

-- 事务示例
BEGIN
    UPDATE accounts SET balance = balance - 1000 WHERE account_id = 1;
    SAVEPOINT before_deposit;
    
    UPDATE accounts SET balance = balance + 1000 WHERE account_id = 2;
    
    -- 检查是否有错误
    IF SQL%NOTFOUND THEN
        ROLLBACK TO SAVEPOINT before_deposit;
        DBMS_OUTPUT.PUT_LINE('Transfer failed');
    ELSE
        COMMIT;
        DBMS_OUTPUT.PUT_LINE('Transfer successful');
    END IF;
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
END;
/

8.1.3 隐式事务控制

-- 自动提交(SQL*Plus 环境)
SET AUTOCOMMIT ON;  -- 每个 DDL/DCL 语句后自动提交
SET AUTOCOMMIT OFF; -- 手动提交(默认)

-- SQLPlus 退出时自动提交未提交事务
-- 但 DROP/TRUNCATE 立即提交

-- DDL 语句自动提交
-- CREATE, ALTER, DROP, GRANT, REVOKE, TRUNCATE
-- 这些语句执行前会隐式提交当前事务

8.2 并发问题

8.2.1 并发问题类型

问题 说明 示例
脏读 读取未提交数据 事务 A 修改,事务 B 读取,A 回滚
不可重复读 同一事务两次读取不同 事务 A 读取,事务 B 修改,A 再次读取
幻读 两次查询结果集不同 事务 A 查询,事务 B 插入,A 再次查询
更新丢失 并发更新覆盖 事务 A/B 读取,修改后依次写入
脏读:读取未提交的数据
┌─────────────────────────────────────────────────────────────────┐
│ 事务A:  UPDATE emp SET salary=10000 WHERE empno=1001          │
│         (未提交)                                                 │
│ 事务B:  SELECT salary FROM emp WHERE empno=1001                │
│         → 看到 10000(脏数据)                                   │
│ 事务A:  ROLLBACK                                                │
│         → 工资回到原值,事务B读到了不存在的数据                   │
└─────────────────────────────────────────────────────────────────┘

不可重复读:同一事务两次读取结果不同
┌─────────────────────────────────────────────────────────────────┐
│ 事务A:  SELECT salary FROM emp WHERE empno=1001 → 5000          │
│ 事务B:  UPDATE emp SET salary=6000 WHERE empno=1001            │
│         COMMIT                                                  │
│ 事务A:  SELECT salary FROM emp WHERE empno=1001 → 6000         │
│         → 同一事务中数据不一致                                    │
└─────────────────────────────────────────────────────────────────┘

幻读:新行出现
┌─────────────────────────────────────────────────────────────────┐
│ 事务A:  SELECT COUNT(*) FROM emp WHERE dept=10 → 10            │
│ 事务B:  INSERT INTO emp VALUES (...) WHERE dept=10             │
│         COMMIT                                                  │
│ 事务A:  SELECT COUNT(*) FROM emp WHERE dept=10 → 11             │
│         → 结果集行数不同                                          │
└─────────────────────────────────────────────────────────────────┘

8.3 隔离级别

8.3.1 Oracle 隔离级别

隔离级别 脏读 不可重复读 幻读 实现方式
READ UNCOMMITTED 可能 可能 可能 不支持(Oracle 不提供)
READ COMMITTED 不可能 可能 可能 默认,读取已提交数据
SERIALIZABLE 不可能 不可能 不可能 悲观锁,类似 MVCC
READ ONLY 不可能 可能 可能 只读事务
-- 设置事务隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET TRANSACTION READ ONLY;  -- 只读事务

-- READ COMMITTED(默认)
BEGIN
    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    -- 每个查询看到的是查询开始时的已提交数据
END;
/

-- SERIALIZABLE
BEGIN
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    -- 整个事务看到的是事务开始时的数据快照
END;
/

-- READ ONLY
BEGIN
    SET TRANSACTION READ ONLY;
    -- 只读事务,不能执行 DML
END;
/

-- SCN 机制
SELECT SCN_TO_TIMESTAMP(12345678) FROM DUAL;
SELECT TIMESTAMP_TO_SCN(SYSTIMESTAMP) FROM DUAL;

8.4 锁机制

8.4.1 锁类型

锁类型 说明 持有时间
TX (行级锁) 锁定单行 事务期间
TM (表级锁) 锁定整表 DML 期间
LMD (分布式锁) 分布式事务 事务期间
行级锁 (TX) - 多读少写场景
┌─────────────────────────────────────────────────────────────────┐
│ 事务A:  SELECT * FROM emp WHERE empno=1001 FOR UPDATE;          │
│         → 对第1001行加锁                                         │
│ 事务B:  UPDATE emp SET sal=5000 WHERE empno=1001;               │
│         → 等待获取锁...                                          │
│ 事务A:  COMMIT/ROLLBACK → 释放锁                                │
│         → 事务B 继续执行                                         │
└─────────────────────────────────────────────────────────────────┘

表级锁 (TM) 模式:
• RS: Row Share - 已锁定行的表锁
• RX: Row Exclusive - DML 默认锁
• S: Share - 禁止 DML
• SRX: Share Row Exclusive - 允许查询,禁止其他锁
• X: Exclusive - 独占访问

8.4.2 锁查询

-- 查看当前锁
SELECT 
    session_id AS sid,
    serial#,
    lock_type,
    mode_held,
    mode_requested,
    lock_id1 AS id1,
    lock_id2 AS id2
FROM dba_lock
WHERE session_id IN (
    SELECT sid FROM v$session WHERE username = 'HR'
);

-- 查看锁等待
SELECT 
    s1.username || '@' || s1.machine || ' (SID=' || s1.sid || ')' AS waiting_session,
    s2.username || '@' || s2.machine || ' (SID=' || s2.sid || ')' AS blocking_session,
    o1.object_name || ' (ID1=' || l1.id1 || ', ID2=' || l1.id2 || ')' AS locked_object,
    l1.type AS lock_type,
    DECODE(l1.lmode, 
        1, 'NULL', 2, 'Row-S', 3, 'Row-X',
        4, 'Share', 5, 'S/Row-X', 6, 'Exclusive') AS lock_mode
FROM v$lock l1
JOIN v$session s1 ON l1.sid = s1.sid
JOIN v$lock l2 ON l1.id1 = l2.id1 AND l1.id2 = l2.id2 AND l2.request > 0
JOIN v$session s2 ON l2.sid = s2.sid
LEFT JOIN dba_objects o1 ON l1.id1 = o1.object_id;

-- 简化查看
SELECT 
    sid,
    serial#,
    username,
    object_name,
    object_type,
    DECODE(lmode,
        0, 'None', 1, 'Null', 2, 'Row Share',
        3, 'Row Exclusive', 4, 'Share',
        5, 'Share Row Exclusive', 6, 'Exclusive') AS lock_mode,
    CTIME,
    BLOCK
FROM v$session s
JOIN v$locked_object l ON s.sid = l.session_id
JOIN dba_objects o ON l.object_id = o.object_id;

8.4.3 锁冲突处理

-- 查看阻塞者
SELECT 
    blocker.sid AS blocker_sid,
    blocker.serial# AS blocker_serial,
    waiter.sid AS waiter_sid,
    waiter.serial# AS waiter_serial,
    o.object_name
FROM v$session blocker
JOIN v$session waiter ON blocker.sid = waiter.blocking_session
JOIN v$session s ON waiter.sid = s.sid
JOIN dba_objects o ON s.row_wait_obj# = o.object_id;

-- 杀会话(谨慎使用)
ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;
ALTER SYSTEM KILL SESSION '145,2037' IMMEDIATE;

-- 或者使用 DBMS_SESSION
BEGIN
    DBMS_SESSION.MODIFY_PACKAGE_STATE(DBMS_SESSION.KILL_SESSION);
END;
/

8.4.4 FOR UPDATE 锁

-- 加锁查询
SELECT * FROM employees WHERE department_id = 50 FOR UPDATE;

-- 加锁并等待(默认)
SELECT * FROM employees WHERE department_id = 50 FOR UPDATE;

-- 加锁不等待,立即返回
SELECT * FROM employees WHERE department_id = 50 FOR UPDATE NOWAIT;

-- 等待 N 秒
SELECT * FROM employees WHERE department_id = 50 FOR UPDATE WAIT 10;

-- 锁定特定列
SELECT employee_id, first_name, salary 
FROM employees 
WHERE department_id = 50 
FOR UPDATE OF salary;  -- 只锁定 salary 列

-- FOR UPDATE SKIP LOCKED(并发友好)
SELECT * FROM employees 
WHERE department_id = 50 
FOR UPDATE SKIP LOCKED;

-- 用法场景:任务分配
SELECT * FROM tasks 
WHERE status = 'PENDING' 
ORDER BY priority 
FOR UPDATE SKIP LOCKED;

-- 应用锁
BEGIN
    UPDATE employees SET salary = 5000 WHERE employee_id = 100;
    -- 不提交,等待应用确认
END;
/

8.5 多版本并发控制 (MVCC)

8.5.1 MVCC 原理

┌─────────────────────────────────────────────────────────────────┐
│                      Oracle MVCC 工作原理                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  读取流程:                                                       │
│  ─────────                                                       │
│  1. 事务开始获取 SCN                                              │
│  2. 查询数据时,根据 SCN 查找一致版本                            │
│  3. 通过 Undo 段构造历史数据                                      │
│  4. 返回查询结果                                                  │
│                                                                 │
│  数据结构:                                                       │
│  ─────────                                                       │
│  ┌─────────────┐      ┌─────────────┐      ┌─────────────┐     │
│  │ Data Block  │ ←── │ Undo Block │ ←── │ Undo Block │     │
│  │ (当前数据)  │      │ (前镜像1)   │      │ (前镜像2)   │     │
│  └─────────────┘      └─────────────┘      └─────────────┘     │
│       SCN=100              SCN=90              SCN=80          │
│                                                                 │
│  一致性读取:                                                     │
│  查询 SCN=95 → 读取 Data Block                                   │
│  查询 SCN=85 → 读取 Undo Block 1                                 │
│  查询 SCN=75 → 读取 Undo Block 2                                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

8.5.2 SCN 与时间戳

-- 获取当前 SCN
SELECT DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER AS scn FROM DUAL;
SELECT TIMESTAMP_TO_SCN(SYSTIMESTAMP) FROM DUAL;

-- SCN 转换为时间戳
SELECT SCN_TO_TIMESTAMP(12345678) FROM DUAL;

-- 查看事务的 SCN
SELECT 
    xid, 
    start_scn, 
    commit_scn, 
    status
FROM v$transaction;

-- 一致性读取演示
SELECT * FROM employees AS OF SCN 12345678;
SELECT * FROM employees AS OF TIMESTAMP TO_TIMESTAMP('2024-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS');

8.6 自治事务

8.6.1 自治事务原理

┌─────────────────────────────────────────────────────────────────┐
│                      自治事务 vs 普通事务                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  普通事务:                                                       │
│  ─────────                                                       │
│  主事务 ────────────────────────────────                         │
│       │                                                         │
│       ├── 触发器/存储过程 (参与主事务)                           │
│       │    │                                                    │
│       └── COMMIT/ROLLBACK → 全部提交/回滚                       │
│                                                                 │
│  自治事务:                                                       │
│  ─────────                                                       │
│  主事务 ────────────────────────────────────                    │
│       │                                                         │
│       ├── 自治过程 (独立事务)                                    │
│       │    │                                                    │
│       │    └── COMMIT → 仅自治事务提交                          │
│       │                                                         │
│       └── COMMIT → 主事务提交                                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

8.6.2 自治事务应用

-- 创建自治事务存储过程
CREATE OR REPLACE PROCEDURE p_log_error(
    p_message IN VARCHAR2
)
IS
    PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
    INSERT INTO error_log (
        error_message, log_date, username
    ) VALUES (
        p_message, SYSDATE, USER
    );
    COMMIT;  -- 自治事务必须提交
END p_log_error;
/

-- 在主事务中调用
BEGIN
    UPDATE employees SET salary = 5000 WHERE employee_id = 100;
    
    -- 无论主事务是否提交,日志都会保存
    p_log_error('Updated employee 100 salary');
    
    ROLLBACK;  -- 员工更新回滚,但日志已保存
END;
/

-- 自治事务触发器
CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
DECLARE
    PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
    IF INSERTING THEN
        INSERT INTO audit_trail VALUES ('INSERT', :NEW.employee_id, USER, SYSDATE);
    ELSIF UPDATING THEN
        INSERT INTO audit_trail VALUES ('UPDATE', :OLD.employee_id, USER, SYSDATE);
    ELSIF DELETING THEN
        INSERT INTO audit_trail VALUES ('DELETE', :OLD.employee_id, USER, SYSDATE);
    END IF;
    COMMIT;
END trg_emp_audit;
/

-- 函数中使用自治事务
CREATE OR REPLACE FUNCTION f_commit_log(p_msg VARCHAR2)
RETURN NUMBER
IS
    PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
    INSERT INTO app_log VALUES (p_msg, SYSDATE);
    COMMIT;
    RETURN 1;
EXCEPTION
    WHEN OTHERS THEN
        RETURN 0;
END f_commit_log;
/

8.7 锁优化建议

8.7.1 减少锁竞争

-- 1. 减小事务范围
BEGIN
    -- 锁定后立即处理,避免长事务
    FOR rec IN (SELECT employee_id FROM employees WHERE status = 'NEW' FOR UPDATE)
    LOOP
        UPDATE employees SET status = 'PROCESSED' WHERE employee_id = rec.employee_id;
    END LOOP;
    COMMIT;
END;
/

-- 2. 使用批量操作
BEGIN
    FORALL i IN 1..1000
        UPDATE employees SET salary = salary * 1.1 WHERE employee_id = i;
    COMMIT;
END;
/

-- 3. 顺序访问
-- 多个事务按相同顺序访问资源,避免死锁

-- 4. 使用序列而非应用生成ID
CREATE SEQUENCE emp_seq;
INSERT INTO employees VALUES (emp_seq.NEXTVAL, ...);
-- 避免应用生成ID导致的主键冲突

-- 5. 分区减少锁竞争
-- 将大表按时间分区,删除旧数据时锁定特定分区
ALTER TABLE sales TRUNCATE PARTITION p_2023_q1;

8.7.2 死锁检测与处理

-- Oracle 自动检测并处理死锁
-- 会选择一个事务回滚(代价最小)

-- 查看死锁信息
SELECT * FROM v$session WHERE event = 'enq: TX - contention';

-- 查看最近死锁图
SELECT * FROM v$lock WHERE type = 'TX' AND request != 0;

-- 死锁追踪参数
SHOW PARAMETER deadlock

-- alert.log 中的死锁信息
-- ORA-00060: deadlock detected while waiting for resource

8.8 本章小结

✅ ACID 特性:原子性、一致性、隔离性、持久性
✅ 并发问题:脏读、不可重复读、幻读、更新丢失
✅ 隔离级别:READ COMMITTED(默认)、SERIALIZABLE、READ ONLY
✅ 锁类型:TX(行级)、TM(表级)
✅ MVCC:通过 Undo 段实现一致性读取
✅ 自治事务:独立于主事务的子事务
✅ 锁优化:减小事务范围、批量操作、顺序访问

📖 下章预告:性能优化实战

下一步:学习 第九章:性能优化实战