Connection 对象
数据库连接 java.sql.Connection 的特性、事务表示、以及和 Java 线程之间的天然关系
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;Connection 对象本身的特性
线性操作:即在操作的时序上,事务和事务之间的执行是线性排开依次执行的
当建立了 java.sql.Connection 连接后,可以不限次数执行事务SQL请求如何在 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 的影响;不允许存在尚未被提交或者回滚的语句。(防止线程异常释放了锁但事务没完成的情况)
当一个事务结束,Connection 被释放,而非销毁
连接池 (统一管理 java.sql.Connection 的容器)
一般连接池需要如下几个功能:- 管理一批 Connection 对象,一般会有连接数上限设置;
- 为每一个获取 Connection 请求做资源分配;如果资源不足,设置等待时间
- 根据实际 Connection 的使用情况,为了提高系统之间的利用率,动态调整连接池中 Connection 对象的数量,如应用实际使用的连接数比较少时,会自动关闭掉一些处于无用状态的连接;当请求量大的时候,再动态创建。
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 | //开启基于注解的声明式事务 |
实现主要功能的类
TxNamespaceHandler,AnnotationDrivenBeanDefinitionParser
InfrastructureAdvisorAutoProxyCreator 间接实现 InstantiationAwareBeanPostProcessor
AbstractAutoProxyCreator # postProcessAfterInitialization //主要工作有两点:
- 找出指定 bean 对应的增强器
- 根据找出的增强器创建代理
AnnotationTransactionAttributeSource
BeanFactoryTransactionAttributeSourceAdvisor //事务增强器
TransactionInterceptor //支撑整个事务功能的架构,主要完成的工作:
- 获取事务,处理已经存在的事务,准备事务信息
- 回滚条件,回滚处理,回滚后信息清除
- 提交事务
关于事务的一些基础验证:
- Connection 设置事务的隔离级别会改变数据库的隔离级别吗?(不会,仅针对本次调用)
- 同一个 spring 事务里面,上面的修改对下面可见吗??(可见)不同的 spring 事务里面,前面的修改可见吗??(不可见)
- 事务方法中,先将数据查出,再在代码里做减法,最后更新数据,这样在并发的情况下会不会产生修改丢失??(会修改丢失,加上版本号)