@linwbai
2018-08-01T14:28:09.000000Z
字数 3246
阅读 1003
yonyou
分布式事务就是指事务的资源分别位于不同的分布式系统的不同节点之上的事务;
例如:信用卡还款,包含两个操作
恢复信用卡额度
例子中两个操作如果处于不同的数据库,或者根本就是两个微服务中的操作,想要保证数据的一致性,靠本地事务是解决不了的,这时候就需要分布式事务了。
事务中的所有操作,要么都成功,要么都失败,不可能部分操作成功部分操作失败。事务的操作或者完全应用到数据库或者完全不影响数据库。
拿信用卡还款来说,原子性就是银行账户扣钱和信用卡额度恢复要么都成功要么都失败。
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿信用卡还款来说,一致性就是还款成功前和还款后银行卡余额加上信用额度是相等的。
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍。
在事务成功以后,该事务对数据库所做的更改应当持久的保存在数据库中;
总结来说, 一致性是最基本的属性,而原子性、隔离性、持久性是为了保证一致性而存在的
事务的隔离级别有什么用呢?我们先看一下不考虑事务的隔离性会出现那些问题。
时间 | 事务A | 事务B |
---|---|---|
1 | 开始事务 | |
2 | 开始事务 | |
3 | 查询额度为100元 | |
4 | 消费10元 | |
5 | 查询额度为90元 | |
6 | 消费异常回滚额度为100元 |
时间轴为5的地方事务B就出现了脏读
时间 | 事务A | 事务B |
---|---|---|
1 | 开始事务 | |
2 | 开始事务 | |
3 | 查询额度为100元 | |
4 | 查询额度为100元 | |
5 | 消费10元 | |
6 | 提交事务 | |
7 | 查询额度为90元 |
事务B中两次查询结果并不一样。不可重复读和脏读的区别是不可重复读是读取了另一个事务提交的数据。
时间 | 事务A | 事务B |
---|---|---|
1 | 开始事务 | |
2 | 开始事务 | |
3 | 把状态为1数据的更新为状态为2 | |
4 | 插入一条状态为1的数据 | |
5 | 提交事务 | |
6 | 查询状态为1的数据发现还有一条 |
MySQL数据库为我们提供的四种隔离级别:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted | 允许 | 允许 | 允许 |
Read committed | 不允许 | 允许 | 允许 |
Repeatable read | 不允许 | 不允许 | 允许 |
Serializable | 不允许 | 不允许 | 不允许 |
TCC是由支付宝架构师提供的一种柔性解决分布式事务解决方案,主要包括三个步骤
- Try: 完成业务检查,预留必须的业务资源
- Confirm: 真正的执行业务逻辑,不做任何业务检查,只使用Try阶段预留的业务资源。所以,只要Try执行成功,Confirm必须能成功。Confirm必须满足幂等性,保证一次分布式事务只成功一次。
- Cancel: 释放Try阶段预留的业务资源。Cancel也需要满足幂等性
TCC自编码的特性决定TCC资源管理器可以跨DB、跨应用实现资源管理,将对不同的DB访问、不同的业务操作通过编码方式转 换一个原子操作,解决了复杂业务场景下的事务问题;
在整个流程,我们主要需要关注的是cancel失败和confirm失败引起的数据不一致现象。
服务需要提供Try、Confirm、Cancel三个接口。
FMT是阿里GTS(全局事务服务)中的一个可以实现自动补偿事务的模型。
FMT 框架要求从业务服务只需要正常执行SQL操作就可以了,框架会把业务的本地事务操作作为第一阶段。在第一阶段,框架会拦截用户SQL,解析SQL语义,然后把业务SQL涉及数据执行前后的状态保存下来,即数据快照,这个就相当于在逻辑上完成了数据库内的undo和redo操作。在第二阶段,如果这个事务要提交,那么框架直接把快照数据删除就可以了,因为第一阶段的正常操作已经执行完成。如果该事务要回滚,那么会先校验脏写,根据第一阶段保存的执行后的快照,检查在本事务执行过程中,数据有没有被其他操作修改,如果没有,则把数据执行前的快照拿出来,完成回滚操作。
使用GTX也有很大的限制的,比如下图
综上所述,我认为绝大部分场景下事务自动补偿得不偿失,想要性能好必须要做好脏读、幻读的准备,想要读已提交必须要舍弃性能。使用多写几个方法的TCC就可以避免上述的大多数问题,何乐而不为呢!
我的思考: 我们是不是可以在程序端保证原子性和隔离性。
时间 | 事务A | 事务B | 事务C |
---|---|---|---|
1 | 开始事务 | ||
2 | 开始事务 | ||
3 | 开始事务 | ||
4 | 查询余额为100元 | 查询余额100元(db) | |
5 | 保存原快照 | ||
6 | 还信用卡10元 | ||
7 | 保存新快照 | 查询余额100元(快照) | |
8 | 提交A事务 | ||
9 | 查询额度为100元 | 查询余额100元(快照) | |
10 | 保存原快照 | 查询额度100元(db) | |
11 | 被还款10元 | ||
12 | 保存新快照 | 查询额度100元(快照) | |
13 | 提交B事务 | ||
14 | 提交总事务(删除快照) | 提交总事务(删除快照) | |
15 | 查询额度110元(db) | ||
16 | 查询余额90元(db) |
这样就保证了真正意义上的读已提交