1. 引言
本节详细介绍Oracle DB的事务。
2. 理解事务的基本概念与特性
2.1 事务的定义与特性
2.1.1 定义
在 Oracle 数据库中,事务是一组逻辑相关的数据库操作单元,这些操作要么全部成功执行并提交(使数据库状态发生永久性改变),要么全部失败并回滚(撤销所有已执行的操作,使数据库恢复到事务开始前的状态)。例如,在银行转账系统中,从一个账户扣款并在另一个账户收款这两个操作就构成一个事务。
2.1.2 事务的ACID 特性:
1. 原子性(Atomicity): 事务是一个不可分割的工作单位,事务中的所有操作要么全部完成,要么全部不完成。就像一个包裹,里面的东西要么全部送达,要么全部退回。例如,在电商系统中,下单购买商品时,商品库存减少、生成订单记录、用户账户余额扣除等操作必须作为一个整体完成,不能出现部分操作成功部分失败的情况。
2. 一致性(Consistency): 事务必须使数据库从一个一致性状态变换到另一个一致性状态。数据库的一致性状态是指数据库中的数据满足所有的完整性约束条件。例如,在关系数据库中,表的主键唯一约束、外键关联约束等都必须在事务执行前后得到满足。如果在一个学生选课系统中,事务插入了一条选课记录,那么必须保证学生表和课程表中的相关数据(如学生已选课程数量、课程已选人数等)仍然满足系统预先定义的约束。
3. 隔离性(Isolation): 一个事务的执行不能被其他事务干扰。多个并发事务之间相互隔离,每个事务感觉不到其他事务的存在。例如,两个用户同时对一个商品库存进行操作,一个用户进行购买(减少库存),另一个用户进行库存盘点,这两个事务应该相互隔离,购买事务不能看到盘点事务未提交的数据,反之亦然。这种隔离是通过数据库的并发控制机制(如锁和 MVCC)来实现的。
4. 持久性(Durability): 一旦事务提交,它对数据库的改变就是永久性的,即使系统出现故障(如断电、软件崩溃等),这些改变也会被保存下来。例如,当银行转账事务提交后,转账金额的变化就会持久地记录在银行数据库中,不会因为后续的系统故障而丢失。
2.2 事务的开始与结束
2.2.1 开始
在 Oracle 中,一个事务可以隐式或显式地开始。隐式开始是指当执行第一条 DML(数据操作语言,如 INSERT、UPDATE、DELETE)语句时,事务自动开始。例如,当执行INSERT INTO employees (employee_id, employee_name) VALUES (1001, ‘John’);时,一个事务就开始了。也可以使用SET TRANSACTION命令显式地开始一个事务,这种方式可以设置事务的一些属性,如隔离级别。
2.2.2 结束
事务可以通过两种方式结束,即提交(COMMIT)和回滚(ROLLBACK)。
1. 提交(COMMIT): 当执行COMMIT命令时,事务中的所有操作被永久性地保存到数据库中,数据库状态发生改变。提交操作会释放事务期间使用的所有资源,如锁等。例如,在完成一系列订单处理操作后,执行COMMIT来确认这些操作,使订单状态更新、库存变化等操作生效。
2. 回滚(ROLLBACK): 如果在事务执行过程中出现错误或者需要取消事务中的操作,可以执行ROLLBACK命令。这会撤销事务中所有已执行的 DML 操作,使数据库恢复到事务开始前的状态。例如,在执行更新员工工资的事务中,如果发现计算错误,可以通过ROLLBACK来取消已经执行的工资更新操作。
2.3 事务隔离级别
1. 读未提交(Read Uncommitted): 这是最低的隔离级别。在这个级别下,一个事务可以读取另一个未提交事务修改的数据。这种隔离级别可能会导致脏读(Dirty Read)问题,即一个事务读取到了另一个事务尚未提交的数据,而这些数据可能随后会被回滚。例如,事务 A 修改了一个账户余额但尚未提交,事务 B 在这个隔离级别下读取了修改后的余额,若事务 A 后来回滚,事务 B 读取到的数据就是无效的。
2. 读已提交(Read Committed): 在这个隔离级别下,一个事务只能读取另一个已提交事务修改的数据。可以避免脏读问题,但可能会导致不可重复读(Non - Repeatable Read)。不可重复读是指在一个事务中,对同一数据的两次读取结果不一致,因为在两次读取之间,另一个事务可能已经修改并提交了该数据。例如,事务 A 在第一次读取一个产品价格后,事务 B 修改了这个价格并提交,事务 A 第二次读取时就会得到不同的价格。
3. 可重复读(Repeatable Read): 在这个隔离级别下,一个事务在整个执行过程中对同一数据的多次读取结果是一致的,即使其他事务对该数据进行了修改并提交。但可能会出现幻读(Phantom Read)问题。幻读是指一个事务在按照某个条件进行查询时,第一次查询和第二次查询的结果集数量不同,因为在两次查询之间,另一个事务插入或删除了满足该条件的数据。例如,事务 A 查询所有价格大于 100 的产品,在查询期间事务 B 插入了新的价格大于 100 的产品,事务 A 再次查询时就会出现幻读。
4. 串行化(Serializable): 这是最高的隔离级别。在这个隔离级别下,事务的执行是完全串行的,就好像每个事务是依次单独执行一样,不会出现脏读、不可重复读和幻读问题。但这种隔离级别会严重影响系统的并发性能,因为它几乎完全禁止了并发操作。
2.4 事务的嵌套与保存点(SAVEPOINT)
1. 嵌套事务: 在 Oracle 中,虽然没有真正意义上的嵌套事务(像一些其他数据库系统中的严格嵌套事务概念),但可以通过存储过程或匿名块的调用来实现类似的效果。在一个外层事务中调用包含事务操作的存储过程,从逻辑上看好像是嵌套事务。不过,Oracle 会将整个操作视为一个大的事务,内层事务的提交或回滚并不独立于外层事务。
2. 保存点(SAVEPOINT): 保存点用于在事务中标记一个位置,以便在需要时可以回滚到这个位置,而不是回滚整个事务。例如,在一个复杂的订单处理事务中,可能包括更新库存、更新客户信息、生成订单记录等多个步骤。可以在更新库存后设置一个保存点,这样如果在后续的客户信息更新或订单记录生成步骤中出现问题,只需要回滚到保存点,重新执行后面的步骤,而不必撤销整个订单处理事务。可以使用SAVEPOINT命令来设置保存点,使用ROLLBACK TO SAVEPOINT命令来回滚到指定的保存点。
3. 事务处理对Oracle数据库性能的影响
3.1 事务处理对数据库性能的正面影响
3.1.1 数据一致性保障与性能优化的平衡
事务的原子性和一致性确保了数据库数据的准确性。通过正确处理事务,数据库能够在复杂的操作环境中维持数据的完整性。例如,在电商系统的下单操作中,事务机制保证了库存减少、订单生成和支付处理等操作要么全部成功,要么全部失败。从性能角度看,这种准确性避免了因数据不一致而导致的后续复杂纠错操作,间接提升了系统整体性能。如果没有事务保证,数据可能频繁处于不一致状态,需要花费大量时间进行数据清理和修复,严重影响系统性能。
3.1.2 隔离性带来的并发性能提升
适当的事务隔离级别可以提高数据库的并发性能。例如,在 “读已提交”(Read Committed)隔离级别下,事务能够在避免脏读的同时允许一定程度的并发读取和写入操作。多个用户可以同时查询和更新数据,只要读取操作不涉及未提交的数据修改。这种并发访问能力使得数据库系统能够更高效地利用系统资源,如 CPU 和磁盘 I/O。与串行执行事务相比,合理的并发事务处理能够在保证数据质量的前提下大幅提高系统的吞吐量,从而提升性能。
3.1.3 持久性确保数据可靠性和性能稳定
事务的持久性保证了一旦事务提交,其对数据库的修改就会永久保存。这对于需要高可靠性的应用场景至关重要。从性能角度来看,可靠的数据存储使得系统在出现故障后能够快速恢复到正确状态,减少因数据丢失或损坏而导致的性能下降。例如,在金融系统中,事务的持久性确保了交易记录的永久性,避免了因数据丢失而需要重新处理大量交易的情况,维护了系统性能的稳定性。
3.2 事务处理对数据库性能的负面影响
3.2.1 锁机制带来的性能开销
为了实现事务的隔离性,数据库通常会使用锁机制。当一个事务对数据对象施加排他锁(X 锁)进行写操作时,会阻止其他事务对同一数据对象的读写操作。例如,在一个高并发的数据库应用中,如果多个事务频繁地对相同的数据行进行写操作,就会导致大量的锁等待和竞争。这种锁竞争会导致事务的执行时间延长,增加系统的响应时间,降低系统的并发性能。即使是共享锁(S 锁),在高并发的读取场景下,如果大量事务同时对同一数据对象施加共享锁,也可能会产生锁升级等问题,影响性能。
3.2.2 事务回滚的成本
当事务需要回滚时,数据库需要撤销已经执行的操作。这涉及到对数据库的反向操作,包括恢复数据的旧值、释放已经获取的资源(如锁)等。例如,在一个包含大量数据插入和更新操作的事务中,如果回滚,数据库需要逐个撤销这些操作,这会消耗大量的系统资源,包括 CPU 时间、磁盘 I/O 和内存。而且,频繁的事务回滚可能导致数据库的撤销(undo)表空间的压力增大,影响存储性能。
3.2.3 高隔离级别对并发性能的限制
较高的事务隔离级别,如 “串行化”(Serializable),虽然能完全避免数据不一致问题,但会严重限制并发性能。在这种隔离级别下,事务的执行是完全串行的,就好像每个事务是依次单独执行一样。这使得系统无法充分利用硬件资源进行并发处理,导致系统的吞吐量大幅下降。例如,在一个高并发的 Web 应用中,如果将事务隔离级别设置为 “串行化”,大量用户请求的事务将不得不排队等待执行,使得系统的响应时间显著增加,性能严重受损。
4. 监控和调优Oracle数据库中的事务处理性能
4.1 监控事务处理性能的方法与工具
4.1.1 使用数据库性能视图
1. V$ SESSION: 这个视图提供了当前数据库会话的详细信息,包括事务是否正在等待锁、事务执行的语句等。通过查询V$ SESSION,可以找出长时间处于等待状态的事务。例如,查看BLOCKING_SESSION 列可以找到阻塞其他会话的事务对应的会话 ID,进而分析可能的性能瓶颈。
2. V$TRANSACTION:它包含了有关当前活动事务的详细信息,如事务开始时间、使用的回滚段等。通过观察事务的持续时间和资源使用情况,可以判断事务是否存在性能问题。例如,如果一个事务在TRANSACTION`中的持续时间过长,可能是因为该事务涉及复杂的操作或者正在等待某些资源。
3. V$SQL_MONITOR: 能够对正在执行的语句进行监控,包括执行时间、等待事件、执行计划等信息。在事务处理过程中,语句的性能直接影响事务的整体性能。通过这个视图,可以发现哪些语句在事务中执行缓慢,以及是哪些等待事件导致了性能下降。例如,如果一个事务中的查询语句在SQL_MONITOR中显示有较长时间的等待锁事件,就需要进一步分析其原因。
4.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;
4.1.3 利用 Oracle Enterprise Manager(OEM)等工具
OEM 提供了直观的图形界面来监控数据库性能。可以通过它查看事务的并发数量、事务的响应时间分布、锁等待的实时图表等。例如,在其控制台中可以设置警报,当事务相关的性能指标(如平均事务响应时间超过阈值)达到一定程度时,自动发出通知。
4.2 调优事务处理性能的策略与措施
4.2.1 优化事务隔离级别
1. 评估业务需求: 仔细评估应用程序的业务逻辑和对数据一致性的要求。如果应用程序能够容忍一定程度的数据不一致,如在一些报表生成场景中,数据的实时准确性要求不是特别高,可以考虑使用较低的隔离级别,如 “读未提交”(Read Uncommitted)来提高并发性能。但在涉及金融交易等对数据准确性要求极高的场景下,可能需要使用 “串行化”(Serializable)隔离级别。
2. 测试与验证: 在调整隔离级别后,需要进行充分的测试,包括功能测试和性能测试。确保应用程序在新的隔离级别下能够正常运行,并且并发性能得到了改善。可以使用性能测试工具模拟多个并发用户访问数据库,观察关键业务操作的响应时间和吞吐量。
4.2.2 减少锁竞争
1. 缩短锁持有时间: 在事务设计中,尽量缩短锁的持有时间。例如,将数据读取和更新操作集中进行,避免在事务中间插入大量与数据操作无关的计算或等待。对于长时间运行的事务,可以考虑将其拆分为多个较小的事务,以减少长时间的锁阻塞。
2. 选择合适的锁类型: 根据操作的性质准确选择锁类型。对于只读操作,优先使用共享锁(S 锁);对于写操作,使用排他锁(X 锁)。在可能的情况下,尽量避免使用表级锁,因为表级锁会限制整个表的并发访问。例如,在更新一个表中的少数行时,使用行级锁而不是对整个表加锁。
3. 避免死锁: 通过调整事务的访问顺序来避免死锁。如果多个事务都需要访问多个资源,尽量让它们按照相同的顺序访问这些资源。例如,如果事务 A 和事务 B 都需要访问资源 X 和资源 Y,都按照先访问 X 后访问 Y 的顺序进行操作,这样可以减少死锁的发生概率。
4.2.3 优化 SQL 语句
1. 查询计划优化: 确保事务中的 SQL 语句有高效的执行计划,避免全表扫描等低效操作。可以使用EXPLAIN PLAN命令来分析 SQL 语句的执行计划,然后根据分析结果进行优化。例如,为经常在查询条件中出现的列添加索引,以提高查询效率,减少查询时间,从而降低锁等待的可能性。
2. 绑定变量使用: 在应用程序中大量使用 SQL 语句的绑定变量。这可以减少硬解析的次数,提高 SQL 语句的执行效率,并且有助于减少锁竞争。因为每次硬解析都会占用一定的系统资源,并且可能导致不同的执行计划,从而影响并发性能。
4.2.4 管理事务大小和复杂度
1. 控制事务范围: 避免在一个事务中包含过多不必要的操作。将复杂的业务流程拆分为多个较小的事务,这样可以减少单个事务的执行时间和资源占用。例如,在一个复杂的订单处理系统中,将库存更新、订单记录生成和支付处理等操作分成独立的事务,这样即使某个操作出现问题,也不需要回滚整个复杂的业务流程。
2. 合理使用保存点(SAVEPOINT): 在事务中适当设置保存点,以便在事务执行过程中出现问题时,可以回滚到特定的位置,而不是回滚整个事务。例如,在一个包含多个步骤的更新事务中,在每个重要步骤后设置保存点,这样如果在后续步骤中出现错误,可以快速回滚到前面的步骤重新执行,减少事务回滚的成本。
4.2.5 调整数据库参数与存储配置
1. 优化回滚段(Rollback Segments)或撤销表空间(Undo Tablespace): 回滚段或撤销表空间用于存储事务回滚所需的信息。根据数据库的负载和事务特点,合理配置其大小和性能参数。例如,在一个有大量事务回滚操作的系统中,适当增加撤销表空间的大小,以避免因回滚信息不足而导致的性能问题。
2. 调整内存参数(如 SGA 和 PGA): 系统全局区(SGA)和程序全局区(PGA)中的参数设置会影响事务处理性能。例如,调整共享池(Shared Pool)的大小可以优化 SQL 语句的缓存和解析效率,从而间接提高事务性能。通过性能测试和监控,找到合适的内存参数配置,以满足事务处理的需求。
本文完。
码字不易,宝贵经验分享不易,请各位支持原创,转载注明出处,多多关注作者,后续不定期分享DB基本知识和排障案例及经验、性能调优等。