原创

你真的了解事务吗?


事务隔离的作用

事务隔离是数据库管理系统(DBMS)中的一项关键特性,它确保了在并发操作环境下,不同事务之间能够独立、一致地执行,以维护数据库的完整性。事务隔离的作用主要包括以下几个方面:

  1. 防止数据不一致性:如果没有事务隔离,当多个事务同时读写相同的数据时,可能会导致脏读(Dirty Read)、不可重复读(Non-Repeatable Read)、幻读(Phantom Read)等数据不一致的现象。
    • 脏读:一个事务读取到另一个未提交事务修改过的数据。
    • 不可重复读:在一个事务内,两次读取同一行数据,结果却不一致,因为在此期间另一事务对该数据进行了修改并提交。
    • 幻读:在一个事务内,对某一范围的记录进行多次查询,每次查询的结果集都不一样,这是因为其他事务在这段时间内插入或删除了满足条件的新行。
  2. 保持事务的独立性:事务隔离级别通过锁定机制或其他并发控制策略(如多版本并发控制MVCC),使得每个事务如同在单用户模式下执行一样,互不影响,从而保证了事务的ACID属性中的“隔离性”。
  3. 提高系统可靠性:通过事务隔离,可以避免因并发操作引发的错误状态,增强了系统的稳定性和可靠性。
  4. 支持并发性能与数据一致性的平衡:不同的事务隔离级别提供了不同的并发性能和数据一致性的保障程度,例如从最低级别的“读未提交”到最严格的“序列化”,应用程序可以根据实际需求选择合适的事务隔离级别来实现性能和一致性的最佳折衷。

在MySQL中,事务隔离级别主要有四种:

  • 读未提交(Read Uncommitted)
  • 已提交读(Read Committed)
  • 可重复读(Repeatable Read)
  • 序列化(Serializable)

每种隔离级别都对应着不同程度的并发问题解决方案,并且随着隔离级别的提高,通常会牺牲一定的并发性能。

@Transactional的作用

在Java开发中,特别是在使用Spring框架构建企业级应用时,@Transactional注解是一个关键的事务管理工具。它允许开发者以声明式的方式来处理数据库事务,而不需要编写复杂的事务管理代码。 作用:

  • 开启事务支持:当@Transactional注解被应用到一个类或方法上时,Spring框架会确保这个方法在一个数据库事务的上下文中执行。这意味着,在该方法开始执行时,会启动一个新的事务(如果当前没有事务的话),或者加入到已经存在的事务中(根据事务传播行为)。

  • 事务边界管理:

    • 如果方法中的所有数据库操作都成功完成,则Spring会在方法结束时提交事务。
    • 如果方法执行过程中抛出了未被捕获的运行时异常(默认情况下,或者是配置了需要回滚的特定异常类型),Spring会自动回滚事务,撤销在这个方法内所做的所有数据库修改。
  • 事务属性定制:

    • @Transactional还可以设置一些属性来进一步控制事务的行为,例如:
      • propagation:定义事务的传播行为,如REQUIRED(默认)、REQUIRES_NEW、SUPPORTS、NOT_SUPPORTED、MANDATORY和NEVER等。
      • isolation:指定事务的隔离级别,如DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ和SERIALIZABLE。
      • readOnly:标记事务是否只读,只读事务可以优化数据库访问性能。
      • timeout:设置事务超时时间。
  • 全局事务协调:在分布式事务环境下,Spring能够与JTA事务协调器协作,通过XA协议或其他方式管理跨多个数据库资源的事务。

下面是一个简单的示例

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void updateUserAndSaveLog(User user, Log log) {
        // 更新用户信息
        userRepository.save(user);
  
        // 保存日志记录
        logRepository.save(log);
  
        // 如果上述两个操作有任何一个失败,由于@Transactional的作用,
        // 整个方法内的数据库操作都将被回滚,从而保持数据的一致性。
    }
}

在这段代码中,updateUserAndSaveLog方法被标记为@Transactional,所以无论是在这个方法内部进行的任何数据库更新,都会作为一个整体事务来处理。如果任何一个数据库操作失败,整个事务将会被回滚,防止出现部分更新的情况。

他的参数含义

@Transactional注解可以接受多个参数来配置事务的行为,以下是其中一些主要参数的含义:

  1. propagation(事务传播行为):
    • 定义了在现有事务中如何执行方法。例如:
      • Propagation.REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
      • Propagation.REQUIRES_NEW:总是开启一个新的事务,并且挂起当前事务(如果有的话)。
      • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续执行。
      • 其他还有MANDATORY、NOT_SUPPORTED、NEVER等。
  2. isolation(事务隔离级别):
    • 指定事务应该如何与并发事务隔离开来。例如:
      • Isolation.DEFAULT:使用数据库默认的隔离级别。
      • Isolation.READ_UNCOMMITTED:允许读取尚未提交的数据变更,可能导致脏读、不可重复读和幻读。
      • Isolation.READ_COMMITTED:只允许读取已提交的数据,可以避免脏读,但仍然可能出现不可重复读和幻读。
      • Isolation.REPEATABLE_READ:同一事务内多次读取相同数据结果一致,禁止脏读和不可重复读,但仍可能产生幻读。
      • Isolation.SERIALIZABLE:最高级别的事务隔离,确保事务串行化执行,能防止所有类型的并发问题,但性能成本也最高。
  3. readOnly:
    • 布尔值属性,指定事务是否为只读。默认为false,表示事务可以进行读写操作。如果设置为true,则表明事务仅用于读取数据,某些数据库可能会优化只读事务的性能。
  4. timeout:
    • 指定事务的超时时间(以秒为单位),超过这个时间限制而事务还未完成,将会发生超时异常并回滚事务。
  5. rollbackFor 和 noRollbackFor:
    • 这两个属性用来指定哪些异常会触发事务回滚,哪些异常不会触发事务回滚。通常通过提供异常类或其父类列表来指定。

示例:

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, readOnly = false, timeout = 30, rollbackFor = {MyException.class})
public void someTransactionalMethod() {
    // ...
}

在这段代码中,someTransactionalMethod方法将按照指定的事务传播行为、隔离级别、是否只读、超时时间和回滚策略来管理事务。

@Transactional注解的SERIALIZABLE事务等级会导致锁表吗

在数据库事务的隔离级别中,Isolation.SERIALIZABLE是最高级别的隔离,它提供完全的事务隔离,确保事务之间按序列化方式执行,即如同事务是按顺序一个接一个执行一样,不存在并发问题。

在某些数据库系统中,为了实现SERIALIZABLE隔离级别,确实可能会导致更严格的锁定策略,甚至在特定场景下表现为锁表。例如,在处理涉及范围查询或更新的操作时,数据库可能需要获取范围锁(如间隙锁、Next-Key Locks等)来避免幻读,这些锁有可能会对整个索引区间或者表产生锁定效果,尤其是在没有优化并发控制机制(如多版本并发控制MVCC)的情况下。

然而,并不是所有支持SERIALIZABLE隔离级别的数据库都会简单地采用全表锁定的方式。许多现代数据库通过更加精细的锁定技术和并发控制机制来尽量减少锁定的开销和提高并发性能。

总的来说,虽然SERIALIZABLE隔离级别可以有效防止脏读、不可重复读以及幻读,但如果不恰当或过度使用,可能会导致严重的并发性能问题,包括增加锁等待时间、死锁概率增大以及事务阻塞等问题。因此,在实际应用中选择合适的事务隔离级别需要根据业务需求和性能要求权衡考虑。

java
  • 作者:admin(联系作者)
  • 发表时间:2024-02-24 15:39
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 公众号转载:请在文末添加作者公众号二维码
  • 评论