共计 4446 个字符,预计需要花费 12 分钟才能阅读完成。
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 操作需满足幂等性。
与通用型解决方案相比,弥补型解决方案的从业务服务不须要革新原有业务逻辑,只须要额定减少一个弥补回滚逻辑即可,业务革新量较小。但要留神的是,业务在一阶段就执行残缺个业务逻辑,无奈做到无效的事务隔离,当须要回滚时,可能存在弥补失败的状况,还须要额定的异样解决机制,比方人工染指