什么是事务?
发生错误,回滚操作。
要么全部正确执行,要么全不执行。
事务四大特性
ACID 原子性Atomicity,一致性Consistency,隔离性Isolation,持久性Durability
- 原子性:事务里是一个不可分割整体,要么一起执行,要么一起不执行。
- 一致性:事务执行前后,数据库一致性没有被破坏。提交事务没有中间状态。要么提交前,要么提交后。
- 隔离性:事务之间互不干扰。
- 持久性:提交了就永久改变了,不会不生效。
事务隔离级别
读未提交 read-uncommitted
- A还没提交,B就读了。若A rollback,B读到错误数据(脏读)。
不可重复读/读提交 read-committed
- 提交了才能读
- A读,B修改并提交,A再读变了(不可重复读)
可重复读 repeatable-read
- 读时不可修改(行锁)A读,B不准改,A再读完成,B改
- A读(范围),B插入并提交(不修改A锁的),A再读,个数变了(幻读)
串行化/可 serializable
- 读时不可增删改(表锁)
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | ✓ | ✓ | ✓ |
不可重复读(read-committed) | ❌ | ✓ | ✓ |
可重复读(repeatable-read) | ❌ | ❌ | ✓ |
串行化(serializable) | ❌ | ❌ | ❌ |
级别越高,执行效率就越低。
- MySQL默认
可重复读
。 - Oracle默认
读提交
,支持read committed 和 serializable。另外还支持read only(只读,不可修改) 和 read write(默认)
事务传播级别
7种:
传播级别 | 描述 | 上下文存在事务 | 上下文不存在事务 |
---|---|---|---|
REQUIRED | 默认 | 加入事务中 | 新建事务 |
SUPPORTS | 支持 | 加入事务中 | 非事务方式执行 |
MANDATORY | 强制上下文有事务 | 加入事务 | 抛出异常 |
REQUIRES_NEW | 新事务 | 新建事务,挂起上下文事务 | 新建事务 |
NOT_SUPPORTED | 不支持 | 挂起上下文事务,执行当前逻辑 | 新建事务 |
NEVER | 不可有上下文事务 | 抛出runtime异常,强制停止 | 非事务方式执行 |
NESTED | 嵌套执行 | 子回滚,父不影响;父回滚,子回滚;子先提交,父后提交 | 新建事务 |
一致性解决方案
两阶段提交(2PC),三阶段提交(3PC),补偿事务(TCC),消息中间件(MQ异步确保)& 最大努力通知(定期校对)
两阶段提交(2PC)
Prepare、Commit 两个阶段
准备阶段
1)协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。
2)参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。(注意:若成功这里其实每个参与者已经执行了事务操作)
3)各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返
回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。
提交阶段
-
当协调者节点从所有参与者节点获得的相应消息都为”同意”时:
1)协调者节点向所有参与者节点发出”正式提交(commit)”的请求。
2)参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
3)参与者节点向协调者节点发送”完成”消息。
4)协调者节点受到所有参与者节点反馈的”完成”消息后,完成事务。 -
如果任一参与者节点在第一阶段返回的响应消息为”中止”,或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:
1)协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。
2)参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
3)参与者节点向协调者节点发送”回滚完成”消息。
4)协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。
2PC缺点:
- 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
- 单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
- 数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
- 二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
三阶段提交(3PC)
CanCommit、PreCommit、DoCommit三个阶段
- 引入超时机制。同时在协调者和参与者中都引入超时机制。
- 在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
CanCommit阶段
协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。
-
事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
-
响应反馈 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No
PreCommit阶段
- 假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。
- 发送预提交请求 协调者向参与者发送PreCommit请求,并进入Prepared阶段。
- 事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
- 响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
- 假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。
- 发送中断请求 协调者向所有参与者发送abort请求。
- 中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。
相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
补偿事务(TCC)
基于业务层面的事务定义。
锁粒度完全由业务自己控制。
本质是一种补偿的思路。
把事务运行过程分成 Try、Confirm / Cancel 两个阶段
Try :尝试执行业务
- 完成所有业务检查( 一致性 )
- 预留必须业务资源( 准隔离性 )
Confirm / Cancel 阶段:
- Confirm :
真正执行业务
不做任务业务检查
Confirm 操作满足幂等性 - Cancel :
释放 Try 阶段预留的业务资源
Cancel 操作满足幂等性
Confirm 与 Cancel 互斥
本地消息表
将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。
本地消息表记录状态,定时扫描,重新发送失败的消息
消息中间件(MQ异步确保) 最大努力通知(定期校对)
RocketMQ
-
业务方法内要向消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。
-
系统A除了实现正常的业务流程外,还需提供一个事务询问的接口,供消息中间件调用。当消息中间件收到一条事务型消息后便开始计时,如果到了超时时间也没收到系统A发来的Commit或Rollback指令的话,就会主动调用系统A提供的事务询问接口询问该系统目前的状态。该接口会返回三种结果:
提交:将该消息投递给系统B。
回滚:直接将条消息丢弃。
处理中:继续等待。
网友评论