Skip to content

Oracle 锁与并发

锁概述

在数据库系统中,锁是用于控制对共享资源的访问的机制,它可以确保多个用户同时访问数据库时的数据一致性和完整性。Oracle 提供了强大的锁机制,用于处理并发访问问题。

锁的作用

  • 保证数据一致性:确保多个用户同时修改数据时的数据一致性
  • 防止丢失更新:防止一个用户的更新被另一个用户的更新覆盖
  • 防止脏读:防止读取未提交的数据
  • 防止不可重复读:防止同一事务中多次读取同一数据时得到不同的结果
  • 防止幻读:防止同一事务中多次查询时结果集的行数发生变化

锁的类型

Oracle 锁可以分为以下几类:

  • DML 锁:用于保护数据的完整性,包括行级锁、表级锁和意向锁
  • DDL 锁:用于保护数据库对象的结构,包括排他 DDL 锁、共享 DDL 锁和可中断解析锁
  • 内部锁:用于保护内部数据库结构,如数据文件、内存结构等

DML 锁

DML 锁(数据操纵语言锁)是用于保护数据完整性的锁,它在执行 INSERT、UPDATE、DELETE 和 SELECT FOR UPDATE 语句时自动获取。

1. 行级锁(TX 锁)

行级锁是最细粒度的锁,它只锁定表中的单行数据。行级锁在执行 DML 语句时自动获取,并且只在事务期间保持。

适用场景

  • 当多个用户同时修改表中的不同行时
  • 当需要最高级别的并发访问时

示例

sql
-- 用户 1 更新行,获取行级锁
UPDATE employees SET salary = salary * 1.1 WHERE employee_id = 100;

-- 用户 2 更新同一行,会被阻塞,直到用户 1 提交或回滚事务
UPDATE employees SET salary = salary * 1.2 WHERE employee_id = 100;

2. 表级锁(TM 锁)

表级锁是用于保护表结构的锁,它在执行 DML 语句时自动获取,并且在事务期间保持。

表级锁类型

  • 共享锁(S 锁):允许其他事务读取表中的数据,但不允许修改
  • 排他锁(X 锁):不允许其他事务读取或修改表中的数据
  • 行共享锁(RS 锁):允许其他事务读取表中的数据,也允许获取行级锁
  • 行排他锁(RX 锁):允许其他事务读取表中的数据,也允许获取行级锁,但不允许获取排他锁
  • 共享行排他锁(SRX 锁):允许其他事务读取表中的数据,但不允许修改或获取排他锁

示例

sql
-- 锁定表,获取排他锁
LOCK TABLE employees IN EXCLUSIVE MODE;

-- 锁定表,获取共享锁
LOCK TABLE employees IN SHARE MODE;

-- 锁定表,获取行共享锁
LOCK TABLE employees IN ROW SHARE MODE;

-- 锁定表,获取行排他锁
LOCK TABLE employees IN ROW EXCLUSIVE MODE;

-- 锁定表,获取共享行排他锁
LOCK TABLE employees IN SHARE ROW EXCLUSIVE MODE;

3. 意向锁

意向锁是表级锁的一种,它用于指示事务打算在表中的行上获取锁。意向锁包括意向共享锁(IS)和意向排他锁(IX)。

作用

  • 提高锁检查的效率
  • 防止死锁
  • 确保锁的兼容性

DDL 锁

DDL 锁(数据定义语言锁)是用于保护数据库对象结构的锁,它在执行 DDL 语句时自动获取。

1. 排他 DDL 锁(XDL 锁)

排他 DDL 锁用于保护数据库对象的结构,它防止其他事务修改同一对象的结构。

适用场景

  • 执行 CREATE、ALTER、DROP 语句时
  • 执行 TRUNCATE 语句时

2. 共享 DDL 锁(SDL 锁)

共享 DDL 锁用于保护数据库对象的结构,它允许其他事务读取同一对象的结构,但不允许修改。

适用场景

  • 执行 SELECT 语句查询数据字典时
  • 执行 DESC 命令时

3. 可中断解析锁(BR 锁)

可中断解析锁用于保护 SQL 语句的解析过程,它允许在解析过程中被中断。

适用场景

  • 解析 SQL 语句时
  • 编译 PL/SQL 程序时

事务隔离级别

事务隔离级别定义了一个事务如何与其他事务隔离,它决定了一个事务可以看到其他事务的哪些修改。Oracle 支持以下事务隔离级别:

1. 读未提交(READ UNCOMMITTED)

读未提交是最低的隔离级别,它允许事务读取其他事务未提交的数据。

特点

  • 可能导致脏读、不可重复读和幻读
  • 并发性能最高

2. 读已提交(READ COMMITTED)

读已提交是 Oracle 的默认隔离级别,它只允许事务读取其他事务已提交的数据。

特点

  • 防止脏读
  • 可能导致不可重复读和幻读
  • 并发性能较高

3. 可重复读(REPEATABLE READ)

可重复读确保同一事务中多次读取同一数据时得到相同的结果。

特点

  • 防止脏读和不可重复读
  • 可能导致幻读
  • 并发性能较低

4. 串行化(SERIALIZABLE)

串行化是最高的隔离级别,它确保同一事务中多次查询时结果集保持一致。

特点

  • 防止脏读、不可重复读和幻读
  • 并发性能最低

设置事务隔离级别

sql
-- 设置事务隔离级别为读已提交
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置事务隔离级别为可重复读
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 设置事务隔离级别为串行化
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

锁等待与死锁

1. 锁等待

锁等待是指一个事务等待另一个事务释放锁的状态。当一个事务尝试获取另一个事务持有的锁时,如果锁不兼容,就会发生锁等待。

监控锁等待

sql
-- 查看锁等待情况
SELECT 
  s.sid,
  s.serial#,
  s.username,
  s.machine,
  s.program,
  l.type,
  l.id1,
  l.id2,
  l.lmode,
  l.request,
  l.ctime
FROM 
  v$session s,
  v$lock l
WHERE 
  s.sid = l.sid
  AND l.request > 0;

2. 死锁

死锁是指两个或多个事务互相等待对方释放锁的状态。当事务 A 持有事务 B 需要的锁,而事务 B 持有事务 A 需要的锁时,就会发生死锁。

死锁处理

  • Oracle 会自动检测死锁,并选择一个事务作为牺牲品,回滚该事务以解除死锁
  • 死锁检测由 Oracle 的死锁检测器定期执行

监控死锁

sql
-- 查看死锁信息
SELECT 
  d.deadlock_graph
FROM 
  v$diag_alert_ext d
WHERE 
  d.message_text LIKE '%deadlock%'
ORDER BY 
  d.originating_timestamp DESC;

3. 避免死锁的方法

  • 保持事务简短:尽量减少事务的执行时间,减少持有锁的时间
  • 统一访问顺序:按照相同的顺序访问表和行,避免循环等待
  • 避免锁定不必要的数据:只锁定需要修改的数据
  • 使用较低的隔离级别:如读已提交,减少锁的持有时间
  • 使用乐观锁:通过版本号或时间戳实现并发控制

并发控制机制

Oracle 提供了多种并发控制机制,用于处理并发访问问题:

1. 多版本并发控制(MVCC)

多版本并发控制是 Oracle 用于处理并发访问的主要机制,它通过保存数据的多个版本,允许读取操作不阻塞写入操作,写入操作也不阻塞读取操作。

工作原理

  • 当执行 DML 语句时,Oracle 会为修改的行创建新的版本
  • 读取操作会读取数据的快照(snapshot),而不是当前版本
  • 写入操作会修改数据的当前版本,并创建新的版本

特点

  • 提供了高并发访问能力
  • 避免了读-写阻塞和写-读阻塞
  • 实现了读已提交和可重复读隔离级别

2. 自动锁定

Oracle 在执行 DML 语句时自动获取锁,无需手动锁定。自动锁定确保了数据的完整性,同时提供了高并发访问能力。

自动锁定规则

  • 执行 INSERT、UPDATE、DELETE 语句时,获取行级锁和表级意向锁
  • 执行 SELECT FOR UPDATE 语句时,获取行级锁和表级意向锁
  • 执行 SELECT 语句时,不获取锁(除非使用 SERIALIZABLE 隔离级别)

3. 手动锁定

除了自动锁定外,Oracle 还允许手动锁定表和行,以满足特定的业务需求。

手动锁定表

sql
-- 锁定表,获取排他锁
LOCK TABLE employees IN EXCLUSIVE MODE;

-- 锁定表,获取共享锁
LOCK TABLE employees IN SHARE MODE;

手动锁定行

sql
-- 锁定行,获取行级锁
SELECT * FROM employees WHERE employee_id = 100 FOR UPDATE;

-- 锁定行,获取行级锁,等待 10 秒
SELECT * FROM employees WHERE employee_id = 100 FOR UPDATE WAIT 10;

-- 锁定行,获取行级锁,不等待
SELECT * FROM employees WHERE employee_id = 100 FOR UPDATE NOWAIT;

锁监控与管理

1. 监控锁状态

Oracle 提供了多个视图用于监控锁状态:

  • V$LOCK:显示当前持有或等待的锁
  • V$SESSION:显示当前会话的信息
  • V$SESSION_WAIT:显示当前会话等待的事件
  • DBA_BLOCKERS:显示阻塞其他会话的会话
  • DBA_DDL_LOCKS:显示 DDL 锁信息
  • DBA_DML_LOCKS:显示 DML 锁信息

监控锁状态示例

sql
-- 查看当前持有或等待的锁
SELECT 
  s.sid,
  s.serial#,
  s.username,
  s.machine,
  s.program,
  l.type,
  l.id1,
  l.id2,
  l.lmode,
  l.request,
  l.ctime
FROM 
  v$session s,
  v$lock l
WHERE 
  s.sid = l.sid;

-- 查看阻塞其他会话的会话
SELECT * FROM dba_blockers;

-- 查看被阻塞的会话
SELECT * FROM dba_waiters;

2. 解决锁等待

当发生锁等待时,可以采取以下措施解决:

  • 等待持有锁的事务完成:这是最简单的方法,但可能需要较长时间
  • 终止持有锁的会话:可以使用 ALTER SYSTEM KILL SESSION 命令终止持有锁的会话
  • 回滚持有锁的事务:可以使用 ALTER SYSTEM DISCONNECT SESSION 命令回滚持有锁的事务

终止会话示例

sql
-- 终止会话
ALTER SYSTEM KILL SESSION 'sid,serial#';

-- 终止会话,立即回滚
ALTER SYSTEM DISCONNECT SESSION 'sid,serial#' IMMEDIATE;

3. 锁优化

锁优化是提高数据库并发性能的重要手段,以下是一些锁优化的建议:

  • 减少锁的持有时间:尽量缩短事务的执行时间,减少持有锁的时间
  • 使用行级锁:行级锁的粒度最细,并发性能最高
  • 避免锁定不必要的数据:只锁定需要修改的数据
  • 使用较低的隔离级别:如读已提交,减少锁的持有时间
  • 使用乐观锁:通过版本号或时间戳实现并发控制
  • 避免长事务:长事务会持有锁很长时间,影响并发性能

并发访问最佳实践

1. 设计阶段

  • 设计简短的事务:尽量减少事务的执行时间,减少持有锁的时间
  • 设计合理的隔离级别:根据业务需求选择合适的隔离级别
  • 设计合理的索引:合理的索引可以减少锁的持有时间
  • 设计无锁的数据结构:如使用序列生成主键,避免主键冲突

2. 开发阶段

  • 编写高效的 SQL 语句:高效的 SQL 语句可以减少锁的持有时间
  • 避免长事务:尽量避免在事务中执行耗时的操作
  • 避免锁定整个表:尽量只锁定需要修改的行
  • 使用绑定变量:使用绑定变量可以减少硬解析,提高并发性能
  • 使用批量操作:批量操作可以减少锁的持有时间

3. 运维阶段

  • 监控锁状态:定期监控锁状态,及时发现锁等待和死锁
  • 调整数据库参数:如调整死锁检测间隔、锁超时时间等
  • 优化查询计划:优化查询计划可以减少锁的持有时间
  • 定期维护索引:定期维护索引可以提高查询性能,减少锁的持有时间

4. 性能优化阶段

  • 分析锁等待事件:使用 AWR 和 ASH 报告分析锁等待事件
  • 识别热点数据:识别频繁被锁定的数据,优化访问方式
  • 使用分区表:分区表可以减少锁的持有范围
  • 使用并行执行:并行执行可以提高查询性能,减少锁的持有时间

常见问题(FAQ)

1. 什么是锁等待?如何解决?

锁等待是指一个事务等待另一个事务释放锁的状态。解决方法包括:

  • 等待持有锁的事务完成
  • 终止持有锁的会话
  • 回滚持有锁的事务

2. 什么是死锁?如何避免?

死锁是指两个或多个事务互相等待对方释放锁的状态。避免方法包括:

  • 保持事务简短
  • 统一访问顺序
  • 避免锁定不必要的数据
  • 使用较低的隔离级别
  • 使用乐观锁

3. 如何监控锁状态?

可以使用以下视图监控锁状态:

  • V$LOCK:显示当前持有或等待的锁
  • V$SESSION:显示当前会话的信息
  • V$SESSION_WAIT:显示当前会话等待的事件
  • DBA_BLOCKERS:显示阻塞其他会话的会话
  • DBA_WAITERS:显示被阻塞的会话

4. 如何终止持有锁的会话?

可以使用 ALTER SYSTEM KILL SESSION 命令终止持有锁的会话:

sql
ALTER SYSTEM KILL SESSION 'sid,serial#';

5. 什么是多版本并发控制?

多版本并发控制是 Oracle 用于处理并发访问的主要机制,它通过保存数据的多个版本,允许读取操作不阻塞写入操作,写入操作也不阻塞读取操作。

6. 如何选择事务隔离级别?

  • 读已提交:默认隔离级别,适用于大多数应用程序
  • 可重复读:适用于需要确保同一事务中多次读取同一数据时得到相同结果的场景
  • 串行化:适用于需要最高数据一致性的场景,如金融应用

7. 如何减少锁的持有时间?

  • 保持事务简短
  • 编写高效的 SQL 语句
  • 使用批量操作
  • 避免在事务中执行耗时的操作
  • 使用较低的隔离级别

8. 什么是乐观锁?如何实现?

乐观锁是一种并发控制机制,它假设并发冲突很少发生,只在提交时检查冲突。实现方式包括:

  • 使用版本号:在表中添加版本号列,更新时检查版本号
  • 使用时间戳:在表中添加时间戳列,更新时检查时间戳

9. 如何处理热点数据的并发访问?

  • 使用序列生成主键,避免主键冲突
  • 使用分区表,减少锁的持有范围
  • 使用缓存,减少数据库访问
  • 使用异步处理,减少同步访问

10. 如何优化并发性能?

  • 设计简短的事务
  • 使用行级锁
  • 避免锁定不必要的数据
  • 使用较低的隔离级别
  • 使用乐观锁
  • 优化查询计划
  • 使用分区表
  • 使用并行执行

总结

Oracle 提供了强大的锁机制,用于处理并发访问问题。理解锁的类型、锁机制和并发控制机制对于设计和开发高性能的应用程序至关重要。通过合理的锁设计、优化事务和监控锁状态,可以提高数据库的并发性能,确保数据的一致性和完整性。

在实际工作中,应该根据业务需求选择合适的隔离级别,设计简短的事务,避免锁定不必要的数据,定期监控锁状态,及时解决锁等待和死锁问题。通过不断学习和实践,积累锁优化的经验,可以提高数据库的并发性能和可靠性。