「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战」
隔离级别
- 隔离级别是用来针对数据不一致的情况进行修复的。
- 隔离效果越好,并行执行效率也就越差。(要做各种检查以及锁定)
读未提交(Read Uncommitted)
Read Uncommitted是隔离级别最低的一种事务级别。在这种隔离级别下,一个事务会读到另一个事务更新后但未提交的数据,如果另一个事务回滚,那么当前事务读到的数据就是脏数据,这就是脏读(Dirty Read)。
读未提交,可以称为最基本的隔离级别,并没有什么隔离能力。
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别是最低的隔离级别,虽然拥有超高的并发处理能力及很低的系统开销,但很少用于实际应用。因为采用这种隔离级别只能防止第一类更新丢失问题,不能解决脏读,不可重复读及幻读问题。
读已提交
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别可以防止脏读问题,但会出现不可重复读及幻读问题。
可重复读
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。这种隔离级别可以防止除幻读外的其他问题。
串行化
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读、第二类更新丢失问题。在这个级别,可以解决上面提到的所有并发问题,但可能导致大量的超时现象和锁竞争,通常数据库不会用这个隔离级别,我们需要其他的机制来解决这些问题:间隙锁,乐观锁,悲观锁。
从MVCC并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。
Serializable隔离级别下,读写冲突,因此并发度急剧下降,在MySQL/InnoDB下不建议使用。
MVCC
//todo
查看隔离级别
查看当前数据库全局设置的隔离级别:
select @@global.tx_isolation;
主流的互联网公司(包括目前的公司),使用的都是读已提交(Read Commited) 。
隔离级别为RC的原因
笔记
-
MySQL的默认隔离级别为什么是RR?
-
主从复制:基于binlog进行复制
-
binlog:简单理解为binlog是一个记录数据库更改的文件 //todo
-
binlog格式
- statement:记录的是修改SQL语句
- row:记录的是每行实际数据的变更
- mixed:statement和row模式的混合
-
-
MySQL在5.0之前binlog只支持STATEMENT格式
-
而这种格式在读已提交(Read Commited) 这个隔离级别下主从复制是有bug的
-
此时在主(master)库上执行下列语句
select * from test;输出如下
+---+ | b | +---+ | 3 | +---+ 1 row in set但是,你在此时在从(slave)库上执行该语句,得出输出如下
Empty set这样,你就出现了主从不一致性的问题!原因其实很简单,就是在master上执行的顺序为先删后插!而此时binlog为STATEMENT格式,它记录的顺序为先插后删!(也就是说binlog记录实际上是按照事务提交顺序先后来的,这里session2先提交,所以binlog先记录执行插入的SQL语句,然后才是session1),从(slave)库同步的是binlog,因此从机执行的顺序和主机不一致!就会出现主从不一致! 如何解决? 解决方案有两种! (1)隔离级别设为可重复读(Repeatable Read) ,在该隔离级别下引入间隙锁。当
Session 1执行delete语句时,会锁住间隙。那么,Ssession 2执行插入语句就会阻塞住! (2)将binglog的格式修改为row格式,此时是基于行的复制,自然就不会出现sql执行顺序不一样的问题!奈何这个格式在mysql5.1版本开始才引入。因此由于历史原因,mysql将默认的隔离级别设为可重复读(Repeatable Read) ,保证主从复制不出问题!
-
-
因此Mysql将可重复读(Repeatable Read) 作为默认的隔离级别。
-
-
选RC不选RR的原因:
- 缘由一:在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大的多
- 缘由二:在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行
- 缘由三:在RC隔离级别下,半一致性读(semi-consistent)特性增加了update操作的并发性 //todo 半一致读
-
在RC级别下,不可重复读问题需要解决么?
- 不用解决,这个问题是可以接受的!毕竟你数据都已经提交了,读出来本身就没有太大问题!
-
在RC级别下,主从复制用什么binlog格式?
- 在该隔离级别下,用的binlog为row格式,是基于行的复制。
-
分布式事务
相关未涉及名词:JTA XA事务
落地实践:
- 字节跳动-支付系统补单
- Seata - 分布式事务框架
分布式的CAP理论
C(Consistency,一致性):在一个分布式的系统中,同一个数据的所有备份,在同一时刻是否有相同的值。也就是,对于同一个数据的读写,是否立刻对于所有副本都能看到一致的结果。一种比较常见的强一致性实现就是,在看到一致的结果之前,写请求不返回,读请求阻塞或者超时。
A(Availability,可用性):在集群中一些节点故障时,集群还可以响应读写请求。
P(Partition-tolerance,分区容忍性):分布式系统具有多个节点,如果节点间网络中断,就会造成分区。
参考链接:
zhuanlan.zhihu.com/p/183753774
note:在第三篇文章里,redis是没有事务的。那么,redis真的没有事务吗? //todo
两阶段提交(2PC)
两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。
二阶段提交是一种强一致性设计。
自己使用过的例子:
-
礼品更新
-
活动-礼品-前台配置,活动和礼品是同个库里,前台配置在另一个库中,前台的配置依赖活动和礼品的ID。
- 先更新礼品和活动,更新成功了再通过feign调用前台配置,如果失败,feign代理类手动抛出异常,通过Transactional注解进行事务的代理回滚。
-
运行过程
- 准备
- 协调者询问参与者事务是否执行成功,参与者发回事务执行结果。
- 提交
- 如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
- 需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
存在的问题
2.1 同步阻塞 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
2.2 单点问题 协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
2.3 数据不一致 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
2.4 太过保守 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
总结
2PC 是一种尽量保证强一致性的分布式事务,因此它是同步阻塞的,而同步阻塞就导致长久的资源锁定问题,总体而言效率低,并且存在单点故障问题,在极端条件下存在数据不一致的风险。2PC 适用于数据库层面的分布式事务场景.
三阶段提交(3PC)
相较于2PC,多了一步:预先确认资源。
补偿事务(TCC)
所谓的TCC即是:
- Try 阶段主要是对业务系统做检测及资源预留。
- Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
- Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
TCC和2PC不同点在于,2PC的回滚是DB层面的(基于DB层面提供的事务功能),TCC的补偿机制是通过服务的补偿代码实现的,二者实现的层级不同。
本地消息表
本地消息表其实就是利用了 各系统本地的事务来实现分布式事务。
本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。
如果调用失败也没事,会有 后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。
这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理。
可以看到本地消息表其实实现的是最终一致性,容忍了数据暂时不一致的情况。
即:
- 用持久化存储,将信息存储和本地事务的执行放在一个事务中,保证本地事务和forward的调用日志是统一的(即:forward日志的前提一定会是,本地事务已经执行成功了)。
- 本地事务执行完毕,往前继续调用其他服务的事务执行。
- 调用失败的情况:不修改forward消息发送表,通过job系统定期进行重试。如果超过一定次数,进行报警通知人工干预。
- 实现的是最终一致性。
消息事务
流程(RocketMQ)
-
向broker发送一条半消息(定义:消费者不可见,可以定义为预发送),发送成功后,再执行本地事务。
-
根据本地事务执行结果(成功/失败),向broker发送消息(commit/rollback),同时根据这个事务的执行情况,向broker提供提供查询事务状态的接口(成功/执行中/失败)。
- 超时情况:如果一段时间内半消息没有收到任何操作请求(zhuanlan.zhihu.com/p/183753774原话),broker会根据2中提供的接口,手动查询成功失败,根据状态进行相关操作。
-
broker将消息commit或rollback。后续的就和正常的消息队列流程一样了。
最大努力通知
最大努力通知其实只是表明了一种柔性事务的思想:我已经尽力我最大的努力想达成事务的最终一致了。
\