隔离级别 + 事务 + 连接池 + 锁
参考文章:
Spring是如何保证同一事务获取同一个Connection的
隔离级别
针对这个隔离性及并发事务可能出现的几种问题,在数据库中定义了如下的四种隔离级别用来解决这种并发事务导致的问题。
(1)读未提交(READ UNCOMMITED)
允许一个事务读取到其他事务未提交的数据,会出现脏读,不可重复读和幻读的问题。
(2)读已提交(READ COMMITED)
只允许一个事务读取其他事务已经提交的数据,可以避免脏读,但是不能避免不可重复读和幻读的问题。
(3)可重复读(REPEATABLE READ)
确保一个事务多次读取某一行都是相同的值,在读取期间,禁止其他事务对改数据进行更新操作,避免了脏读和不可重复读,但还是会出现幻读。这种隔离级别是 MySQL 的默认隔离级别。
(4)串行化(SERIALIZABLE)
保证一个事务多次读取同一行都是相同的数据,在读取期间,禁止其他事务对该表进行更新,删除,插入操作,避免了脏读,不可重复读,幻读,但是性能低下,相当于所有的并发事务都被串行去执行了。
Spring 事务
只读属性
可以通过事务管理器中的属性来指定该事务为一个只读事务,只读事务表示该事务只读读取数据,不进行数据的更新操作。指定了只读属性之后,数据库引擎会对该事务进行优化。
事务超时属性
事务在运行过程中会获取到表上或者数据行上的锁,如果长时间持有锁,可能会导致其他操作阻塞比较长的时间,影响应用的性能。所以可以通过指定事务的超时属性来强制事务最多经过多长时间之后必须要回滚,释放资源。
事务传播
REQUIRED
如果有事务在运行,当前的方法就在这个事务内运行,否则就开启一个新的事务,并在自己的事务内运行,默认传播行为。
REQUIRED_NEW
如果当前存在事务,则暂停当前的事务,重新创建一个新的事务(如果当前没有事务,则直接创建就行)。具体做法是:将当前事务封装到一个实体中,然后去创建一个新的事务,新的事务接收这个实体为参数,用于事务的恢复。如果当前有事务,经过这个操作之后,就会存在着两个事务,这两个事务之间没有任何依赖关系,可以实现新事务回滚,外部事务可以继续执行
数据库连接池
Connection
这种是线程不安全的,同一时刻是不能被多个线程共享的。
JDBC 事务处理
1.数据一旦提交,就不可回滚。
2.数据什么时候意味着提交?
当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成
功,就会向数据库自动提交,而不能回滚。
关闭数据库连接,数据就会自动的提交如果多个操作,每个操作使用的是自己单独的连接,则无法
保证事务。即同一个事务的多个操作必须在同一个连接下。
3.JDBC 程序中为了让多个 SQL 语句作为一个事务执行:
调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
在出现异常时,调用 rollback(); 方法回滚事务
若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态
setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行 close()方法前,建议恢复自动提
交状态
Spring 事务获取同一个连接
Spring 有声明式事务和编程式事务: 声明式事务只需要提供@Transactional
的注解,然后事务的开启和提交/回滚、资源的清理就都由 spring 来管控,我们只需要关注业务代码即可; 编程式事务则需要使用 spring 提供的模板,如TransactionTemplate
,或者直接使用底层PlatformTransactionManager
手动控制提交、回滚。
事务同步管理类
TransactionSynchronizationManager 内部使用了很多的ThreadLocal
为不同的事务线程提供了独立的资源副本,并同时维护这些事务的配置属性和运行状态信息 (比如强大的事务嵌套、传播属性和这个强相关)
事务名称 currentTransactionName
事务是否是只读 currentTransactionReadOnly
事务隔离级别 currentTransactionIsolationLevel
事务是否开启 actualTransactionActive
是否新事务 TransactionStatus.isNewTransaction()
场景描述:
场景 1:
有些场景比如我们使用MyBatis
的时候,某些场景下,可能无法使用 Spring 提供的模板类来达到效果,而是需要直接操作源生 API Connection
。
那如何拿到这个链接 Connection 呢???(主意此处打大前提:必须保证和当前 MaBatis 线程使用的是同一个链接,这样才接受本事务控制嘛,否则就脱缰了~)
DataSourceUtils 提供此功能
场景 2:
模块 A-》模块 B 在不同事务中处理数据时,模块 A 在开启事务后,查询不到模块 B 已经提交的事务。
这是由于 mysql 的隔离级别为可重复读导致的,开启 spring 事务时指定隔离级别为读已提交即可。
TransactionSynchronization
使用得最多的是afterCommit
和afterCompletion
:
事务虽然已经提交,但是我的连接可能还是活动的(比如使用了连接池链接是不会关闭的)
若你的回调中刚好又使用到了这个链接,它会参与到原始的事务里面去
这个时候你参与到了原始事务,但是它并不会给你
commit提交
。(所以你在这里做的 update、insert 等默认都将不好使)回收资源(链接)的时候,因为你使用的就是原始事务的资源,所以 Spring 事务还会给你回收掉,从而就可能导致你的程序出错
事务提交:
后续该connection
是不可能再执行connection.commit()
方法了的,因为同一个事务只可能被提交一次。从上面理论知道:即使我们在afterCommit()
里执行,Spring 也保证了我拿到的链接还是当前线程所属事务的Connection
因此我继续猜测:connection
的自动提交功能可能是在这期间被恢复了,从而导致了这条 SQL 语句它的自动提交成功
数据库锁
事务在并发执行时访问相同记录的情况大致可以分为以下三种:
1、读——读情况:读取操作本身不会对记录产生任何影响,不会引起什么问题
2、写——写情况:并发事务相继对相同的记录进行改动,可能会引发很严重的问题。
3、读——写或写——读情况:也就是一个事务进行读取操作,另一个事务进行改动操作
版权声明: 本文为 InfoQ 作者【hasWhere】的原创文章。
原文链接:【http://xie.infoq.cn/article/9de98615dfd6aa56bc2623003】。文章转载请联系作者。
评论