used spring-transaction

Connection 对象

数据库连接 java.sql.Connection 的特性、事务表示、以及和 Java 线程之间的天然关系

  1. Java 事务控制的基本单位(Conection 对象)
    Connection 实例来表示和数据库的一个连接,通信方式TCP/IP,事务控制方法:commit(),rollback()。

    • Connection 是宝贵的系统资源(数据库线程数;增加锁竞争的开销)(用连接池管理)
    • 数据库最多支持多少 Connection 连接(mysql 为例)

    – 查看当前数据库最多支持多少数据库连接
    show variables like ‘%max_connections%’;
    – 设置当前运行时mysql的最大连接数,服务重启连接数将还原
    set GLOBAL max_connections = 200;
    – 修改 my.ini 或者my.cnf 配置文件
    max_connections = 200;

  2. Connection 对象本身的特性
    线性操作:即在操作的时序上,事务和事务之间的执行是线性排开依次执行的
    当建立了 java.sql.Connection 连接后,可以不限次数执行事务SQL请求

  3. 如何在 Java 中实现对 Connection 对象的线性操作

    • Java 多线程访问同一个 java.sql.Connection 会有什么问题?
      Java 多线程访问同一个 java.sql.Connection 会导致事务错乱。
      如两个线程同时在 Connection 上操作事务,语句执行的序列可能表现成了:(此处省略一万字)
      delete zzz; update xxx; insert ttt; rollback; update yyy; commit;

    • 一个线程在整个生命周期中,独占一个 Connection 连接?(不现实)
      Java 中的线程数量可能远超数据库连接数量,会出现僧多粥少的情况;
      Java 线程在工作过程中,真正访问 JDBC 数据库连接所占用的时间比例很短。
      目前普遍的解决方案是:当线程需要做数据库操作时,才会真正请求获取 JDBC 数据库连接,线程使用完了之后,立即释放;
      在线程进行事务操作时,线程在此期间内是独占数据库连接对象的,也就是说,在事务进行期间,有一个非常重要的特性,就是:数据库连接对象可以吸附在线程上,我把这种特性称之为事务对象的线程吸附性 这种特性,正是由于这种特性,在 Spring 实现上,使用了基于线程的 ThreadLocal 来表示这种线程依附行为

    • Java 多线程访问同一个 Connection 的原则
      以资源互斥的方式访问 Connection 对象;
      在线程执行结束时,应当最终及时提交(commit)或回滚(rollback)对 Connection 的影响;不允许存在尚未被提交或者回滚的语句。(防止线程异常释放了锁但事务没完成的情况)

  4. 当一个事务结束,Connection 被释放,而非销毁

  5. 连接池 (统一管理 java.sql.Connection 的容器)
    一般连接池需要如下几个功能:

    • 管理一批 Connection 对象,一般会有连接数上限设置;
    • 为每一个获取 Connection 请求做资源分配;如果资源不足,设置等待时间
    • 根据实际 Connection 的使用情况,为了提高系统之间的利用率,动态调整连接池中 Connection 对象的数量,如应用实际使用的连接数比较少时,会自动关闭掉一些处于无用状态的连接;当请求量大的时候,再动态创建。
  6. Connection 的使用

    • 开启事务(Connection.setAutoCommit(false))之前设置隔离级别
    • commit(),rollback()
    • Connection 对象设置的隔离级别只对该 Connection 对象有效,与其他链接Connection对象无关。

数据库的隔离级别和传播机制

事务的隔离级别,隔离的是什么?

  • 隔离性,是指不同的客户端在做事务操作时,理想状态下,各个客户端之间不会有任何相互影响,好像感知不到对方存在一样。
  • 真正隔离的对象在实现上是数据库资源的互斥性访问,不同的隔离级别就是通过数据库资源划分的不同粒度体现的。
  • 隔离级别的理解
    原理:资源互斥访问的粒度。解决了什么最高级别的问题。没有解决什么最低级别的问题。
  • 数据库性能的一个衡量标准:TPS: 单位时间内的事务数(Transactions Per Second),TPS越高,表示数据库的性能越好。

序列化读(SERIALIZABLE READ)

  • 将整个数据库作为互斥资源 –不同的客户端访问不同的表??
  • 使用数据库的表作为互斥资源 –锁全表,事务隔离的级别最高,即:序列化读(SERIALIZABLE READ)
  • 解决的问题:幻读,并发性能最低
  • 最大 TPS = (1 / T)* N –假设客户端的平均事务操作的耗时为T,资源互斥组数量为N

可重复读(REPEATABLE_READ)

使用表的某行记录作为互斥资源;
解决的最高级别的问题:不可重复读;没解决的最低级别的问题:幻读。

读已提交(READ_COMMITTED)

实际上,数据库在实现原子性(Atomic)时,对于表的特定行,其实有两个状态:Uncommited、Commited,并且使用读写分离锁的机制,读锁只读取 Commited 状态的记录,是共享锁,可同时进行;
在事务要完成 Uncommited—> Commited 的状态转变时,即真正 commit 的时候,则使用写锁以互斥的方式完成。
使用 committed 的行数据作为互斥资源;
解决的最高级别的问题:脏读;没解决的最低级别的问题:不可重复读。

读未提交(READ_UNCOMMITTED)

同样是读写锁分离机制,读并发,写互斥。但是这里允许读锁读取 uncommited 的行数据。
使用 uncommited 的行数据作为互斥资源;
解决的最高级别的问题:修改丢失;没解决的最低级别的问题:脏读。

不加事务隔离级别

产生修改丢失问题

结论

  • 资源互斥粒度控制的越细,客户端事务的并发能力就越高,但是与此同时,会相应地降低数据的一致性。
  • 事务的并发数和数据数据一致性这两个是两个相反的理想指标。

扩展知识

  • 读已提交的不可重复读现象对开发同学有什么启示?
    不可重复读会导致一条行数据两次读取数据可能不一致,这就要求我们在数据库事务操作上,尽可能少用查询出来的结果作为参数执行后续的updateSQL 语句,尽可能使用状态机来保证数据的完整性。这方面的知识可以单独开一个课题来讨论 :如何使用数据库来保证业务数据的逻辑完整性?
  • 不同问题产生的方式?
    • 幻影读:读数据的时候插入了新数据
    • 不可重复读:读到的数据被修改了
    • 脏读:撤销修改,读到脏数据
    • 修改丢失:修改覆盖

Spring 基于事务和连接池的抽象和设计

Spring 事务的实现原理

spring 的事务概述

  • 基础类:TransactionDefinition,TransactionStatus,PlatformTransactionManager,SavepointManager
  • 声明式事务:aop;类:TransactionTemplate
  • 编程式事务:模板方法;类:TransactionAspectSupport,TransactionInfo
  • 事务同步器:TransactionSynchronizationManager,TransactionSynchronizationAdapter
  • 注册事务同步器;可以在外层 try{}catch(){} 异常,保证不影响主流程。TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {})
  • 注解事务手动回滚:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

注解事务

1
2
//开启基于注解的声明式事务  
<tx:annotation-driven transaction-manager="transactionManager" />

实现主要功能的类
TxNamespaceHandler,AnnotationDrivenBeanDefinitionParser
InfrastructureAdvisorAutoProxyCreator 间接实现 InstantiationAwareBeanPostProcessor
AbstractAutoProxyCreator # postProcessAfterInitialization //主要工作有两点:

  • 找出指定 bean 对应的增强器
  • 根据找出的增强器创建代理

AnnotationTransactionAttributeSource
BeanFactoryTransactionAttributeSourceAdvisor //事务增强器
TransactionInterceptor //支撑整个事务功能的架构,主要完成的工作:

  • 获取事务,处理已经存在的事务,准备事务信息
  • 回滚条件,回滚处理,回滚后信息清除
  • 提交事务

关于事务的一些基础验证:

  1. Connection 设置事务的隔离级别会改变数据库的隔离级别吗?(不会,仅针对本次调用)
  2. 同一个 spring 事务里面,上面的修改对下面可见吗??(可见)不同的 spring 事务里面,前面的修改可见吗??(不可见)
  3. 事务方法中,先将数据查出,再在代码里做减法,最后更新数据,这样在并发的情况下会不会产生修改丢失??(会修改丢失,加上版本号)