Oracle 事务与锁机制详解

Oracle 事务与锁机制详解

适用版本:Oracle Database 11g / 12c / 19c / 23ai 阅读基础:了解 SQL 基本操作、Oracle 多版本读一致性 文档版本:v1.0 / 2026-07


目录


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]:

  1. 在 Redo Log Buffer 中写入 commit 标记
  2. LGWR 将 Redo Log Buffer 刷到 Redo Log 文件
  3. 释放 Undo 段中持有的事务槽
  4. 释放该事务持有的所有行锁
  5. 返回 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 锁模式

模式全称缩写说明
0NoneNL无锁
1NullNULL特殊情况
2Row ShareRS / SS行共享,允许其他事务加 RS/RX/S/SS
3Row ExclusiveRX / SSX行独占,允许 RS/RX
4ShareS共享,允许 RS/S
5Share Row ExclusiveSRX / SSX共享行独占,允许 RS
6ExclusiveX独占,不允许其他锁

显式 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 死锁的常见原因

  1. 不同事务以不同顺序更新相同行

    -- 事务 A:先 id=1 再 id=2
    -- 事务 B:先 id=2 再 id=1
  2. 外键无索引

    -- 子表外键未建索引
    -- 父表更新会锁定整个子表
  3. ITL 等待导致的死锁

    两个事务都需要 ITL 槽,但块中 ITL 已满且无空闲空间分配新 ITL
  4. 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 字段含义

类型说明
TMDML 表锁
TX事务行锁
UL用户自定义锁(DBMS_LOCK)
MR媒体恢复锁
RTRedo Thread 锁
CF控制文件锁

lmode/request 字段含义

数值模式
0None
1Null
2Row Share (RS)
3Row Exclusive (RX)
4Share (S)
5Share Row Exclusive (SRX)
6Exclusive (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)