1、聊聊事务原理
一句话来说,要么成功,要么失败,不能有中间态
1.1、回忆一下ACID
- Atomic:原子性,就是一堆SQL,要么一起成功,要么都别执行,不允许某个SQL成功了,某个SQL失败了,这就是扯淡么
- Consistency:一致性,这个是针对数据一致性来说的,就是一组SQL执行之前,数据必须是准确的,执行之后,数据也必须是准确的。别搞了半天,执行完了SQL,结果SQL对应的数据修改没给你执行,这不是坑爹么
- Isolation:隔离性,这个就是说多个事务在跑的时候不能互相干扰,别事务A操作个数据,弄到一半儿还没弄好呢,结果事务B来改了这个数据,导致事务A的操作出错了,那不就搞笑了么
- Durability:持久性,事务成功了,就必须永久对数据的修改是有效的,别过了一会儿数据自己没了,不见了,那就好玩儿了
1.2、老生常谈的事务隔离级别
- 读未提交(Read Uncommitted):这个很坑爹,就是说某个事务还没提交的时候,修改的数据,就让别的事务给读到了,这就恶心了,很容易导致出错的。这个也叫做脏读
- 读已提交(Read Committed)又叫做不可重复读:这个比上面那个稍微好一点,但是一样比较尴尬,就是说事务A在跑的时候, 先查询了一个数据是值1,然后过了段时间,事务B把那个数据给修改了一下还提交了,此时事务A再次查询这个数据就成了值2了,这是读了人家事务提交的数据啊,所以是读已提交。这个也叫做不可重复读,就是所谓的一个事务内对一个数据两次读,可能会读到不一样的值
- 可重复读(Read Repeatable):这个就是比上面那个再好点儿,就是说事务A在执行过程中,对某个数据的值,无论读多少次都是值1;哪怕这个过程中事务B修改了数据的值还提交了,但是事务A读到的还是自己事务开始时这个数据的值
- 串行化:幻读,不可重复读和可重复读都是针对两个事务同时对某条数据在修改,但是幻读针对的是插入,比如某个事务把所有行的某个字段都修改为了2,结果另外一个事务插入了一条数据,那个字段的值是1,然后就尴尬了。第一个事务会突然发现多出来一条数据,那个数据的字段是1。如果要解决幻读,就需要使用串行化级别的隔离级别,所有事务都串行起来,不允许多个事务并行操作
2、MySQL的事务隔离级别剖析一下
2.1、InnoDB存储引擎架构设计
2.2、MySQL是怎么实现RR事务隔离的
2.2.1、Redo Log日志了解一下
2.2.2、MVC Undo Log日志链条
- 事务A,插入了一条数据
- 接着有一个事务B跑来修改了一下这条数据
- 接着假设事务C又来修改了一下这个值为C
2.2.3、 MySQL RR readview + undo log日志链
- 首先假设有一条数据是事务id=50的一个事务插入,之后有事务A和事务B同时在运行
- 事务A发起一个查询,他就是第一次查询就会生成一个ReadView,此时ReadView里的creator_trx_id是60,min_trx_id是60,max_trx_id=71,m_ids=[60,70]
- 这个时候事务A基于这个ReadView去查询这条数据,会发现这条数据的trx_id=50,是小于ReadView里的min_trx_id的,
说明他发起查询之前,早就有事务插入这条数据了还提交了所以此时可以查到这条原始数据
- 接着事务B此时更新了这条数据的值为B,此时会修改trx_id=70,同时生成一个undo log,而且此时事务B还提交了,也就是说事务B已经结束了
- 这个时候有一个问题,ReadView中的m_ids此时还会是60和70吗?
那是必然的,因为ReadView一旦生成就不会改变了,这个时候虽然事务B已经结束了,但是事务A的ReadView里,还是会有60和70两个事务id,意思就是说,你在事务A开启的时候,事务B当时是在运行的
那好,接着此时事务A去查询这条数据的值,他会惊讶的发现此时数据的trx_id是70了,70一方面是在ReadView的min_trx_id和max_trx_id的范围区间的,同时还在m_ids列表中
说明起码是事务A开启查询的时候,id=70的这个事务B还是在运行的,然后由这个事务B更新了这条数据,此时事务A是不能查询到事务B的这个值的,因此这个时候只能顺着指针往历史版本链条上去找
接着事务顺着指针找到下面一条数据,trx_id为50,是小于ReadView的min_trx_id的,说明在他开启查找之前,就已经提交这个事务了,所以事务A是可以查询到这个值的,此时事务A查到的是原始值
你事务A多次读同一个数据,每次读到的都是一样的值,除非是他自己修改了值,否则读到的都是一样的值,不管别的事务如何修改数据,事务A的ReadView始终是不变的,他基于这个ReadView始终看到的值是一样的
- 接着此时事务A再次查询,此时发现符合条件有2条数据,一条是原始值的那个数据,一条是事务C插入的那条数据,但是事务C插入的那条数据的trx_id是80,这个80是大于自己的ReadView的max_trx_id的,说明是自己发起查询之后,这个事务才启动的,所以此时这条数据是不能查询的,所以MySQL RR避免了幻读
3、XA规范了解一下
X/Open的组织定义了分布式事务的模型,这里面有几个角色,就是AP(Application,应用程序),TM(Transaction Manager,事务管理器),RM(Resource Manager,资源管理器),CRM(Communication Resource Manager,通信资源管理器)
TM的话就是一个在系统里嵌入的一个专门管理横跨多个数据库的事务的一个组件,RM的话说白了就是数据库(比如MySQL),CRM可以是消息中间件(但是也可以不用这个东西)
那么XA是啥呢?说白了,就是定义好的那个TM与RM之间的接口规范,就是管理分布式事务的那个组件跟各个数据库之间通信的一个接口
这个XA仅仅是个规范,具体的实现是数据库产商来提供的,比如说MySQL就会提供XA规范的接口函数和类库实现
JTA(Java Transaction API),是J2EE的编程接口规范,它是XA协议的JAVA实现,例如Atomikos, bitronix都提供了jar包方式的JTA实现框架。这样我们就能够在Tomcat或者Jetty之类的服务器上运行使用JTA实现事务的应用系统,一般是单机跨多库情况下,但是在微服务的今天,一般都是一个服务一个库,一般都不会这么干了
3.1、2PC是啥?
X/Open组织定义的一套分布式事务的模型,还是比较虚的,还没办法落地,而且XA接口规范也是一个比较务虚的一个东西,还是没法落地的
2PC说白了就是基于XA规范搞的一套分布式事务的理论,也可以叫做一套规范,或者是协议
缺点:
1、同步阻塞:在阶段一里执行prepare操作会占用资源,一直到整个分布式事务完成,才会释放资源,这个过程中,如果有其他人要访问这个资源,就会被阻塞住
2、单点故障:TM是个单点,一旦挂掉就完蛋了
3、事务状态丢失:即使把TM做成一个双机热备的,一个TM挂了自动选举其他的TM出来,但是如果TM挂掉的同时,接收到commit消息的某个库也挂了,此时即使重新选举了其他的TM,压根儿不知道这个分布式事务当前的状态,因为不知道哪个库接收过commit消息,那个接收过commit消息的库也挂了
4、脑裂问题:在阶段二中,如果发生了脑裂问题,那么就会导致某些数据库没有接收到commit消息,那就完蛋了,有些库收到了commit消息,结果有些库没有收到,这咋整呢,那肯定完蛋了
3.1、3PC是啥?
如果人家TM在DoCommit阶段发送了abort消息给各个库,结果因为脑裂问题,某个库没接收到abort消息,自己还执行了commit操作,不是也不对么
4、TCC是个什么原理(柔性事务)
try、confirm和cancel
4.1、允许空回滚
事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会出现因为丢包而导致的网络超时,此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,而 Cancel 操作调用未出现超时
TCC 服务在未收到 Try 请求的情况下收到 Cancel 请求,这种场景被称为空回滚;空回滚在生产环境经常出现,用户在实现TCC服务时,应允许允许空回滚的执行,即收到空回滚时返回成功
4.2、防悬挂控制
事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会出现因网络拥堵而导致的超时,此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,Cancel 调用未超时;在此之后,拥堵在网络上的一阶段 Try 数据包被 TCC 服务收到,出现了二阶段 Cancel 请求比一阶段 Try 请求先执行的情况,此 TCC 服务在执行完到的 Try 之后,将永远不会再收到二阶段的 Confirm 或者 Cancel ,造成 TCC 服务悬挂。
用户在实现 TCC 服务时,要允许空回滚,但是要拒绝执行空回滚之后 Try 请求,要避免出现悬挂
4.3、TCC vs 2PC
2PC:是资源层面的分布式事务,一直会持有资源的锁。 如果跨十几个库,一下锁这么多数据库,会导致,极度浪费资源。降低了吞吐量
TCC:在业务层面的分布式事务,最终一致性,不会一直持有锁。将锁的粒度变小,每操作完一个库,就释放了锁。但是需要原本一个接口要拆三个接口,比较麻烦
用2PC 比 TCC 要性能高。因为tcc多了多次接口调用。而此时的2PC 不怕占用资源,反正就一个调用。高并发场景下TCC 优势要大
4.4、异步确保型TCC技术方案
如果要接入到一个TCC分布式事务中来,从业务服务必须改造自己的接口,本来就是一个接口,现在要新增两个接口,try接口,cancel接口。改造起来比较麻烦
这个大概来说就是把之前的通用型TCC方案给改造了一下,就是在主业务服务和从业务服务之间加了一个可靠消息服务,但是这个可靠消息服务可不是在请求什么MQ之类的东西,而是将消息放在数据库里的
4.5、补偿性TCC解决方案
补偿型 TCC 解决方案与通用型 TCC 解决方案的结构相似,其从业务服务也需要参与到主业务服务的活动决策当中。但不一样的是,前者的从业务服务只需要提供 Do 和 Compensate 两个接口,而后者需要提供三个接口
Do 接口直接执行真正的完整业务逻辑,完成业务处理,业务执行结果外部可见;Compensate 操作用于业务补偿,抵消或部分抵消正向业务操作的业务结果,Compensate操作需满足幂等性。
与通用型解决方案相比,补偿型解决方案的从业务服务不需要改造原有业务逻辑,只需要额外增加一个补偿回滚逻辑即可,业务改造量较小。但要注意的是,业务在一阶段就执行完整个业务逻辑,无法做到有效的事务隔离,当需要回滚时,可能存在补偿失败的情况,还需要额外的异常处理机制,比如人工介入
6、可靠消息最终一致性方案(RocketMQ)
7、最大努力通知方案
8、saga 长事务解决方案
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net