事务acid

本文详细阐述了数据库事务的ACID特性,通过银行转账实例展示了这些特性在实际中的应用。还介绍了脏读、幻读现象及其解决方案,以及JDBC中的事务隔离级别设置,最后讨论了死锁的概念和避免策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

事务ACID特性

事务的ACID特性是数据库管理系统在处理事务时必须遵循的四个基本属性,它们分别是:

  • 原子性(Atomicity):事务中的所有操作要么全部完成,要么全部不发生,不会处于中间状态。如果事务中的任何操作失败,则整个事务将失败,之前所做的任何更改都会被撤销,确保数据库的一致性。
  • 一致性(Consistency):事务必须使数据库从一个一致性状态转移到另一个一致性状态。这意味着事务执行的结果必须是合法的,符合数据库的完整性约束。如果事务执行违反了任何完整性约束,则整个事务会回滚到执行前的状态。
  • 隔离性(Isolation):每个事务的操作都是独立的,不应受到其他并发执行事务的影响。即使在多个事务并发执行时,每个事务都好像是在独立的环境中运行一样,相互之间不会产生影响,直到事务提交后结果才对其他事务可见。
  • 持久性(Durability):一旦事务提交,它对数据库的更改就是永久性的,即使发生系统故障,更改也不会丢失。通常这意味着事务的结果已被记录到非易失性存储中,如硬盘。

这些特性在实际应用中是如何体现

以下是一个简化的银行转账操作的例子,说明ACID特性在实际应用中的体现:
假设Alice想从她的账户转1000元给Bob。
原子性(Atomicity):

  • 整个转账操作被视为一个事务。如果任何一步失败(比如Alice的账户余额不足),整个事务将回滚,确保不会发生部分转账。
  • 例如,如果系统在将钱从Alice的账户扣除后但在将钱存入Bob的账户之前崩溃,事务将回滚,Alice的账户不会扣款,Bob的账户也不会收到钱。

一致性(Consistency):

  • 在转账前,数据库的完整性约束会检查Alice的账户余额是否大于或等于1000元。如果约束不满足,事务将不会执行,保持数据库的一致性状态。
  • 如果转账成功,数据库的一致性约束会确保Alice和Bob的账户余额总和在转账前后保持不变。

隔离性(Isolation):

  • 假设同时有另一个事务在处理Bob的账户,隔离性确保这两个事务不会互相干扰。在这个例子中,隔离性确保在转账事务完成之前,其他事务看不到Alice账户余额的减少或Bob账户余额的增加。
  • 隔离性还可以防止“脏读”、“不可重复读”和“幻读”等并发问题。

持久性(Durability):

  • 一旦转账事务提交,其结果就被永久记录在数据库中。即使系统随后崩溃,转账记录也不会丢失,Alice的账户会确实减少1000元,Bob的账户会确实增加1000元。
  • 持久性通常通过将事务日志写入到磁盘来实现,确保在发生故障时能够恢复数据。

DirtyRead

脏读(Dirty Read)是数据库事务并发控制中的一个现象,它发生在当一个事务读取了另一个未提交事务写入的数据时。如果未提交的事务随后回滚,那么先前读取的数据就变得无效,因为它们是基于未完成的操作。
下面是一个脏读的例子:
假设有两个事务T1和T2:

  • 事务T1开始,并从账户A中读取金额为$1000。
  • 事务T1尚未提交。
  • 事务T2开始,并从账户A中读取金额为$1000(这是脏读发生的地方,因为T1尚未提交)。
  • 事务T1更新账户A的金额为$900,并提交。
  • 事务T2现在基于脏数据计算利息,假设利息是基于$1000计算的。
  • 事务T2提交。

事务T2基于一个可能会回滚的数据进行了计算,这是不安全的。如果事务T1在更新后回滚,事务T2的计算结果就会是错误的。为了避免脏读,数据库系统通常提供不同的事务隔离级别,其中较高的隔离级别(如串行化)可以防止脏读,但可能会影响性能。

Phantom Read

幻读(Phantom Read)是数据库事务并发控制中的一个现象,它发生在当一个事务在数据库中执行两次相同的查询操作时,第二次查询返回了第一次查询中没有的结果(通常是其他事务插入的新行)。
以下是一个幻读的例子:
假设有两个事务T1和T2:

  • 事务T1开始,并执行查询,返回所有账户余额大于$100的账户列表。
  • 事务T2开始,并插入一个新的账户记录,其余额为$200,然后提交。
  • 事务T1再次执行相同的查询,这次返回的列表中包含了事务T2插入的新账户。

事务T1在同一个事务内两次执行了相同的查询,但是得到了不同的结果集。这种现象称为幻读,因为事务T1看到了在第一次查询中不存在的“幻影”记录。
幻读通常发生在事务隔离级别低于串行化(SERIALIZABLE)的情况下。在REPEATABLE READ隔离级别下,幻读是可以发生的,因为它只保证了在事务内的相同查询不会看到已存在的行的修改或删除,但不会阻止新行的插入。
为了防止幻读,数据库系统可能会使用范围锁(Range Lock),在事务T1执行第一次查询时,锁定满足条件的所有行,包括那些目前不存在但可能在事务执行期间被插入的行。这样,事务T2在尝试插入新行时将被阻塞,直到事务T1完成。

事务隔离级别

在Java中使用JDBC(Java Database Connectivity)时,可以通过设置连接的事务隔离级别来控制事务的隔离属性,从而解决脏读、不可重复读和幻读等问题。JDBC提供了以下五个标准的事务隔离级别:
TRANSACTION_NONE

  • 表示不支持事务。
    TRANSACTION_READ_UNCOMMITTED
  • 允许脏读、不可重复读和幻读。这个隔离级别几乎不会在实际应用中使用,因为它提供了最少的隔离保证。
    TRANSACTION_READ_COMMITTED
  • 防止脏读。这是大多数数据库系统的默认隔离级别,因为它提供了合理的读一致性保证,同时不会过度影响性能。
    TRANSACTION_REPEATABLE_READ
  • 防止脏读和不可重复读。在这个隔离级别下,同一个事务中的多次读取操作会返回相同的数据结果,即使其他事务在中间修改了数据并提交。
    TRANSACTION_SERIALIZABLE
  • 防止脏读、不可重复读和幻读。这是最高的事务隔离级别,它通过锁定所有相关数据来强制事务串行执行,从而提供最高程度的隔离。但是,这可能会导致大量的锁争用和性能下降。

以下是如何在JDBC中设置事务隔离级别的示例代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class TransactionIsolationExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/mydb";
        String user = "username";
        String password = "password";

        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            // 设置事务隔离级别为可重复读
            conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);

            // 开始事务
            conn.setAutoCommit(false);

            // 执行数据库操作
            // ...

            // 如果没有异常,提交事务
            conn.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            // 如果有异常,回滚事务
            // conn.rollback();
        }
    }
}

我们首先设置了连接的事务隔离级别,然后开始了一个新的事务(通过设置autoCommit为false)。在执行了所有的数据库操作之后,如果没有发生异常,我们就提交事务。如果发生了异常,我们可以在catch块中回滚事务。

死锁

死锁是数据库或操作系统等并发系统中的一种常见问题,它发生在两个或多个事务在执行过程中,因争夺资源而造成的一种互相等待的现象。在这种情况下,每个事务都在等待另一个事务释放它持有的资源,但由于这些事务也在等待其他事务释放资源,因此它们都无法继续执行,导致系统陷入停滞状态。
死锁的四个必要条件通常被描述为:

  • 互斥条件:一个资源每次只能被一个事务使用。
  • 持有和等待条件:一个事务已经持有至少一个资源,但又提出了新的资源请求,而该资源已被其他事务持有,所以当前事务会等待。
  • 非抢占条件:已经分配给一个事务的资源不能强制性地被抢占,只能由该事务完成后自愿释放。
  • 循环等待条件:存在一种事务资源的循环等待链,每个事务至少持有一个资源,并等待获取下一个事务所持有的资源。

以下是一个简单的死锁示例:
假设有两个事务T1和T2,以及两个资源R1和R2。

  • 事务T1获取资源R1。
  • 事务T2获取资源R2。
  • 事务T1请求资源R2,但由于R2已被T2持有,因此T1等待。
  • 同时,事务T2请求资源R1,但由于R1已被T1持有,因此T2等待。

现在,T1和T2都在等待对方释放资源,形成了死锁。
数据库管理系统通常会有死锁检测和死锁处理机制。死锁检测是通过数据库的内部算法来周期性地检查事务之间的资源依赖关系,以确定是否存在死锁。如果检测到死锁,DBMS会采取行动来解除死锁,通常是通过撤销(回滚)其中一个事务,并释放它持有的资源,从而使得其他事务可以继续执行。被撤销的事务可能会被重新启动,这取决于应用程序的具体逻辑。
为了避免死锁,可以采取以下策略:

  • 顺序访问资源:通过定义事务访问资源的统一顺序,可以避免循环等待条件。
  • 超时机制:为事务设置超时时间,如果事务在一定时间内没有完成,就将其回滚,释放资源。
  • 乐观并发控制:在修改数据之前,检查自读取数据以来是否有其他事务修改了这些数据。
  • 减少事务大小:尽量保持事务简短,减少资源占用时间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十点 vha

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值