1. 引言
本文深入介绍Oracle DB的并发控制。
2. 并发控制概述
2.1 并发的必要性
在 Oracle 数据库环境中,多个用户或进程同时访问和操作数据库是很常见的情况。并发访问可以提高系统的资源利用率和整体性能,但同时也带来了数据一致性和完整性的挑战。例如,当两个事务同时对一个账户余额进行更新操作时,如果没有适当的控制机制,可能会导致数据错误。
2.2 并发控制目标
并发控制目标主要目标是确保多个并发事务能够正确地共享数据库资源,同时保证数据的一致性、隔离性和持久性。一致性要求事务将数据库从一个合法状态转换到另一个合法状态;隔离性确保每个事务在执行过程中感觉不到其他并发事务的影响;持久性保证一旦事务提交,其对数据库的修改是永久性的。
3. Oracle 的锁机制
Oracle 的锁机制是并发控制的基础
3.1 共享锁(S 锁)
- 定义与用途:共享锁用于对数据对象进行只读操作。当一个事务对数据对象施加共享锁时,允许其他事务对同一数据对象也施加共享锁,这意味着多个事务可以同时读取同一数据而不会相互干扰。例如,在一个多用户查询产品信息表的场景中,每个查询事务都可以对该表施加共享锁,从而实现并发读取。
- 兼容性:共享锁之间是兼容的,即一个事务可以在已经被其他事务施加了共享锁的数据对象上再施加共享锁。
3.2 排他锁(X 锁)
- 定义与用途:排他锁用于对数据对象进行写操作,如插入、更新或删除。当一个事务对数据对象施加排他锁时,在该锁释放之前,其他事务既不能对该数据对象施加共享锁也不能施加排他锁。例如,当一个事务正在更新一个订单记录时,会对该记录施加排他锁,防止其他事务同时对这条记录进行读取或写入操作,以确保数据的一致性。
- 兼容性:排他锁与任何其他类型的锁(包括共享锁和排他锁)都不兼容。
3.3 意向锁:
- 定义与用途:意向锁是一种表级别的锁,用于提高数据库系统的效率。当事务对一个表中的行施加锁时,Oracle 会自动在表级别添加意向锁。意向锁分为意向共享锁(IS 锁)和意向排他锁(IX 锁)。意向共享锁表示事务在表中的某些行上施加了共享锁,意向排他锁表示事务在表中的某些行上施加了排他锁。例如,当一个事务对表中的一行施加了共享锁,Oracle 会在表级别添加意向共享锁,这样其他事务在查看表锁信息时,可以快速了解到表内有行级别的共享锁存在。
- 兼容性:意向锁之间以及意向锁与其他锁的兼容性遵循一定的规则,以确保在不同的事务操作场景下能够正确地控制并发访问。
4. 除了锁机制,Oracle DB还有哪些并发控制机制
4.1 事务隔离级别
4.1.1 概述
事务隔离级别是一种控制并发事务之间相互影响程度的机制。通过设置不同的隔离级别,可以限制一个事务对其他事务未提交或已提交修改的可见性,从而确保数据的一致性和完整性。
4.1.2 具体级别及特点:
- 读未提交(Read Uncommitted):这是隔离级别中限制最少的一种。在这种模式下,一个事务可以读取到另一个事务尚未提交的数据。虽然这种方式提供了最高的并发性能,但可能会导致脏读问题。例如,事务 A 正在修改一条记录但尚未提交,事务 B 却能读取到事务 A 修改后的记录。如果事务 A 后来回滚了修改,事务 B 读取的数据就成了无效的 "脏数据"。
- 读已提交(Read Committed):此隔离级别规定一个事务只能读取其他事务已经提交的数据,从而避免了脏读。不过,它可能会导致不可重复读的情况。例如,事务 A 在一个事务中两次读取同一条记录,在两次读取之间,事务 B 对该记录进行了修改并提交,那么事务 A 两次读取的结果就会不同。
- 可重复读(Repeatable Read):在这个隔离级别下,一个事务在整个执行过程中对同一数据的多次读取结果是一致的,即不会出现不可重复读的情况。然而,可能会产生幻读问题。例如,事务 A 按照某个条件查询一组记录,在事务执行期间,事务 B 插入或删除了符合该条件的记录,当事务 A 再次查询时,就会发现结果集的数量发生了变化,好像出现了 "幻影" 记录。
- 串行化(Serializable):这是最严格的隔离级别。在这种模式下,事务的执行是完全串行的,就好像每个事务是依次单独执行一样,不会出现脏读、不可重复读和幻读问题。但这种隔离级别会严重影响系统的并发性能,因为它几乎完全禁止了并发操作。
4.2 多版本并发控制(MVCC:关于MVCC后续博文会专门介绍)
4.2.1 原理
Oracle 数据库使用多版本并发控制来提供高效的并发访问。MVCC 的核心思想是为每个数据行维护多个版本。当一个事务对数据进行修改时,数据库不会直接覆盖原来的数据,而是创建一个新的版本。这样,不同的事务可以根据自己的事务开始时间和隔离级别来访问相应版本的数据。
4.2.2 优势:
- 提高并发性能:通过允许不同事务同时访问同一数据的不同版本,MVCC 可以在不使用锁或者减少使用锁的情况下实现高并发访问。例如,在一个高并发的数据库应用中,多个事务可以同时读取数据而不会相互阻塞,除非它们试图修改相同的数据。
- 解决读写冲突:MVCC 能够有效地解决读写冲突问题。读操作不会被写操作阻塞,因为读操作可以访问数据的旧版本。例如,一个事务在读取数据时,即使另一个事务正在对同一数据进行修改,读事务仍然可以获取到修改之前的版本,从而不会被阻塞。
4.3 乐观并发控制(Optimistic Concurrency Control)与悲观并发控制(Pessimistic Concurrency Control)
4.3.1 乐观并发控制:
- 原理:乐观并发控制假设在大多数情况下,事务之间不会产生冲突。在这种机制下,事务在执行过程中不会对数据进行加锁,而是在提交事务时检查数据是否被其他事务修改。如果发现数据已经被修改,那么当前事务可以选择回滚并重新尝试,或者根据应用程序的逻辑来处理冲突。
- 应用场景:适用于读操作远远多于写操作,并且冲突发生的概率较低的场景。例如,在一个内容管理系统中,文章的阅读次数统计功能,多个用户同时读取文章内容和统计信息,只有在极少数情况下才会同时更新这些数据,此时可以使用乐观并发控制来提高并发性能。
4.3.2 悲观并发控制:
- 原理:与乐观并发控制相反,悲观并发控制假设事务之间很可能会发生冲突。因此,在事务开始操作数据之前,就会对相关数据进行加锁,以防止其他事务对同一数据进行访问。例如,在一个银行账户转账系统中,当一个事务处理转账操作时,会对涉及的账户记录施加排他锁,防止其他事务同时对这些账户进行操作。
- 应用场景:适用于写操作比较频繁,并且对数据一致性要求很高的场景。在这种场景下,通过提前加锁来避免数据冲突,确保数据的准确性和完整性。
5. 如何监控和调整Oracle数据库的并发控制性能
5.1 监控并发控制性能的指标与工具
5.1.1 数据库性能视图
- V$ SESSION:这个视图提供了当前数据库会话的详细信息,包括会话的状态(如活动、空闲等)、会话正在执行的 SQL 语句、会话使用的锁类型等。通过查询这个视图,可以了解哪些会话正在进行并发操作,以及它们是否可能存在锁等待的情况。例如,查看BLOCKING_SESSION列可以找到阻塞其他会话的会话 ID,进而分析可能的并发冲突。
- V$ LOCK:用于展示数据库中当前的锁信息,包括锁的类型(共享锁、排他锁等)、锁的模式(行级锁、表级锁等)、被锁定的对象以及持有锁和等待锁的会话等。可以通过关联SESSION视图来确定是哪些事务导致了锁竞争。例如,通过查询V$LOCK` 视图可以发现哪些资源被频繁锁定,以及锁的等待队列长度。
- V$ SQL_MONITOR:能够对正在执行的SQL语句进行监控,包括执行时间,等待事件,SQL_MONITOR中显示有较长时间的等待锁事件,就需要进一步分析其原因。
5.1.2 AWR(Automatic Workload Repository)报告
AWR 定期收集数据库的性能统计数据,包括系统资源使用情况、SQL 执行统计、等待事件等。通过分析 AWR 报告,可以了解数据库在一段时间内的并发性能趋势。例如,查看报告中的 "Top 5 Timed Events" 部分,可以发现是否有与并发控制相关的等待事件(如 "enq: TX - row lock contention" 表示行级锁争用)在性能瓶颈中占比较高。
可以使用DBMS_WORKLOAD_REPOSITORY包来生成和管理 AWR 报告。例如,通过以下语句生成一个过去一小时的 AWR 报告:
DECLARE
l_awr_report CLOB;
BEGIN
l_awr_report := DBMS_WORKLOAD_REPOSITORY.AWR_REPORT_TEXT (
-
dbid => SYS_CONTEXT('userenv', 'dbid'),
-
inst_num => SYS_CONTEXT('userenv', 'instance'),
-
begin_snap => :begin_snap_id,
-
end_snap => :end_snap_id
);
-- 将报告输出到文件或进行其他处理
DBMS_OUTPUT.PUT_LINE(l_awr_report);
END;
5.1.3 性能监控工具(如 Oracle Enterprise Manager)
Oracle Enterprise Manager 是一个功能强大的数据库管理工具,它提供了直观的图形界面来监控数据库性能。可以通过它查看并发会话的数量、锁等待的实时图表、SQL 语句的并发执行情况等。例如,在其控制台中可以设置警报,当并发相关的性能指标(如锁等待时间超过阈值)达到一定程度时,自动发出通知。
5.2 调整并发控制性能的策略与方法
5.2.1 优化事务隔离级别
- 评估业务需求:根据应用程序的业务逻辑重新审视事务隔离级别。如果应用程序对数据一致性要求不是非常严格,并且可以容忍一定程度的不一致(如某些报表统计场景),可以考虑将隔离级别从高设置为低,如从 "可重复读"(Repeatable Read)调整为 "读已提交"(Read Committed),以减少锁的使用,提高并发性能。
- 测试与验证:在调整隔离级别后,需要进行充分的测试,包括功能测试和性能测试。确保应用程序在新的隔离级别下能够正常运行,并且并发性能得到了改善。可以使用性能测试工具模拟多个并发用户访问数据库,观察关键业务操作的响应时间和吞吐量。
5.2.2 优化锁机制
- 减少锁持有时间:在事务设计中,尽量缩短锁的持有时间。例如,将数据读取和更新操作集中进行,避免在事务中间插入大量与数据操作无关的计算或等待。对于长时间运行的事务,可以考虑将其拆分为多个较小的事务,以减少长时间的锁阻塞。
- 选择合适的锁类型:根据操作的性质准确选择锁类型。对于只读操作,优先使用共享锁(S 锁);对于写操作,使用排他锁(X 锁)。在可能的情况下,尽量避免使用表级锁,因为表级锁会限制整个表的并发访问。例如,在更新一个表中的少数行时,使用行级锁而不是对整个表加锁。
- 避免死锁:通过调整事务的访问顺序来避免死锁。如果多个事务都需要访问多个资源,尽量让它们按照相同的顺序访问这些资源。例如,如果事务 A 和事务 B 都需要访问资源 X 和资源 Y,都按照先访问 X 后访问 Y 的顺序进行操作,这样可以减少死锁的发生概率。
5.2.3 调整 MVCC 相关参数(关于MVCC后续博文会专门介绍)
- 理解 MVCC 参数影响:深入了解与 MVCC 相关的数据库参数,如UNDO_RETENTION(用于控制撤销数据的保留时间,与 MVCC 版本数据存储有关)。增加UNDO_RETENTION可以让事务能够访问更旧的版本数据,但也会占用更多的存储空间。
- 参数调整与测试:根据系统的负载和性能要求,谨慎调整 MVCC 参数。在调整参数后,要密切关注系统的性能变化和存储资源的使用情况。例如,在一个读操作较多的系统中,可以适当增加UNDO_RETENTION来满足更多的历史数据版本查询需求,但要同时注意存储空间的消耗和回收机制。
5.2.4 SQL 语句优化
- 优化查询计划:确保 SQL 语句有高效的执行计划,避免全表扫描等低效操作。在并发环境中,低效的 SQL 语句可能会导致锁竞争加剧。可以使用EXPLAIN PLAN命令来分析 SQL 语句的执行计划,然后根据分析结果进行优化。例如,为经常在查询条件中出现的列添加索引,以提高查询效率,减少查询时间,从而降低锁等待的可能性。
- 绑定变量使用:在应用程序中大量使用 SQL 语句的绑定变量。这可以减少硬解析的次数,提高 SQL 语句的执行效率,并且有助于减少锁竞争。因为每次硬解析都会占用一定的系统资源,并且可能导致不同的执行计划,从而影响并发性能。
5.2.5 结合乐观和悲观并发控制策略
混合使用策略:
根据事务的特点,在应用程序中灵活采用乐观和悲观并发控制策略。对于读操作多、写操作少且冲突概率低的部分,使用乐观并发控制。例如,在一个新闻网站的文章浏览计数功能中,由于多个用户同时浏览文章时很少会同时修改计数,采用乐观并发控制可以提高性能。而对于写操作频繁、冲突可能性高的部分,如文章内容的编辑和发布,使用悲观并发控制来确保数据的准确性。
处理乐观并发控制冲突:
当使用乐观并发控制出现冲突时,要有有效的处理机制。一种常见的方法是在事务提交阶段检查数据是否被修改,如果是,则根据应用程序的逻辑决定是回滚并重新尝试,还是进行数据合并等其他操作。例如,在一个协同编辑文档的应用中,当一个用户提交修改时发现文档已经被其他用户修改,系统可以提示用户查看最新版本后重新编辑,或者尝试合并两次修改的内容。
- 后记
并发问题一直是事务处理和高并发编程的核心问题,大家一定要搞透彻明白才能写出高效的代码。
码字不易,宝贵经验分享不易,请各位支持原创,转载注明出处,多多关注作者,后续不定期分享DB基本知识和排障案例及经验、性能调优等。