Spring 事务处理超级详细详解

事务是数据库逻辑上的一组操做,一个事务中的一组操做,要么都执行,要么都不执行。
复制代码

事务的四大特性(ACID)

Atomicity原子性:整个事务中的全部操做,要么所有完成,要么所有不完成,不可能停滞在中间某个环节。事务在执行过程当中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务历来没有执行过同样。mysql

Consistency一致性:整个事务执行先后数据库是保持一致的,保证数据库数据的完整性和正确性。算法

Isolation隔离性:各个并发事务之间不会互相干扰,多个并发事务之间互相隔离。(4个隔离级别)sql

Durability持久性:持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即使是在数据库系统遇到故障的状况下也不会丢失提交事务的操做。数据库

redo,undo,binlog二进制编程

事务日志

redolog 重作日志,用来保证事务的持久性,在Innodb存储引擎下,Insert,Delete,Update操做记录的都是redo物理日志,记录的是数据页的物理变化,主要用于数据库的崩溃恢复。 Redo日志能够分为两部分,一个是内存中存储的redo日志缓存(redo log buffer),是容易丢失的,一个是存储在本地磁盘的redo日志文件,是持久的。 redo日志整个产生流程:缓存

  • 第一步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝安全

  • 第二步:生成一条重作日志并写入redo log buffer,记录的是数据被修改后的值bash

  • 第三步:当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加写的方式session

  • 第四步:按期将内存中修改的数据刷新到磁盘中(innodb_flush_log_at_trx_commit设置策略0,1,2)并发

innodb_flush_log_at_trx_commit: 0: 每秒刷新到磁盘log(在mysql故障时可能会丢失最后1秒的数据) 1:每次提交都刷新日志到磁盘log(mysql默认) 2:每次提交都刷新到osbuffer(系统缓存),但每秒才刷新到磁盘log(在mysql故障时不影响,操做系统故障丢失最后1s数据)

undo log日志

undo主要记录的是数据的逻辑变化,为了在发生错误时回滚到以前的状态,须要把以前的操做记录下来。

undo日志,只将数据库逻辑地恢复到原来的样子,在回滚的时候,它其实是作的相反的工做,好比一条INSERT ,对应一条 DELETE,对于每一个UPDATE,对应一条相反的 UPDATE,将修改前的行放回去。undo日志用于事务的回滚操做进而保障了事务的原子性。

在InnoDB存储引擎中,undo存储在回滚段(Rollback Segment)中,每一个回滚段记录了1024个undo log segment,而在每一个undo log segment段中进行undo 页的申请,在5.6之前,Rollback Segment是在共享表空间里的,5.6.3以后,可经过 innodb_undo_tablespace设置undo存储的位置。

undo的类型 在InnoDB存储引擎中,undo log分为:

insert undo log

update undo log

insert undo log是指在insert 操做中产生的undo log,由于insert操做的记录,仅对事务自己可见,对其余事务不可见,故该undo log能够在事务提交完成后直接删除。

update undo log是指在update和delete操做中产生的undo log,该undo log可能须要提供mvcc机制(多版本并发控制),不能在事务提交后就删除undo log,等待purge线程进行最后的删除。(purge线程是指在Innodb存储引擎中,delete操做并非直接删除数据,而是在要删除的数据上标识Delete_Bit,也就是平时所说的逻辑删除,purge线程会去清除带有Delete_Bit标识的数据)

undo日志并非redo日志的逆过程,redo日志记录的是物理日志,是持久存在的,而undo日志是逻辑日志,对事物回滚时,只是把数据库恢复到以前的状态,每一个undo的生命周期只是从事务开始到事务结束。

假设有A、B两个数据,值分别为1,2.

1. 事务开始
2. 记录A=1到undo log
3. 修改A=3
4. 记录A=3到 redo log
5. 记录B=2到 undo log
6. 修改B=4
7. 记录B=4到redo log
8. 将redo log写入磁盘
9. 事务提交
复制代码

binlog二进制日志

二进制日志是在存储引擎之上的层面,redo和undo是Innodb引擎中的操做日志,而binlog二进制日志是在引擎之上,所以无论数据库采用Innodb默认存储引擎仍是其余存储引擎,都会产生binlog。 虽然binlog和redolog都是记录对数据库的操做,可是二者却不同: 1.binlog记录不论是什么引擎,都会记录对数据库的操做,而redolog记录的是InnoDB引擎下对表的操做,而且binlog先于redolog记录。 2.binlog在commit后一次性写入缓存中的日志文件,而redolog则在数据准备修改前写入redobufferlog缓存中,写入完成后才会执行数据修改操做,待事务完成后会刷新到持久性redolog磁盘文件中。 3.事务日志是记录的是物理页的变化,具备幂等性,记录方式比较简洁。好比在一个事务中进行了某行数据的添加,删除,又添加,最终事务日志记录的只是最后添加的记录,也就是物理页的变化。而二进制日志则会把这几回操做所有记录下来,记录比较多。

sync_binlog:sync_binlog 是 MySQL 的二进制日志(binlog)同步到磁盘的频率。MySQL server 在 binary log 每写入 sync_binlog 次后,刷写到磁盘。 若是 autocommit 开启,每一个语句都写一次 binary log,不然每次事务写一次。默认值是 0,不主动同步,而依赖操做系统自己不按期把文件内容 flush 到磁盘。设为 1 最安全,在每一个语句或事务后同步一次 binary log,即便在崩溃时也最多丢失一个语句或事务的日志,但所以也最慢。

大多数状况下,对数据的一致性并无很严格的要求,因此并不会把 sync_binlog 配置成 1. 为了追求高并发,提高性能,能够设置为 100 或直接用 0. 而和 innodb_flush_log_at_trx_commit 同样,对于支付服务这样的应用,仍是比较推荐 sync_binlog = 1.

事务的加锁方式

为了维护事务隔离性与一致性,通常数据库采用的加锁的方式。以Mysql为例,主要有表锁与行锁,固然如今也提供了MetaData元数据锁。因为数据库是一个高并发应用,若是加锁过分就会极大下降数据库的并发处理能力,因此这里分析一下InnoDB引擎数据库的加锁机制。

数据库遵循两段锁协议,将一个事务分为两个阶段,即加锁阶段与解锁阶段。

加锁阶段:表锁通常应用与数据库DDL操做,整张表加锁,加锁过程当中不容许任何操做。因为表锁太过无解,因此又出现了行锁。行锁只做用于那一行数据,其余行的DML操做都不会受到影响。行锁又分为共享锁(S锁,其余事务能够继续加共享锁,不能加排它锁)与排它锁(X锁,其余事务不能加任何锁)。

共享锁:又称为读锁,其余事务能够一块读取这行数据,可是不能进行DML操做。

排它锁:又称为写锁,其余任何事务不能进行加锁操做,可是能够正常读取数据(读取结果为事务以前的数据),由于Mysql InnoDB引擎默认为update,insert,delete语句加上排他锁,select语句默认不加锁。(每句sql都是一个事务,可经过select lock in share mode为select加共享锁,经过select for update为select加排它锁)。

解锁阶段:事务commit提交后释放锁。

隔离级别

在数据库的并发操做中,为了保证数据的正确性,也就是维护事务的隔离性与一致性。这些数据库锁也正是为这些而存在的。
复制代码

  • 未提交读(Read Uncommited):容许脏读,也就是能够读取到其余未提交的事务修改的数据。
  • 已提交读(Read Committed):只能读取到已提交的数据。(Oracle默认隔离级别)
  • 可重复读(Repeatable Read):可重复读,在同一个事务中,查询到的数据都是事务开始时候的数据,InnoDB默认隔离级别,消除了不可重复读,可是会存在幻读。(当一个事务开始执行过程当中(没有加锁),另一个事务获取锁修改了这条数据并提交,那么这个事务读取到的数据仍是最开始时候的数据。)
  • 串行化(Serializable):彻底串行化,获取表锁,读写都会阻塞。

经过 set session/global transaction isolation level +隔离级别设置隔离级别

不可重复读与幻读的区别

不可重复读主要在于update与delete,而幻读主要在于insert。可重读经过在读取时候加锁,可实现可重复读。可是却没法锁住insert数据,当事务A先前读取了数据,或者修改了所有数据,事务B仍是能够insert数据提交,这时事务A就会发现莫名其妙多了一条以前没有的数据,这就是幻读。幻读能够经过表锁来实现,也就是Serializable,可是会极大下降并发能力。

可是mysql是如何解决幻读问题的呢?

下面就了解一下乐观锁和悲观锁。

悲观锁:正如其名,它指的是对数据被外界(包括本系统当前的其余事务,以及来自外部系统的事务处理)修改持保守态度,所以,在整个数据处理过程当中,将数据处于锁定状态。悲观锁的实现,每每依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,不然,即便在本系统中实现了加锁机制,也没法保证外部系统不会修改数据)。在悲观锁的状况下,为了保证事务的隔离性,就须要一致性锁定读。读取数据时给加锁,其它事务没法修改这些数据,相似select... for update这样的语句。修改删除数据时也要加锁,其它事务没法读取这些数据。

乐观锁:相对悲观锁而言,乐观锁机制采起了更加宽松的加锁机制来解决事务的隔离性。悲观锁大多数状况下依靠数据库的锁机制实现,以保证操做最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销每每没法承受。 乐观锁,使用CAS实现,而CAS算法大可能是基于数据版本( Version )记录机制实现。即在表中添加一个version字段,读取出数据时,将此版本号一同读出,以后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,若是提交的数据版本号大于数据库表当前版本号,则予以更新,不然认为是过时数据。

目前成熟的数据库如Mysql,Oracle都是使用了以乐观锁为理论基础的MVCC(多版本并发控制)来避免幻读和不可重复读。

源码解析

Spring支持两种的事务使用:编程式事务与声明式事务。
复制代码

编程式事务:经过在业务代码中对事务作手动回滚,对代码入侵性强,不推荐使用.(TransactionAspectSupport,TransactionTemplate) 声明式事务:使用@Transactional注解。 Spring事务中,主要包含TransactionDefinition,TransactionStatus,PlatformTransactionManager,所谓的事务管理,其实就是"按照给定的事务规则执行事务的提交或回滚操做",TransactionDefination就表示给定的事务规则,TransactionStatus表示运行着的事务状态,PlatformTransactionManager用来执行事务操做。

TransactionDefinition

TransactionDefinition用于定义一个事务,包括事务的传播熟悉,隔离级别,超时时间,只读等属性,默认使用DefaultTransactionDefinition,也支持自定义配置。

PlatformTransactionManager

PlatformTransactionManager用于事务的执行操做,接口定义以下:

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}
复制代码

根据底层所使用的不一样的持久化 API 或框架,PlatformTransactionManager 的主要实现类大体以下:

  • DataSourceTransactionManager:适用于使用JDBC和iBatis进行数据持久化操做的状况。

  • HibernateTransactionManager:适用于使用Hibernate进行数据持久化操做的状况。

  • JpaTransactionManager:适用于使用JPA进行数据持久化操做的状况。

  • 另外还有JtaTransactionManager JdoTransactionManager、JmsTransactionManager等等。

TransactionStatus

TransactionStatus表示事务的状态,经过PlatformTransactionManager.getTransaction()获取,TransactionStatus接口提供了一个简单的事务控制和事务查询的方法。

public  interface TransactionStatus{
   boolean isNewTransaction();
   void setRollbackOnly();
   boolean isRollbackOnly();
}
复制代码

声明式事务主要是经过Spring AOP实现的,对使用@Transactional注解的方法进行拦截,AOP经过代理方式执行目标方法,

根据invokeWithinTransaction方法建立PlatformTransactionManager,TransactionStatus,TransactionDefinition,从源码中能够看到TransactionInfo,表示事务对象,对前面几个进行封装。

class TransactionInfo{
    private final PlatformTransactionManager transactionManager;

    private final TransactionAttribute transactionAttribute;

	private final String joinpointIdentification;

	private TransactionStatus transactionStatus;

	private TransactionInfo oldTransactionInfo;
}
复制代码

, 经过代理执行目标方法,若是方法执行完毕成功则执行commitTransactionAfterReturning进行事务提交,经过TransactionInfo.getTransactionManager().cimmit(),若是方法执行过程当中出现异常并捕获到,则执行completeTransactionAfterThrowing进行事务回滚,TransactionInfo.getTransactionManager().rollback();

经过ThreadLocal本地线程变量管理TransactionInfo,表示Spring事务是线程安全的。
相关文章
相关标签/搜索