事务
事务本质是指一组操作,或者全都成功,或者全都失败
一般我们尽可能让全部成功,所以需要重试,但是如果一直失败我们也需要进行相应处理,因此使用超时检测,当超时后触发回滚
所以事务的三要素是:检测执行,重试 和 回滚
数据库事务
ACID,原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)。
原子性、一致性、持久性,实际上是由InnoDB中的两份日志来保证的,一份是redo log日志,一份是undo log日志。
而隔离性 是通过数据库的锁, 加上MVCC来保证的。
所以数据库事务的本质就是如何实现ACID
所以数据库为了实现事务的ACID,分别使用了,缓存快照,多次提交,redo log,undo do,加锁,MVCC等手段
数据库锁分类
共享锁:加锁后其他线程可以读
排它锁:加锁后其他线程读写都不行
Record Locks(行锁)
行锁,顾名思义,是加在索引行(对!是索引行!不是数据行!)上的锁。比如select * from user where id=1 and id=10,就会在id=1和id=10的索引行上加Record Lock。
Gap Locks(间隙锁)
间隙锁,它会锁住两个索引之间的区域。比如select * from user where id>1 and id<10 for update,就会在id为(1,10)的索引区间上加Gap Lock。
Next-Key Locks(间隙锁)
也叫间隙锁,它是Record Lock + Gap Lock形成的一个闭区间锁。比如select * from user where id>=1 and id<=10,就会在id为[1,10]的索引闭区间上加Next-Key Lock。
这样组合起来就有,行级共享锁,表级共享锁,行级排它锁,表级排它锁。
在数据库增删改查四种操作中,insert、delete和update都是会加排它锁的,而select只有显式声明才会加锁:
mysql InnoDB四种隔离级别
异常情况
脏读:读取了脏数据(不存在的数据)。 事务一读 事务二写(未提交) 事务二读(脏数据) 事务二回滚
不可重复读:既可以读取修改的数据,也可以读取新增的数据(幻读)。 事务一读 事务二写(更新已提交) 事务二读(数据不一致,不可重复读)
幻读:仅可以读取新增的数据,但是无法读取修改的数据; 事务一读 事务二写(新增已提交) 事务二读(数据不一致,幻读)
隔离级别和原理
1、读取未提交(可能发生脏读、不可重复读和幻读现象)
原理:读不会加任何锁。而写会加排他锁,并到事务结束之后释放。
2、读取已提交(可能发生不可重复读和幻读现象)
原理: MVCC多版本并发控制
InnoDB在该隔离级别(READ COMMITTED)写数据时,使用排它锁, 读取数据不加锁而是使用了MVCC机制。
因此,在读已提交的级别下,都会通过MVCC获取当前数据的最新快照,不加任何锁,也无视任何锁(因为历史数据是构造出来的,身上不可能有锁)。
但是,该级别下还是遗留了不可重复读和幻读问题: MVCC版本的生成时机: 是每次select时。这就意味着,如果我们在事务A中执行多次的select,在每次select之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读,即:重复读时,会出现数据不一致问题,后面我们会讲解超支现象,就是这种引起的。
3、可重复读(默认)(可能发生幻读现象)
原理:MVCC和 Next-key Lock
针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
READ COMMITTED级别不同的是MVCC版本的生成时机,即:一次事务中只在第一次select时生成版本,后续的查询都是在这个版本上进行,从而实现了可重复读。
但是因为MVCC的快照只对读操作有效,对写操作无效,举例说明会更清晰一点: 事务A依次执行如下3条sql,事务B在语句1和2之间,插入10条age=20的记录,事务A就幻读了。
4、串行化
原理:该级别下,会自动将所有普通select转化为select … lock in share mode执行,即针对同一数据的所有读写都变成互斥的了,可靠性大大提高,并发性大大降低。
Spring事务
java接触的第一个事务就是Spring事务
1、@Transactional注解
2、注入TransactionTemplate 在业务代码里管理事务
传播行为、隔离级别、事务超时时间、事务是否只读等属性都是可以通过参数配置的
事务本质还是指的数据库操作事务,因此Spring事务本质是封装了各个数据库的事务
比如 Mysql建表时设置的引擎类型设置为 MYISAM的话,即使代码里面添加了@Transactional最终事务也不会生效的。
传播行为
2个事务嵌套的时候如何去处理他们的关系
一句话总结就是,或者合并成一个事务,或者事务各自独立,或者报异常
7 种事务传播行为分别是
1、REQUIRED(Spring默认的事务传播类型 required:需要、依赖、依靠)
作用:
如果当前没有事务,则自己新建一个事务,如果当前存在事务则加入这个事务。
举例:
当A调用B的时候:如果A中没有事务,B中有事务,那么B会新建一个事务;如果A中也有事务、B中也有事务,那么B会加入到A中去,变成一个事务,这时,要么都成功,要么都失败。
2、SUPPORTS(supports:支持;拥护)
作用:
当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行。
举例:
如果A中有事务,则B方法的事务加入A事务中,成为一个事务(一起成功,一起失败),如果A中没有事务,那么B就以非事务方式运行(执行完直接提交)
3、MANDATORY(mandatory:强制性的)
作用:
当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
举例:
如果A中有事务,则B方法的事务加入A事务中,成为一个事务(一起成功,一起失败);如果A中没有事务,B中有事务,那么B就直接抛异常了,意思是B必须要支持回滚的事务中运行。
4、REQUIRES_NEW(requires_new:需要新建)
作用:
创建一个新事务,如果存在当前事务,则挂起该事务。B会新建一个事务,A和B事务互不干扰,他们出现问题回滚的时候,也都只回滚自己的事务
5、NOT_SUPPORTED(not supported:不支持)
作用:
以非事务方式执行,如果当前存在事务,则挂起当前事务被调用者B会以非事务方式运行(直接提交),如果当前有事务,也就是A中有事务,A会被挂起(不执行,等待B执行完,返回);A和B出现异常需要回滚,互不影响
6、NEVER(never:从不)
作用:
如果当前没有事务存在,就以非事务方式执行;如果有,就抛出异常。就是B从不以事务方式运行
举例:
A中不能有事务,如果没有,B就以非事务方式执行,如果A存在事务,那么直接抛异常
7、NESTED(nested:嵌套的)
作用:
如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)
隔离级别
5 种事务隔离级别:
1 ,默认直接使用数据库的隔离级别
或者自定义 四种和 mysql一样的隔离级别
2、读取未提交
3、读取已提交
4、可重复读
5、串行化
超时时间
控制事务等待还是回滚的判断时间
只读
在多条查询语句一起执行时,其他事务所提交的写操作数据,对该事务都不可见
分布式事务
一个事务中分布式系统之间互相调用,Spring无法控制,需要自己使用第三方工具控制,如Seata,或者自己写代码实现
一个事务中同时操作多个不同的数据库,Spring也无法控制,可以通过分布式事务方案解决
常用的有五种
seata的解决方案前四种
1、2PC到3PC
2PC:事务协调者收集多个参与者节点的Undo和Todo是否准备好的消息,并不会真正更新数据库,当收到所有节点的Undo和Todo都准备好后统一提交
问题,协调者收集每个参与者节点Undo和Todo后是否准备好的消息,并不知道参与者会不会崩溃,如果这时候崩溃了,那么万一需要回滚,就无法让故障节点执行Undo了
3PC:
第一在阶段(当收到所有节点的Undo和Todo是否准备好的消息)之后,再检测一遍每个节点是否出故障了,没问题再统一提交
第二每个节点都加入了超时机制
问题1、阻塞等待 2、协调者单点故障问题 3、协调者发的提交命令丢失了会导致数据不一致
2、AT 模式
拦截分布式系统的所有对数据库操作的SQL,拼接组合到一起,给本批操作生成before image 和 after image,最后或者都成功或者都失败
问题:如果是共享的数据库,数据被其他人修改了,会产生脏写问题
3、TCC模式
3PC是自动回滚Uodo的,TCC相当于3PC模式里自己写补偿代码,然后控制回滚时候的回调到自己的补偿代码里。
4、SAGA模式
事务链条的模式,只关注自己的事务成功即可,每一步都要写补偿代码,出现故障触发补偿代码,适合长事务与旧有系统无法改造与新业务组合的情况
5、事务消息表
多步操作组合到表的一条记录里,都走完后,该记录状态成功
,检测该表是否存在非状态成功的记录,如果有,则重试,如果重试超过了超时时间,则回调回滚操作。