likes
comments
collection
share

聊一聊数据库事务的那些事(隔离级别,传播行为)

作者站长头像
站长
· 阅读数 17

聊一聊数据库事务的那些事(隔离级别,传播行为)

我们平时使用事务的时候,可能脑子里面想到和事务有关的知识点无非就是,ACID,事务隔离级别那一套,使用的事务也就是是通过注解的形式,或者手动开启事务。更细致一点的问题或许没有深究下去,比如事务的传播行为,注解形式和手动事务的区别等,今天我们就这几个方面进行介绍。

1. 事务的概念

事务是一组数据库操作的执行单元,这些操作要么全部成功执行,要么全部失败回滚。事务具有以下四个基本特性,通常简称为ACID特性:

  • 原子性(Atomicity):事务被视为一个原子操作,要么全部执行成功,要么全部执行失败回滚。如果事务中的任何一步操作失败,那么整个事务会被回滚到事务开始之前的状态,不会留下部分完成的结果。

  • 一致性(Consistency):事务开始之前和事务结束之后,数据库的完整性约束没有被破坏。换句话说,事务的执行将使数据库从一个一致的状态转移到另一个一致的状态。

  • 隔离性(Isolation):并发执行的事务之间应该相互隔离,使每个事务都感觉不到其他事务的存在。事务的隔离性确保了在并发环境中,每个事务都能独立地访问和修改数据,而不会相互干扰或产生不一致的结果。

  • 持久性(Durability):一旦事务提交成功,对数据库的修改将永久保存,即使系统发生故障或重启。持久性确保了事务的结果是可靠和持久的。

2. 事务的隔离级别

为了实现事务的隔离性,数据库定义了不同的隔离级别,用于控制并发事务之间的互相影响程度。常见的隔离级别包括:

  • 读未提交(Read Uncommitted):事务可以读取其他事务尚未提交的数据,可能导致脏读、不可重复读和幻读的问题。

  • 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读,但仍可能导致不可重复读和幻读的问题。

  • 可重复读(Repeatable Read):事务在整个过程中能够多次读取相同的数据,保证了读取的一致性,但仍可能导致幻读的问题。

  • 串行化(Serializable):事务顺序执行,完全隔离了并发事务,保证了最高的隔离性,但可能导致并发性能下降。

3. 事务的传播行为

事务的传播行为是指在多个事务方法相互调用的情况下,如何处理事务的传播和边界。Spring提供了几种不同的传播行为选项,可以根据业务需求进行配置。

以下是几种常用的事务传播行为:

  1. REQUIRED(默认):如果当前存在事务,则加入该事务;如果没有事务,则创建一个新的事务。这是最常用的传播行为。例如:
@Transactional
public void methodA() {
    // 这里会加入调用方的事务,或者创建一个新事务
    methodB();
}

@Transactional
public void methodB() {
    // 该方法也运行在和methodA相同的事务中
}
  1. REQUIRES_NEW:无论当前是否存在事务,都创建一个新的事务。如果存在外部事务,外部事务将被挂起。例如:
@Transactional
public void methodA() {
    // 这里会挂起外部事务,创建一个新事务
    methodB();
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    // 该方法运行在一个新的事务中
}
  1. SUPPORTS:如果当前存在事务,则加入该事务;如果没有事务,则以非事务方式执行。适用于不要求强制事务的场景。例如:
@Transactional
public void methodA() {
    // 这里会加入调用方的事务
    methodB();
}

@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
    // 该方法会根据调用方是否有事务来决定是否加入事务
}
  1. NOT_SUPPORTED:以非事务方式执行方法,如果存在当前事务,将会挂起当前事务。适用于不需要事务支持的场景。例如:
@Transactional
public void methodA() {
    // 这里会挂起调用方的事务
    methodB();
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB() {
    // 该方法以非事务方式执行
}
  1. MANDATORY:要求当前存在事务,否则抛出异常。例如:
@Transactional
public void methodA() {
    // 这里会运行在调用方的事务中
    methodB();
}

@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
    // 该方法要求调用方必须有事务,否则抛出异常
}

这些是一些常见的事务传播行为选项,还有其他一些选项可供选择,如NESTED(如果当前存在事务,则在当前事务的嵌套事务中执行)等。你可以根据具体的业务需求选择适当的传播行为来控制事务的边界和行为。

4. 不同数据库的默认事务隔离级别

不同的数据库管理系统(DBMS)可能具有不同的默认事务隔离级别,并且修改默认事务隔离级别的方法也因DBMS而异。以下是一些常见的数据库和它们的默认事务隔离级别以及如何修改默认事务隔离级别的示例:

  1. MySQL:

    • 默认事务隔离级别:可重复读(REPEATABLE READ)
    • 修改默认事务隔离级别:可以在会话级别或全局级别修改默认事务隔离级别。
      • 会话级别:SET SESSION TRANSACTION ISOLATION LEVEL <isolation_level>;
      • 全局级别:SET GLOBAL TRANSACTION ISOLATION LEVEL <isolation_level>;
  2. Oracle:

    • 默认事务隔离级别:读已提交(READ COMMITTED)
    • 修改默认事务隔离级别:Oracle不支持直接修改默认事务隔离级别。您可以通过在会话级别使用SET TRANSACTION ISOLATION LEVEL <isolation_level>;来设置特定会话的隔离级别。
  3. Microsoft SQL Server:

    • 默认事务隔离级别:读已提交(READ COMMITTED)
    • 修改默认事务隔离级别:可以在数据库级别或会话级别修改默认事务隔离级别。
      • 数据库级别:ALTER DATABASE <database_name> SET READ_COMMITTED_SNAPSHOT ON;
      • 会话级别:SET TRANSACTION ISOLATION LEVEL <isolation_level>;
  4. PostgreSQL:

    • 默认事务隔离级别:读已提交(READ COMMITTED)
    • 修改默认事务隔离级别:可以在会话级别修改默认事务隔离级别。
      • 会话级别:SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL <isolation_level>;

请注意,上述示例中的<isolation_level>是事务隔离级别的具体名称,例如可重复读(REPEATABLE READ)、读已提交(READ COMMITTED)等。这些隔离级别名称可能会因不同的DBMS而略有不同。

在实际使用时,请根据所使用的数据库管理系统的文档和指南来了解默认事务隔离级别以及如何修改它们。这样可以确保您正确地配置事务隔离级别以满足您的应用需求。

5. 注解事务和手动事务

  • 手动管理事务:在手动管理事务的情况下,需要明确地开启、提交和回滚事务。一般步骤包括获取数据库连接、关闭自动提交、执行数据库操作、根据操作结果决定是提交还是回滚事务,并最后关闭数据库连接。这种方式灵活性较高,适用于复杂的事务场景,但需要手动编写事务管理代码。

  • 使用注解管理事务:在使用注解管理事务的情况下,可以使用框架提供的注解来简化事务管理。常见的框架包括Spring Framework和Java Persistence API (JPA)。通过在方法或类上添加相应的注解,可以指定事务的传播行为、隔离级别、回滚条件等。框架会自动管理事务的开启、提交和回滚,减少了手动编写事务管理代码的工作量。

使用注解形式管理事务的示例

@Service
@Transactional
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public void createUser(User user) {
        userRepository.save(user);
    }
    
    public void updateUser(User user) {
        userRepository.save(user);
    }
    
    public void deleteUser(int userId) {
        userRepository.deleteById(userId);
    }
}

在上述示例中,我们使用了Spring Framework提供的@Transactional注解来管理事务。在UserService类上添加了@Transactional注解,表示该类中的所有方法都将在事务的管理下执行。当调用createUserupdateUserdeleteUser方法时,事务会自动开启、提交或回滚,根据方法的执行结果来决定是否提交或回滚事务。

使用手动事务管理的示例

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    public void createUser(User user) {
        TransactionDefinition definition = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(definition);
        
        try {
            userRepository.save(user);
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
    
    public void updateUser(User user) {
        TransactionDefinition definition = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(definition);
        
        try {
            userRepository.save(user);
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
    
    public void deleteUser(int userId) {
        TransactionDefinition definition = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(definition);
        
        try {
            userRepository.deleteById(userId);
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

在上述示例中,我们手动管理事务的过程包括以下步骤:

  1. PlatformTransactionManager中获取TransactionDefinition,定义事务的属性。
  2. 通过transactionManager.getTransaction(definition)获取事务的状态。
  3. try块中执行数据库操作,并通过transactionManager.commit(status)提交事务。
  4. 如果在执行过程中捕获到异常,通过transactionManager.rollback(status)回滚事务,并抛出异常。

使用手动事务管理时,需要明确地在适当的地方开启、提交或回滚事务,并处理异常情况,确保事务的正确执行。

6. 事务的应用领域

事务的概念并不仅限于数据库层面,它可以应用于不同的领域和技术栈。虽然最常见的使用场景是在数据库操作中使用事务来确保数据的一致性和完整性,但事务的概念也可以应用于其他场景。

以下是一些示例,展示了事务的应用领域:

  1. 数据库操作:在数据库中,事务用于将一组相关的数据库操作作为一个逻辑单元进行管理。这意味着要么所有操作都成功执行,要么所有操作都被回滚,以保持数据的一致性。

  2. 消息队列:在消息队列系统中,事务可以确保消息的可靠传递。事务可以用来保证消息的发送和接收操作要么同时成功,要么同时失败,从而避免消息丢失或重复消费的问题。

  3. 分布式系统:在分布式系统中,事务可以用于协调多个节点之间的操作,以保证全局的一致性。分布式事务管理器(如2PC、3PC、Saga等)可以用来实现跨多个服务或系统的事务一致性。

  4. 文件系统:在文件系统中,事务可以用来确保文件的完整性。例如,将多个文件的写操作作为一个事务进行管理,要么所有文件都成功写入,要么都不写入,以避免文件损坏或部分写入的问题。

  5. 应用程序开发:在应用程序开发中,事务可以用于确保一系列操作的原子性和一致性。例如,在Java中,可以使用事务管理框架(如Spring Framework)来管理方法的执行,以便在发生异常时回滚事务。

总结来说,事务不仅仅局限于数据库层面,它是一种通用的概念,在各个领域和技术中都有应用。事务的目标是确保一系列操作的一致性,无论是在数据库操作、消息队列、分布式系统、文件系统还是应用程序开发中。

转载自:https://juejin.cn/post/7240636320761380922
评论
请登录