Oracle 事务与锁机制详解
Oracle 事务与锁机制详解
适用版本:Oracle Database 11g / 12c / 19c / 23ai 阅读基础:了解 SQL 基本操作、Oracle 多版本读一致性 文档版本:v1.0 / 2026-07
目录
- 1. 概述:事务与并发控制
- 2. 事务的 ACID 属性与 Oracle 实现
- 3. Oracle 锁的分类
- 4. DML 锁(DML Locks)
- 5. DDL 锁(DDL Locks)
- 6. 闩锁(Latch)与 Mutex
- 7. 锁的模式与兼容性矩阵
- 8. 锁等待与阻塞链分析
- 9. 死锁(Deadlock)
- 10. 事务隔离级别
- 11. 相关视图与诊断
- 12. 常见坑与排错
- 13. 最佳实践
- 14. 参考资料
1. 概述:事务与并发控制
在多用户数据库中,并发控制是保证数据一致性的核心机制。Oracle 通过多版本读一致性(MVCC) + 行级锁实现高并发能力[1][2]。
核心设计原则:
- 读不阻塞写:查询不会阻塞 DML
- 写不阻塞读:DML 不会阻塞查询(通过 Undo 提供 CR 块)
- 写写串行:同一行的并发修改必须串行执行(行锁)
- 极少锁开销:行锁开销极小,不依赖内存资源
2. 事务的 ACID 属性与 Oracle 实现
| 属性 | 含义 | Oracle 实现 |
|---|---|---|
| Atomicity(原子性) | 事务要么全部成功,要么全部回滚 | Undo 数据 + COMMIT/ROLLBACK |
| Consistency(一致性) | 事务前后数据保持一致状态 | 约束、触发器、应用逻辑 |
| Isolation(隔离性) | 并发事务互不干扰 | MVCC + 锁机制 |
| Durability(持久性) | COMMIT 后数据永久保存 | Redo Log + LGWR |
事务生命周期:
-- 开始事务(隐式)
INSERT INTO employees VALUES (1, 'Tom');
UPDATE employees SET salary=5000 WHERE id=1;
-- 提交(COMMIT)
COMMIT;
-- 此时变更持久化
-- 或回滚(ROLLBACK)
ROLLBACK;
-- 此时所有变更被撤销(使用 Undo)
COMMIT 内部操作[3]:
- 在 Redo Log Buffer 中写入 commit 标记
- LGWR 将 Redo Log Buffer 刷到 Redo Log 文件
- 释放 Undo 段中持有的事务槽
- 释放该事务持有的所有行锁
- 返回 commit 完成消息给客户端
注意:COMMIT 不写数据块到磁盘(DBWn 异步写入)。
3. Oracle 锁的分类
Oracle 锁可分为以下层次[1]:
Oracle Lock
│
├── DML Locks(数据锁)
│ ├── TM 锁(Table Modification)
│ └── TX 锁(Transaction eXclusive)
│
├── DDL Locks(字典锁)
│ ├── Exclusive DDL Lock
│ ├── Shared DDL Lock
│ └── Breakable Parse Lock
│
├── Internal Locks/Latches(内部锁/闩锁)
│ ├── Latch(闩锁,轻量级)
│ ├── Mutex(互斥量,更轻量)
│ └── Internal Lock(如 PCM 锁、Queue 锁)
│
└── PCM Locks(RAC Cache Fusion 锁)
锁持续时间:
| 锁类型 | 持续时间 |
|---|---|
| DML 锁 | 整个事务(直到 COMMIT 或 ROLLBACK) |
| DDL 锁 | DDL 语句执行期间 |
| Latch/Mutex | 极短(微秒级) |
4. DML 锁(DML Locks)
DML 锁用于保护数据并发修改,是应用开发中最常接触的锁。
4.1 TM 锁(Table Lock)
作用:保护表结构在 DML 期间不被 DDL 修改[2]。
触发场景:
-- INSERT 时获取表的 TM 锁(SS 模式)
INSERT INTO employees VALUES (1, 'Tom');
-- UPDATE 时获取表的 TM 锁(SS 模式)
UPDATE employees SET salary=5000 WHERE id=1;
-- DELETE 时获取表的 TM 锁(SS 模式)
DELETE FROM employees WHERE id=1;
-- 显式加锁
LOCK TABLE employees IN ROW EXCLUSIVE MODE;
LOCK TABLE employees IN EXCLUSIVE MODE;
TM 锁模式:
| 模式 | 全称 | 缩写 | 说明 |
|---|---|---|---|
| 0 | None | NL | 无锁 |
| 1 | Null | NULL | 特殊情况 |
| 2 | Row Share | RS / SS | 行共享,允许其他事务加 RS/RX/S/SS |
| 3 | Row Exclusive | RX / SSX | 行独占,允许 RS/RX |
| 4 | Share | S | 共享,允许 RS/S |
| 5 | Share Row Exclusive | SRX / SSX | 共享行独占,允许 RS |
| 6 | Exclusive | X | 独占,不允许其他锁 |
显式 LOCK TABLE 用法:
-- 行共享模式:允许其他事务查询、插入、更新、删除
LOCK TABLE employees IN ROW SHARE MODE;
-- 行独占模式:允许其他事务查询、插入、更新、删除(默认 DML 模式)
LOCK TABLE employees IN ROW EXCLUSIVE MODE;
-- 共享模式:允许其他事务查询,但不能 DML
LOCK TABLE employees IN SHARE MODE;
-- 共享行独占:允许查询,不允许其他 DML(除自身)
LOCK TABLE employees IN SHARE ROW EXCLUSIVE MODE;
-- 独占模式:其他事务只能查询
LOCK TABLE employees IN EXCLUSIVE MODE;
4.2 TX 锁(Transaction Lock)
作用:标识一个事务正在修改某行数据,保护行级并发[2][4]。
特性:
- 行级锁:每个 TX 锁对应一个事务,不对应一行
- 基于数据块:锁信息存储在数据块头部的 ITL 槽中
- 不消耗内存:与行数无关,开销极小
- 排他性:同一行同一时刻只能有一个 TX 锁
工作原理:
事务 T1:UPDATE employees SET salary=5000 WHERE id=1
1. 找到 id=1 的行所在数据块
2. 在数据块头部的 ITL 槽中记录事务 ID(XID)
3. 在行头部标记 lock byte = ITL slot number
4. 在内存中创建 TX 锁资源(XID -> Undo 段+事务槽)
数据块 ITL 与行锁:
数据块结构:
+--------------------------------+
| Block Header |
| +---------------------------+ |
| | ITL 0x01: XID=NULL | | 空闲槽位
| +---------------------------+ |
| | ITL 0x02: XID=0x0001.012 | | T1 事务占用
| +---------------------------+ |
+--------------------------------+
| Row Directory |
| +---------------------------+ |
| | Row 1: lock_byte=0x02 | | 指向 ITL 0x02
| +---------------------------+ |
| | Row 2: lock_byte=0x00 | | 未锁定
| +---------------------------+ |
+--------------------------------+
TX 锁等待:
- 事务 T2 想修改 Row 1(已被 T1 锁定)
- T2 在 Row 1 上发现 lock_byte=0x02,指向 ITL 0x02
- 从 ITL 0x02 找到 T1 的 XID
- 在内存中找到 T1 持有的 TX 锁资源
- T2 排队等待 T1 的 TX 锁释放
等待事件:
enq: TX - row lock contention -- 行锁等待
5. DDL 锁(DDL Locks)
DDL 锁保护数据字典结构,DDL 操作时自动获取[2]:
5.1 独占 DDL 锁(Exclusive DDL Lock)
-- 修改表结构时获取独占 DDL 锁
ALTER TABLE employees ADD (email VARCHAR2(100));
-- 此时其他 DDL 和 DML 都被阻塞
5.2 共享 DDL 锁(Shared DDL Lock)
-- 创建视图、存储过程时获取共享 DDL 锁
CREATE VIEW emp_view AS SELECT * FROM employees;
-- 其他事务可以查询,但不能修改表结构
5.3 可中断解析锁(Breakable Parse Lock)
-- 库缓存中对象间的依赖关系
-- 例如:emp_view 依赖 employees 表
-- 当 employees 表结构变更时,emp_view 的解析锁被打破,标记为 INVALID
相关视图:
SELECT session_id, owner, name, type, mode_held, mode_requested
FROM dba_ddl_locks
WHERE owner = USER;
6. 闩锁(Latch)与 Mutex
Latch 和 Mutex 是 Oracle 内部轻量级同步机制,保护 SGA 中的共享数据结构[1][4]:
6.1 Latch(闩锁)
特性:
- 极短持有:微秒级
- 不排队:先到先得,无 FIFO 队列
- 自旋等待:失败后短暂自旋再重试
- 大量类型:shared pool latch、cache buffers chains latch 等
Latch 等待事件:
latch: shared pool -- 共享池闩锁
latch: cache buffers chains -- Buffer Cache 链闩锁
latch: library cache -- 库缓存闩锁
latch free -- 通用闩锁等待
查询:
SELECT name, gets, misses, spin_gets, wait_time
FROM v$latch
WHERE misses > 0
ORDER BY misses DESC;
6.2 Mutex(互斥量)
特性:
- 更轻量:比 Latch 开销更小
- 代码路径短:直接在操作系统层实现
- 每个对象独立:每个 cursor、cache 对象有独立 mutex
- 11g+ 大量替代 Latch:Library Cache mutex、Cursor mutex
Mutex 等待事件:
cursor: pin S -- 共享 pin cursor
cursor: pin X -- 独占 pin cursor
cursor: mutex S -- 共享 mutex
cursor: mutex X -- 独占 mutex
library cache: mutex X -- 库缓存 mutex
6.3 Latch/Mutex 优化
减少硬解析:
-- 使用绑定变量
EXECUTE IMMEDIATE 'SELECT * FROM emp WHERE id=:1' USING v_id;
-- 而不是
EXECUTE IMMEDIATE 'SELECT * FROM emp WHERE id=' || v_id;
调整 Shared Pool 大小:
-- 监控 Shared Pool 闩锁
SELECT name, gets, misses
FROM v$latch
WHERE name LIKE 'shared pool%';
-- 如果 misses 高,增大 Shared Pool
ALTER SYSTEM SET shared_pool_size=2G SCOPE=BOTH;
7. 锁的模式与兼容性矩阵
锁模式兼容性:
| 已持 有\请求 | RS (2) | RX (3) | S (4) | SRX (5) | X (6) |
|---|---|---|---|---|---|
| RS (2) | ✓ | ✓ | ✓ | ✓ | ✗ |
| RX (3) | ✓ | ✓ | ✗ | ✗ | ✗ |
| S (4) | ✓ | ✗ | ✓ | ✗ | ✗ |
| SRX (5) | ✓ | ✗ | ✗ | ✗ | ✗ |
| X (6) | ✗ | ✗ | ✗ | ✗ | ✗ |
说明:
- ✓ 表示兼容(可同时持有)
- ✗ 表示不兼容(需等待)
- SS 模式最宽松,X 模式最严格
8. 锁等待与阻塞链分析
8.1 找到阻塞者
-- 方法 1:查找阻塞会话
SELECT
blocking_session,
blocking_session_serial#,
session_id,
session_serial#,
event,
sql_id
FROM v$session
WHERE blocking_session IS NOT NULL;
-- 方法 2:使用 v$lock 找锁关系
SELECT
s1.username || '@' || s1.machine AS waiter,
s1.sid AS waiter_sid,
s1.serial# AS waiter_serial,
s2.username || '@' || s2.machine AS blocker,
s2.sid AS blocker_sid,
s2.serial# AS blocker_serial,
l.type,
l.lmode,
l.request
FROM v$lock l1
JOIN v$session s1 ON s1.sid = l1.sid
JOIN v$lock l2 ON l1.id1 = l2.id1 AND l1.id2 = l2.id2 AND l1.request <> 0
JOIN v$session s2 ON s2.sid = l2.sid AND l2.lmode <> 0
WHERE l1.type = 'TX';
8.2 查看锁等待事件
-- 实时锁等待
SELECT
event,
sid,
p1, p2, p3,
wait_time,
seconds_in_wait
FROM v$session_wait
WHERE event LIKE 'enq: TX%'
ORDER BY seconds_in_wait DESC;
-- 历史锁等待统计
SELECT
event,
total_waits,
time_waited,
round(time_waited/NULLIF(total_waits,0),2) AS avg_wait_ms
FROM v$system_event
WHERE event LIKE 'enq:%'
ORDER BY time_waited DESC;
8.3 阻塞链可视化
-- 树形查询阻塞链
WITH lock_tree AS (
SELECT
level AS lvl,
sid,
serial#,
username,
blocking_session,
event
FROM v$session
START WITH blocking_session IS NULL
CONNECT BY PRIOR sid = blocking_session
)
SELECT
LPAD(' ', (lvl-1)*4) || '→ ' || TO_CHAR(sid) AS session_chain,
username,
blocking_session,
event
FROM lock_tree
WHERE blocking_session IS NOT NULL OR lvl > 1
ORDER BY lvl, sid;
8.4 终止阻塞会话
-- 找到阻塞会话
SELECT sid, serial#, username, program, machine
FROM v$session
WHERE sid IN (
SELECT DISTINCT blocking_session
FROM v$session
WHERE blocking_session IS NOT NULL
);
-- 终止会话
ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;
-- 或断开会话连接(更优雅)
ALTER SYSTEM DISCONNECT SESSION 'sid,serial#' POST_TRANSACTION;
9. 死锁(Deadlock)
9.1 死锁的产生
经典死锁场景:
事务 A 事务 B
───────── ─────────
UPDATE t SET ... WHERE id=1;
UPDATE t SET ... WHERE id=2;
UPDATE t SET ... WHERE id=2;
-- 等待 B 释放 id=2 锁 UPDATE t SET ... WHERE id=1;
-- 等待 A 释放 id=1 锁
→ 死锁!双方互相等待对方持有的锁
9.2 Oracle 的死锁检测
Oracle 自动检测死锁(默认每 1-3 秒检测一次),并主动终止其中一个事务以打破死循环[4]:
ORA-00060: deadlock detected while waiting for resource
警报日志记录:
Sun Jul 21 10:23:15 2026
ORA-00060: Deadlock detected. More info in file
/u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_1234.trc.
Trace 文件内容:
Deadlock graph:
---------Blocker(s)------- ---------Waiter(s)------
Resource Name process session holds waits process session holds waits
TX-00010014-00001234 12 15 X 16 18 X
TX-00020028-00005678 16 18 X 12 15 X
session 15 did NOT wait for session 18:
Rows waited on:
Session 15: obj - rowid = 00001234 - AAA...
(dictionary objn - 4660, file - 4, block - 240, slot - 0)
Session 18: obj - rowid = 00001234 - AAA...
(dictionary objn - 4660, file - 4, block - 240, slot - 1)
9.3 死锁的常见原因
-
不同事务以不同顺序更新相同行
-- 事务 A:先 id=1 再 id=2 -- 事务 B:先 id=2 再 id=1 -
外键无索引
-- 子表外键未建索引 -- 父表更新会锁定整个子表 -
ITL 等待导致的死锁
两个事务都需要 ITL 槽,但块中 ITL 已满且无空闲空间分配新 ITL -
Bitmap 索引冲突
不同行但相同键值的 INSERT 互相阻塞
9.4 死锁分析与解决
-- 1. 查看 trace 文件
! cat /u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_*.trc | grep -A 30 "Deadlock graph"
-- 2. 找到涉及的对象
SELECT object_name, object_type
FROM dba_objects
WHERE object_id = 4660;
-- 3. 找到涉及的行
SELECT * FROM employees WHERE rowid = 'AAA...';
-- 4. 找到执行 SQL
SELECT sql_text
FROM v$sql
WHERE sql_id = '<从 trace 中找到>';
10. 事务隔离级别
Oracle 支持的事务隔离级别[1]:
| 隔离级别 | 名称 | 行为 |
|---|---|---|
| READ COMMITTED | 读已提交(默认) | 语句级读一致性,不显示未提交数据 |
| SERIALIZABLE | 串行化 | 事务级读一致性,不允许修改事务开始后的数据 |
| READ ONLY | 只读 | 仅查询,不允许 DML |
-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET TRANSACTION READ ONLY;
-- 修改会话默认隔离级别
ALTER SESSION SET ISOLATION_LEVEL=SERIALIZABLE;
READ COMMITTED 详解:
-- T1
UPDATE employees SET salary=5000 WHERE id=1;
-- 不提交
-- T2
SELECT salary FROM employees WHERE id=1;
-- 返回旧值(来自 Undo 的 CR 块)
-- Oracle 通过 SCN 机制保证语句级一致性
-- T2
UPDATE employees SET salary=salary+100 WHERE id=1;
-- 阻塞,等待 T1 释放行锁
SERIALIZABLE 详解:
-- T2 设置为 SERIALIZABLE
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- T1 提交后
COMMIT;
-- T2 再查询
SELECT salary FROM employees WHERE id=1;
-- 仍返回旧值(事务级一致性)
-- T2 尝试修改
UPDATE employees SET salary=salary+100 WHERE id=1;
-- ORA-08177: can't serialize access for this transaction
11. 相关视图与诊断
11.1 V$LOCK
SELECT
sid,
type, -- 锁类型(TM/TX/UL 等)
id1, -- 标识符 1
id2, -- 标识符 2
lmode, -- 持有锁模式(0-6)
request, -- 请求锁模式(0-6)
block, -- 是否阻塞其他会话(0/1)
ctime -- 持有时间(秒)
FROM v$lock
WHERE type IN ('TM', 'TX')
ORDER BY block DESC, ctime DESC;
type 字段含义:
| 类型 | 说明 |
|---|---|
| TM | DML 表锁 |
| TX | 事务行锁 |
| UL | 用户自定义锁(DBMS_LOCK) |
| MR | 媒体恢复锁 |
| RT | Redo Thread 锁 |
| CF | 控制文件锁 |
lmode/request 字段含义:
| 数值 | 模式 |
|---|---|
| 0 | None |
| 1 | Null |
| 2 | Row Share (RS) |
| 3 | Row Exclusive (RX) |
| 4 | Share (S) |
| 5 | Share Row Exclusive (SRX) |
| 6 | Exclusive (X) |
11.2 V$SESSION
SELECT
sid,
serial#,
username,
machine,
program,
status,
event,
blocking_session,
blocking_session_serial#,
sql_id,
sql_child_number
FROM v$session
WHERE username IS NOT NULL
AND type <> 'BACKGROUND';
11.3 DBA_BLOCKERS / DBA_WAITERS
-- 阻塞者
SELECT * FROM dba_blockers;
-- 等待者
SELECT * FROM dba_waiters;
11.4 V$LOCKED_OBJECT
SELECT
lo.session_id,
lo.oracle_username,
lo.os_user_name,
lo.locked_mode,
do.object_name,
do.object_type
FROM v$locked_object lo
JOIN dba_objects do ON lo.object_id = do.object_id
ORDER BY lo.session_id;
11.5 V$TRANSACTION
SELECT
addr,
xidusn, -- Undo 段号
xidslot, -- 事务槽号
xidsqn, -- 序列号
status,
start_time,
used_ublk, -- 使用的 Undo 块数
used_urec -- 使用的 Undo 记录数
FROM v$transaction;
11.6 综合查询:找出谁在阻塞谁
SELECT
bs.username AS blocker_user,
bs.sid AS blocker_sid,
bs.serial# AS blocker_serial,
bs.machine AS blocker_machine,
bs.program AS blocker_program,
ws.username AS waiter_user,
ws.sid AS waiter_sid,
ws.serial# AS waiter_serial,
ws.event AS waiter_event,
ws.seconds_in_wait AS waited_seconds
FROM v$session bs
JOIN v$session ws ON ws.blocking_session = bs.sid
AND ws.blocking_session_serial# = bs.serial#
WHERE bs.username IS NOT NULL
ORDER BY ws.seconds_in_wait DESC;
12. 常见坑与排错
12.1 ORA-00054:资源正忙
现象:
ALTER TABLE employees ADD (email VARCHAR2(100));
-- ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
原因:表上有未提交事务,DDL 无法获取独占 DDL 锁。
修复:
-- 1. 找到持有锁的会话
SELECT session_id, oracle_username, locked_mode
FROM v$locked_object
WHERE object_id = (SELECT object_id FROM dba_objects WHERE object_name='EMPLOYEES');
-- 2. 等待事务提交(推荐)
-- 或终止会话
ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;
-- 3. 设置 DDL_LOCK_TIMEOUT 等待
ALTER SESSION SET DDL_LOCK_TIMEOUT=60;
ALTER TABLE employees ADD (email VARCHAR2(100));
12.2 ORA-01555:快照过旧
现象:长查询报 ORA-01555: snapshot too old。
原因:查询开始后,需要的 Undo 数据已被覆盖。
修复:
-- 1. 增大 Undo 表空间
ALTER TABLESPACE undotbs1 ADD DATAFILE '/u02/undo02.dbf' SIZE 1G;
-- 2. 增加 undo_retention
ALTER SYSTEM SET undo_retention=3600 SCOPE=BOTH;
-- 3. 启用 Undo 保留保证
ALTER TABLESPACE undotbs1 RETENTION GUARANTEE;
-- 4. 优化长查询(分批处理)
12.3 外键无索引导致锁升级
现象:父表删除/更新时,子表整表被锁。
原因:外键无索引,Oracle 通过表级锁保证一致性。
修复:
-- 查找无索引的外键
SELECT
c.table_name,
c.constraint_name,
cc.column_name
FROM user_constraints c
JOIN user_cons_columns cc ON c.constraint_name = cc.constraint_name
WHERE c.constraint_type = 'R'
AND NOT EXISTS (
SELECT 1 FROM user_ind_columns ic
WHERE ic.table_name = c.table_name
AND ic.column_name = cc.column_name
);
-- 为外键创建索引
CREATE INDEX idx_emp_dept_id ON employees(dept_id);
12.4 enq: TX - row lock contention
现象:行锁等待事件频繁。
排查:
-- 找出锁等待的 SQL
SELECT
sql.sql_id,
sql.sql_text,
sess.username,
sess.machine,
sess.event,
sess.seconds_in_wait
FROM v$sql sql
JOIN v$session sess ON sql.sql_id = sess.sql_id
WHERE sess.event = 'enq: TX - row lock contention';
-- 找出阻塞者
SELECT
blocker.sid AS blocker_sid,
blocker.username AS blocker_user,
waiter.sid AS waiter_sid,
waiter.username AS waiter_user
FROM v$session blocker
JOIN v$session waiter ON waiter.blocking_session = blocker.sid
WHERE waiter.event = 'enq: TX - row lock contention';
修复:
- 优化应用,让事务尽快提交
- 检查是否同一热点行被频繁更新
- 考虑使用乐观锁版本号机制
- 必要时终止阻塞会话
12.5 enq: TX - allocate ITL entry
现象:高并发场景出现 ITL 等待。
原因:数据块中 ITL 槽位不足,且块满无法动态分配新 ITL。
修复:
-- 1. 查找等待
SELECT event, total_waits, time_waited
FROM v$system_event
WHERE event = 'enq: TX - allocate ITL entry';
-- 2. 增大表的 INITRANS
ALTER TABLE high_concurrent_tab INITRANS 20;
-- 3. 重建表使新参数生效
ALTER TABLE high_concurrent_tab MOVE;
ALTER INDEX idx_name REBUILD;
12.6 死锁频繁发生
排查步骤:
# 1. 查看 alert log 找死锁 trace 文件
grep "ORA-00060" $ORACLE_BASE/diag/rdbms/orcl/orcl/trace/alert_orcl.log
# 2. 分析 trace 文件
cat /u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_*.trc
# 3. 找出死锁涉及的 SQL
修复:
-- 1. 检查外键是否有索引
SELECT table_name, constraint_name
FROM user_constraints
WHERE constraint_type='R';
-- 2. 检查应用是否以固定顺序更新多行
-- 例如:始终按 id 升序更新
-- 3. 缩短事务持续时间
-- 避免在事务中包含用户交互
-- 4. 检查是否使用了 bitmap 索引
SELECT index_name, index_type
FROM user_indexes
WHERE index_type='BITMAP';
13. 最佳实践
13.1 事务设计原则
-- 1. 事务尽量短小
BEGIN
INSERT INTO orders ...;
INSERT INTO order_items ...;
COMMIT;
END;
-- 2. 避免长事务(特别是包含用户交互的事务)
-- 错误示例:
INSERT INTO temp VALUES (...);
-- 等待用户输入
-- 等待用户输入
COMMIT;
-- 3. 合理使用 savepoint
SAVEPOINT step1;
INSERT INTO ...;
SAVEPOINT step2;
UPDATE ...;
-- 出错时回滚到指定点
ROLLBACK TO step1;
13.2 锁顺序一致性
-- 应用层统一约定:按主键升序加锁
-- 事务 A 和事务 B 都先锁 id=1 再锁 id=2,避免死锁
-- 应用层示例
PROCEDURE update_two_rows(p_id1 NUMBER, p_id2 NUMBER) IS
v_min_id NUMBER := LEAST(p_id1, p_id2);
v_max_id NUMBER := GREATEST(p_id1, p_id2);
BEGIN
-- 按固定顺序加锁
UPDATE employees SET ... WHERE id = v_min_id;
UPDATE employees SET ... WHERE id = v_max_id;
COMMIT;
END;
13.3 外键必加索引
-- 创建外键后立即创建索引
ALTER TABLE employees ADD CONSTRAINT fk_emp_dept
FOREIGN KEY (dept_id) REFERENCES departments(dept_id);
CREATE INDEX idx_emp_dept_id ON employees(dept_id);
13.4 高并发表增大 INITRANS
CREATE TABLE high_concurrent_tab (
id NUMBER,
data VARCHAR2(100)
) INITRANS 20 PCTFREE 20;
-- 对应索引也增大
CREATE INDEX idx_tab_id ON high_concurrent_tab(id) INITRANS 20;
13.5 使用乐观锁避免热点
-- 表中加入版本号字段
ALTER TABLE products ADD (version NUMBER DEFAULT 0);
-- 更新时检查版本
UPDATE products
SET price = 100, version = version + 1
WHERE id = 1 AND version = 5;
-- 如果 affected_rows = 0,说明已被其他事务修改
13.6 避免使用 SELECT … FOR UPDATE 长时间持锁
-- 错误:长时间持锁
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- 长时间处理
UPDATE orders SET status = 'processed' WHERE id = 1;
COMMIT;
-- 正确:先查询,更新时锁定
SELECT * FROM orders WHERE id = 1;
-- 处理
UPDATE orders SET status = 'processed' WHERE id = 1 AND status = 'pending';
13.7 监控锁等待
-- 创建定期监控脚本
SELECT
COUNT(*) AS blocking_sessions,
SUM(seconds_in_wait) AS total_wait_seconds
FROM v$session
WHERE blocking_session IS NOT NULL;
-- 阈值告警
-- blocking_sessions > 5 时告警
-- total_wait_seconds > 300 时告警
13.8 处理死锁的标准流程
-- 1. 死锁被 Oracle 自动解决,一个事务收到 ORA-00060
-- 2. 应用捕获异常后 ROLLBACK 整个事务
-- 3. 应用记录死锁日志,便于分析
-- 4. DBA 收集 trace 文件分析根因
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -60 THEN
log_deadlock(SQLERRM, ...);
ROLLBACK;
-- 可重试事务
END IF;
14. 参考资料
[1] Oracle Database Concepts 19c, “Data Concurrency and Consistency” https://docs.oracle.com/en/database/oracle/oracle-database/19/cncpt/data-concurrency-and-consistency.html
[2] Oracle Database Administrator’s Guide 19c, “Managing Locks” https://docs.oracle.com/en/database/oracle/oracle-database/19/admin/managing-locks.html
[3] AskTOM, “Transaction and Lock Internals” https://asktom.oracle.com/pls/apex/f?p=100:1:0
[4] 墨天轮,“Oracle 锁机制与死锁分析深度解析” https://www.modb.pro/db/1759656813559025664
[5] Oracle Support Note 102925.1, “Deadlock Troubleshooting” https://support.oracle.com/epmos/faces/DocumentDisplay?id=102925.1
[6] Tom Kyte, “Expert Oracle Database Architecture”(Apress, 2010)
[7] Jonathan Lewis, “Oracle Core: Essential Internals for DBAs and Developers”(Apress, 2011)