外观
Oracle 并发控制
并发控制概述
并发控制是数据库管理系统的核心功能之一,用于管理多个用户同时访问数据库时的资源竞争问题。Oracle 数据库采用多种机制来确保数据一致性和系统性能之间的平衡。
并发控制的重要性
- 确保数据一致性:防止脏读、不可重复读和幻读
- 提高系统吞吐量:允许多个事务同时执行
- 减少锁竞争:优化资源利用率
- 避免死锁:确保事务能够顺利完成
Oracle 并发控制机制
Oracle 数据库主要采用以下并发控制机制:
- 多版本并发控制(MVCC)
- 锁机制
- 事务隔离级别
- 快照隔离
多版本并发控制(MVCC)
MVCC 基本原理
Oracle 是最早实现 MVCC(Multi-Version Concurrency Control)的数据库之一,通过为每个数据行维护多个版本来实现高并发访问。
核心概念
- 撤销段(Undo Segments):存储数据的旧版本
- 系统更改号(SCN):全局唯一的事务标识符
- 读一致性:每个查询看到的数据都是事务开始时的一致视图
MVCC 工作机制
- 事务修改数据时,Oracle 将旧数据复制到撤销段
- 新数据写入数据块,并记录 SCN
- 查询时,Oracle 根据查询开始时的 SCN 从撤销段读取相应版本的数据
- 事务提交后,Oracle 会在适当的时候清理不再需要的撤销数据
MVCC 在不同 Oracle 版本中的改进
| 版本 | 改进内容 |
|---|---|
| 11g | 引入自动撤销管理(AUM)增强 |
| 12c | 支持可插拔数据库的撤销隔离 |
| 19c | 优化撤销段管理,提高大并发下的性能 |
| 21c | 增强多租户环境下的撤销管理 |
乐观锁与悲观锁
悲观锁
悲观锁假设冲突总是会发生,因此在事务开始时就锁定资源。
实现方式
- SELECT ... FOR UPDATE:锁定查询结果集
- LOCK TABLE:锁定整个表
- FOR UPDATE NOWAIT:避免锁等待
应用场景
- 高并发写操作
- 关键业务数据更新
- 长事务
sql
-- 使用悲观锁锁定特定行
SELECT * FROM orders WHERE order_id = 123 FOR UPDATE;
-- 不等待锁
SELECT * FROM orders WHERE order_id = 123 FOR UPDATE NOWAIT;乐观锁
乐观锁假设冲突很少发生,只在提交时检查数据是否被修改。
实现方式
- 版本号字段:每次更新递增版本号
- 时间戳字段:记录最后修改时间
- Oracle 内置机制:利用 SCN 或 ORA_ROWSCN
应用场景
- 读多写少的系统
- 短事务
- 分布式系统
sql
-- 版本号实现乐观锁
UPDATE products
SET quantity = quantity - 1, version = version + 1
WHERE product_id = 456 AND version = 3;
-- 检查更新是否成功
IF SQL%ROWCOUNT = 0 THEN
-- 处理并发冲突
END IF;锁类型与锁模式
Oracle 锁类型
| 锁类型 | 描述 | 适用场景 |
|---|---|---|
| DML 锁 | 保护数据完整性 | INSERT、UPDATE、DELETE 操作 |
| DDL 锁 | 保护数据库对象结构 | CREATE、ALTER、DROP 操作 |
| 内部锁 | 保护数据库内部结构 | 内存结构、文件访问等 |
锁模式
| 锁模式 | 描述 | 兼容模式 |
|---|---|---|
| 共享锁(S) | 允许并发读,阻止写 | 与 S、RS 兼容 |
| 排他锁(X) | 阻止所有并发访问 | 仅与 None 兼容 |
| 行共享锁(RS) | 允许其他事务并发访问 | 与 S、RS、RX 兼容 |
| 行排他锁(RX) | 允许并发读,阻止排他访问 | 与 S、RS、RX 兼容 |
| 共享行排他锁(SRX) | 限制并发写操作 | 与 S、RS 兼容 |
并发控制性能调优
识别并发问题
常见并发性能指标
- 锁等待时间(v$lock_wait)
- 阻塞会话数(v$session_blockers)
- 死锁数量(v$deadlock)
- 事务等待时间(v$transaction)
监控并发问题
sql
-- 查看阻塞会话
SELECT
s1.sid AS blocking_session,
s2.sid AS blocked_session,
l1.type AS lock_type,
l1.lmode AS lock_mode,
l2.request AS requested_mode
FROM
v$lock l1,
v$lock l2,
v$session s1,
v$session s2
WHERE
l1.block = 1 AND
l2.request > 0 AND
l1.id1 = l2.id1 AND
l1.id2 = l2.id2 AND
l1.sid = s1.sid AND
l2.sid = s2.sid;
-- 查看死锁信息
SELECT * FROM v$deadlock;并发性能优化策略
应用设计优化
- 缩小事务范围:尽量减少事务持续时间
- 避免长事务:将大事务拆分为多个小事务
- 合理设计索引:减少锁竞争
- 使用批量操作:减少DML语句数量
锁定策略优化
- 优先使用乐观锁:减少锁竞争
- 避免锁定整个表:使用行级锁
- 合理设置隔离级别:根据业务需求选择
- 使用 NOWAIT 或 WAIT 子句:避免无限等待
数据库参数优化
- undo_retention:调整撤销数据保留时间
- parallel_degree_policy:优化并行执行
- cursor_sharing:减少硬解析
- statistics_level:确保统计信息准确
不同 Oracle 版本的并发优化特性
| 版本 | 并发优化特性 |
|---|---|
| 11g | 自动撤销管理增强、结果缓存 |
| 12c | 自适应执行计划、多租户并发控制 |
| 19c | 实时统计信息、自动索引优化 |
| 21c | 混合分区表、增强的并行处理 |
隔离级别与并发控制
Oracle 支持的隔离级别
| 隔离级别 | 描述 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|---|
| READ COMMITTED | 读取已提交的数据 | 防止 | 允许 | 允许 |
| SERIALIZABLE | 串行化执行 | 防止 | 防止 | 防止 |
| READ ONLY | 只读事务 | 防止 | 防止 | 防止 |
| READ WRITE | 读写事务 | 防止 | 允许 | 允许 |
设置隔离级别
sql
-- 设置事务隔离级别
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 或在事务开始时设置
BEGIN
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 事务操作
END;常见并发问题及解决方案
死锁
死锁产生原因
- 循环等待:事务A等待事务B的锁,事务B等待事务A的锁
- 锁升级:行锁升级为表锁
- 长事务:事务持有锁时间过长
死锁解决方案
- 避免循环依赖:统一访问资源的顺序
- 减少锁持有时间:尽快提交事务
- 使用 NOWAIT 子句:避免无限等待
- 监控死锁:定期检查 v$deadlock 视图
锁等待
锁等待产生原因
- 大量并发写操作
- 不合理的锁定策略
- 长事务
- 索引设计不合理
锁等待解决方案
- 优化查询语句:减少锁定范围
- 使用更细粒度的锁:行级锁替代表级锁
- 增加并发度:调整初始化参数
- 监控锁等待:使用 AWR 报告分析
读一致性问题
读一致性产生原因
- 事务隔离级别设置不当
- 撤销段不足
- 长查询跨越多个事务
读一致性解决方案
- 合理设置 undo_retention 参数
- 增加撤销表空间大小
- 优化长查询:减少查询范围
- 使用适当的隔离级别
最佳实践
开发阶段最佳实践
- 设计合理的事务边界:尽量减少事务持续时间
- 优先使用乐观锁:减少锁竞争
- 合理设计索引:确保查询使用索引,减少全表扫描
- 避免锁定整个表:使用行级锁
- 使用批量操作:减少DML语句数量
运维阶段最佳实践
- 监控并发性能:定期检查锁等待、阻塞会话等指标
- 调整撤销表空间:根据业务需求设置合适的大小
- 优化数据库参数:调整与并发相关的参数
- 定期收集统计信息:确保执行计划准确
- 进行压力测试:评估系统在高并发下的性能
常见问题(FAQ)
Q1: Oracle 是如何实现读一致性的?
A: Oracle 通过多版本并发控制(MVCC)实现读一致性。当事务修改数据时,旧数据会被保存到撤销段,查询时根据查询开始时的 SCN 从撤销段读取相应版本的数据,确保每个查询看到的都是事务开始时的一致视图。
Q2: 乐观锁和悲观锁有什么区别?
A: 乐观锁假设冲突很少发生,只在提交时检查数据是否被修改;悲观锁假设冲突总是会发生,在事务开始时就锁定资源。乐观锁适用于读多写少的场景,悲观锁适用于高并发写操作的场景。
Q3: 如何避免死锁?
A: 避免死锁的方法包括:统一访问资源的顺序,减少锁持有时间,使用 NOWAIT 子句避免无限等待,监控死锁并及时处理,合理设计事务边界。
Q4: 如何监控 Oracle 中的锁等待?
A: 可以通过查询 v$lock_wait、v$session_blockers、v$transaction 等视图来监控锁等待情况。也可以使用 AWR 报告、ASH 报告等工具进行深入分析。
Q5: 不同 Oracle 版本的并发控制有什么区别?
A: 随着 Oracle 版本的升级,并发控制机制不断增强。例如,11g 引入了自动撤销管理增强,12c 支持可插拔数据库的撤销隔离,19c 优化了撤销段管理,21c 增强了多租户环境下的并发控制。
Q6: 如何选择合适的隔离级别?
A: 选择隔离级别需要根据业务需求和性能要求综合考虑。READ COMMITTED 是默认隔离级别,适用于大多数场景;SERIALIZABLE 提供最高级别的一致性,但性能较低;READ ONLY 适用于只读查询场景。
Q7: 长事务对并发性能有什么影响?
A: 长事务会持有锁很长时间,导致其他事务等待,增加锁竞争,降低系统吞吐量。此外,长事务还会导致撤销段增长,影响读一致性查询的性能。
Q8: 如何优化高并发下的 Oracle 性能?
A: 优化高并发下的 Oracle 性能可以从以下几个方面入手:优化应用设计,缩小事务范围,合理使用锁定策略,优化数据库参数,调整撤销表空间,定期收集统计信息,进行压力测试等。
