事务的作用是保证一系列操作的原子性。
原子性是指这一系列操作,要么全部执行,要么全部不执行。
最终目的是保证数据的一致性。
举个例子:用户积分赠送,用户A向用户B赠送1000积分。这个赠送功能是由一系列操作组成的(实际只有两个,用户A积分-1000,用户B积分+1000)。
如果用户A积分-1000成功,而用户B积分+1000的时候出现异常了,会出现1000积分莫名蒸发了。
如果有事务,在用户B积分+1000的时候出现异常,没有完成的时候,会回滚用户A积分-1000操作,整体上看就像没有进行过积分赠送一样,不会有积分蒸发。
常见的有数据库事务、分布式事务。
数据库事务
数据库事务是由数据库服务器消耗大量系统资源提供的特性,因此使用数据库事务有几个原则:
- 能不用则不用,对数据一致性要求不高的场景可以考虑不使用事务。
- 保证事务尽可能小。把一些无关的数据处理和查询操作提取到事务外。
隔离级别
主要是描述了多个并发事务之间的行为。
常见并发问题 | 描述 |
---|---|
丢失更新 | 撤销一个事务时,把其他事务已提交的更新数据覆盖(A和B事务并发执行,A事务执行更新后,提交;B事务在A事务更新后,B事务结束前也做了对该行数据的更新操作,然后回滚,则两次更新操作都丢失了) |
脏读 | 一个事务读到另一个事务未提交的更新数据(A和B事务并发执行,B事务执行更新后,A事务查询B事务没有提交的数据,B事务回滚,则A事务得到的数据不是数据库中的真实数据。也就是脏数据,即和数据库中不一致的数据) |
不可重复读 | 一个事务读到另一个事务已提交的更新数据(A和B事务并发执行,A事务查询数据,然后B事务更新该数据,A再次查询该数据时,发现该数据变化了) |
覆盖更新 | 这是不可重复读中的特例,一个事务覆盖另一个事务已提交的更新数据(即A事务更新数据,然后B事务更新该数据,A事务查询发现自己更新的数据变了) |
幻读 | 一个事务读到另一个事务已提交的新插入的数据(A和B事务并发执行,A事务查询数据,B事务插入或者删除数据,A事务再次查询发现结果集中有以前没有的数据或者以前有的数据消失了) |
MYSQL提供了以下几种隔离级别:
可以通过命令查看当前的隔离级别:
select @@global.tx_isolation,@@tx_isolation;
隔离级别 | 描述 | 并发问题 |
---|---|---|
Serializable(串行化 | 一个事务在执行过程中完全看不到其他事务对数据库所做的更新(事务执行的时候不允许别的事务并发执行。事务串行化执行,事务只能一个接着一个地执行,而不能并发执行。 | 无 |
Repeatable Read(可重复读) | 一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他其他事务对已有记录的更新。 | 幻读 |
Read Commited(读已提交数据) | 一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且能看到其他事务已经提交的对已有记录的更新。 | 不可重复读、覆盖更新、幻读 |
Read Uncommitted(读未提交数据) | 一个事务在执行过程中可以看到其他事务没有提交的新插入的记录,而且能看到其他事务没有提交的对已有记录的更新。 | 丢失更新、脏读、不可重复读、覆盖更新、幻读 |
传播特性
描述的是事务嵌套的行为。
spring提供了7种事务传播特性:
传播特性 | 描述 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。 |
Spring 注解事务使用
小例子地址:http://git.oschina.net/lan4demo/transaction-demo
小例子使用了内存模式的h2数据库,但是有兴趣的朋友可以配置上本地的mysql,使用tcpdump进行抓包,可以对比得出使用数据库事务的不同之处。
分布式事务
分布式事务产生的原因:
- 分库分表
- SOA/微服务
基于XA协议的两阶段提交
有个全局的事务管理器。分析其流程,可以看出能够实现强一致性,缺点也很明显,系统开销很大,不适合并发高的网站,比如电商。
消息事务+最终一致性
以两个系统的协作为例子,该解决方案对系统A和系统B的分布式事务,其实进行了问题转换:
- 分布式事务换成了系统A的本地事务和消息事务。是对消息中间件的一种特殊利用,它是将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败
- 系统A和系统B的协作通过消息队列来完成,在系统A的事务提交了,消息发送出去之后,将会通过消息重投来保证最终一致性。
比如去哪儿网的消息队列中间件QMQ就是通过将消息与业务数据库放在一起实现的消息事务。