数据库事务原理详解

数据库事务原理详解

一、 从Spring事务配置说起

1.1、Spring事务的基础配置:

<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 配置事务传播特性 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="add*" propagation="REQUIRED" rollback-for="Exception,RuntimeException,SQLException"/>
        <tx:method name="remove*" propagation="REQUIRED" rollback-for="Exception,RuntimeException,SQLException"/>
        <tx:method name="modify*" propagation="REQUIRED" rollback-for="Exception,RuntimeException,SQLException"/>
        <tx:method name="login" propagation="NOT_SUPPORTED"/>
        <tx:method name="query*" read-only="true"/>
    </tx:attributes>
</tx:advice>
<aop:config>
    <aop:pointcut id="transactionPointcut" expression="execution(public * com..*.service..*Service.*(..))"/>
    <aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut"/>
</aop:config>

Spring事务管理基于AOP实现,主要作用是统一封装非功能性需求。

1.2、通过 PlatformTransactionManager使用

不推荐使用

1.3、通过TransactionTemplate 使用事务

1.4、声明式事务

二、 事务的基本概念

事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
特点:事务是恢复和并发控制的基本单位。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这4个属性通常称为ACID特性。

  • 原子性:一个事务是一个不可分割的工作单位,事务中包括的诸多操作,要么都做,要么都不做。
  • 一致性:事务必须使数据库从一个一致性状态变到另一个一致性状态。
  • 隔离性:并发执行的各个事务之间不能互相干扰。
  • 持久性:事务一旦提交,它对数据库中数据的改变就应该是永久性的。

三、 事务的基本原理

Spring事务的本质其实就是数据库对事务的支持。对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:

  1. 获取连接:Connection con = DriverManager.getConnection();
  2. 开启事务:con.setAutoCommit(true/false);
  3. 执行CRUD操作
  4. 提交事务/回滚事务:con.commit(); / con.rollback();
  5. 关闭连接:con.close()。
    问题1:Spring是如何在我们书写CRUD操作之前和之后开启事务和关闭事务的呢?解决了这个问题,也就可以从整体上理解Spring的事务管理实现原理。下面简单地介绍下,以注解方式为例:
  • 在配置文件中开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
  • Spring在启动的时候会解析生成相关的Bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,根据@Transactional的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务、异常回滚事务)。
  • 真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

四、 Spring事务的传播属性

所谓Spring事务的传播属性,就是定义在多个事务同时存在的时候,Spring应该如何处理这些事务的行为。这些属性在TransactionDefinition中定义,具体常量的解释如下表所示:

NO.事务传播行为类型说明
1PROPAGATION_REQUIRED(默认使用)如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
2PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
3PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
4PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
5PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
6PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
7PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

五、 数据库事务隔离级别

数据库事务隔离级别如下表所示:

NO.隔离级别导致的问题
1Read-Uncommitted0导致脏读
2Read-Committed1避免脏读,允许不可重复读和幻读
3Repeatable-Read2避免脏读,不可重复读,允许幻读
4Serializable3串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行速度慢
  • 脏读:一个事务对数据进行了增、删、改,但未提交,另一个事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。
  • 不可重复读:一个事务中发生了两次读操作,在第一次读操作和第二次读操作之间,另外一事务对数据进行了修改,这时候两次读取的数据是不一致的。
  • 幻读:第一个事务对一定范围的数据进行了批量修改,第二个事务在这个范围内增加了一条数据,这时候第一个事务就会丢失对新增数据的修改。

数据库事务隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。大多数数据库(比如SQLServer和Oracle)事务默认隔离级别的Read-Commited,少数数据库(比如MySQL InnoDB)事务默认隔离级别为Repeatable-Read。

六、 Spring中的事务隔离级别

Spring中的事务隔离级别如下表所示。

NO.常量解释
1ISOLATION_DEFAULT这是PlatformTransactionManager默认的事务隔离级别(数据库默认的)
2ISOLATION_READ_UNCOMMITTED这是最低的事务隔离级别,它允许另外一个事务看到这个事务未提交的数据。这种隔离级别会产生脏读、不可重复读和幻读。
3ISOLATION_READ_COMMITTED保证一个事务修改的数据提交后才能被另一个事务读取。另一个事务不能读取该事务未提交的数据。
4ISOLATION_REPEATABLE_READ可防止脏读、不可重复读,但是可能出现幻读。
5ISOLATION_SERIALIZABLE这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行

七、 事务的嵌套

分析嵌套事务场景来深入理解Spring事务传播机制。
假设外部事务ServiceA的Method A()调用内部事务ServiceB的Method B()。

  • PROPAGATION_REQUIRED:没事务创建事务,有事务就使用当前事务。
  • PROPAGATION_REQUIRES_NEW:挂起当前事务后创建新事务,当创建的事务执行完后挂起的事务才会继续执行。
  • PROPAGATION_SUPPORTS:有事务就使用事务,没事务就以没事务方式执行。
  • PROPAGATION_NESTED:嵌套事务最有价值的地方,它起到了分支执行的效果。
void MethodA() {
	try{
		ServiceB.MethodB();
	}catch (SomeException) {
		// 执行其他事务,如ServiceC.MethodC();
	}
}
  1. 捕获异常,执行异常分支逻辑
  2. 外部事务回滚/提交
  • 另外三种事务传播属性基本用不到,在此不做分析。

八、 Spring事务API架构图

Spring事务API架构图如下图所示:
Spring事务API架构图

九、 浅谈分布式事务

理论依据就是CAP理论,即Consistency(一致性)、Availability(可用性)和Partition Tolerance(分区容错性)。为了可用性和分区容错性,往往放弃强一致性,转而追求最终一致性。
理解数据的一致性:

  • 强一致性:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的值。
  • 弱一致性:系统并不保证后续进行或者线程的访问都会返回最新的值。在数据写入成功之后,系统不承诺立即可以读到最新写入的值,也不会承诺多久之后可以读到。
  • 最终一致性:弱一致性的特定形式。系统保证在没有后续更新的前提下,最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟、系统负载和副本个数影响。

事务失效的根本原因:动态代理(AOP)

@Log4j2
@Service
public class TestService {

    @Autowired
    private OrderMapper mapper;

    private TestService proxy;
    @Autowired
    private ApplicationContext ac;
    //被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。
    // PostConstruct在构造函数之后执行,init()方法之前执行。PreDestroy()方法在destroy()方法知性之后执行
    @PostConstruct
    public void init(){
        proxy = ac.getBean(TestService.class);
    }

    @Transactional
    public void parent(){
        log.info("====调用parent()==========");
        try{
            //this.child();
            //解决方案1:从AOP上下文中获取当前代理对象
            TestService service = (TestService)AopContext.currentProxy();
            service.child();
            //解决方案2:从Spring上下文中获取代理对象
            proxy.child();
        }catch (Exception e){
            e.printStackTrace();
        }

        Order order = new Order();
        order.setTitle("测试定单:parent");
        order.setOrderno("parent");
        order.setAmount(100);
        order.setStatus(0);
        mapper.save(order);
        //log.info("保存成功 id:[{}]",order.getId());
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void child(){
        log.info("====调用child()==========");
        Order order = new Order();
        order.setTitle("测试定单:child");
        order.setOrderno("child");
        order.setAmount(100);
        order.setStatus(0);
        mapper.save(order);
        //log.info("保存成功 id:[{}]",order.getId());
        int a = 1/0;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值