1、什么是事务?
做一件事情分为几个步骤,这几个步骤要么都成功要么都失败!
2、事务的特性?
a) 原子性 b) 隔离性 c) 一致性 d) 持久性
3、不考虑事务的隔离性,会产生哪些读问题? a)不可重复读: 一个事务读取到了另一个事务提交的数据 b)脏读: 在一个事务处理过程中读取到了另一个事务未提交的数据 c) 虚读:T1事务把一条数据的id从1修改为2,并添加了一条id为1的数据,T2事务去读取自己之前添加的id为1的数据,但实际上读取到的是T1修改后的数据;
4、SQL的隔离级别有四种 a) Serializable(串行化,mysql事务的最高级别); b) Repeatable read(可重复读,mysql默认隔离级别); c) Read Commit(读取已提交数据,避免脏读;Oracle的默认隔离级别); d) read uncommit(读取未提交数据)
5、 分布式事务初探
a)需求,用户注册成功后需要给添加 1000 积分! b)分析:涉及连个不同库里的两张表,又分属于两个业务系统
A 库--用户表:tb_user
B 库--积分表:tb_score c) 用户系统服务 UserService addUser(User user);
public class UserService {
@Autowired
private UserRepository userRepository;
public User addUser(User user) {
return userRepository.sava(user);
}
}
d) 积分系统服务 ScoreService addScore(int userId , int socre)
public class ScoreService {
@Autowired
private ScoreRepository scoreRepository;
public int addScore(int userId , int socre) {
return scoreRepository.addScore(userId,socre);
}
}
6、 思考如下伪代码实现的业务对还是不对?为什么?
注册系统中:
Start Transaction;
addUser(User user) ; anyException rollback;
addScore(int userId, int socre); anyException rollback;
commit;
如果对用户,积分表的 SQL 操作全部成功,则全部提交,如果任何一个出现问题,则全部回 滚,以保证数据的一致性。 互联 网的业务特点,数据量较大,并发量较大,经常使用拆库的方式提升系统的性能。如果 进行了拆库,用户表,积分表可能分布 在不同的数据库上,甚至不同的数据库实例上,此时 就不能用事务来保证数据的一致性了。这种情况下如何保证数据的一致 性?
7、方案一:补偿事务
补偿事务是一种在业务端实施业务逆向操作事务,来保证业务数据一致性的方式。
添加用户的事务:
public class UserService {
@Autowired
private UserRepository userRepository;
public int addUser(User user) {
start transaction;
//调 dao 添加用户 异常 rollback return 0;
commit;
return 1;
}
}
添加用户的补偿事务
public class UserService {
@Autowired
private UserRepository userRepository;
int Compensate_addUser(User user){
//调 dao 删除新添加用户;
}
}
添加积分的事务:
public class ScoreService {
@Autowired
private ScoreRepository scoreRepository;
public int addScore(int userId ,int Score){
start transaction;
//调 dao 给指定用户添加积分 异常 rollback return 0;
commit;
return 1;
}
}
添加积分的补偿事务
public class ScoreService {
@Autowired
private ScoreRepository scoreRepository;
int Compensate_addScore(int userId,int score){
//调 dao 删除给定id 的积分
}
}
8、要保证添加用户和增加积分一致性过程
int register(User user){
//执行第一个事务
Int flag =addUser( user);
If(flag=1){
//第一个添加用户的事务执行成功,执行第二个事务加积分!
flag=addScore(userid, score);
if(flag=1){
//第二个事务成功,则成功!
return 1;
}else{
//第二个事务执行失败,执行第一个事务的补偿事务
compensate_addUser(User user)
}
}
}
该方案的不足是: (1)不同的业务要写不同的补偿事务,不具备通用性 (2)没有考虑补偿事务的失败 (3)如果业务流程很复杂,if/else 会嵌套非常多层
9、方案二:两阶段提交
单库操作是通过一个大事务来保证数据的一致性,如果分库操作的话就会变成多个小事务 ,如上例
Start Transaction;
addUser(User user) ; anyException rollback;
addScore(int userId, int socre); anyException rollback;
commit;
1)一个事务,分成执行与提交两个阶段,执行的时间其实是很长的,而 commit 的执行其实 是很快的,于是整个执行过程的时间轴 :第一个事务执行 200ms, 提交 1ms; 第二个事务执行 300ms, 提交 1ms; 那在什么时候系统出现问题,会出现不一致呢? 回答:第一个事务成功提交之后,最后一个事务成功提交之前,如果出现问题 (例如服务器重启,数据库异常等),都可能导致数据不一致。
2)如果改变事务执行与提交的时序,变成事务先执行,最后一起提交,情况会变成什么样呢:第一个事务执行 200ms; 第二个事务执行 300ms; 第一个事务提交 1ms; 第二个事务提交 1ms;
3)那在什么时候系统出现问题,会出现不一致呢? 问题的答案与之前相同:第一个事务成功提交之后,最后一个事务成功提交之 前,如果出现问题(例如服务器重启,数据库异常等),都可能导致数据不一 致。
10、这个变化的意义是什么呢? 1方案一总执行时间是 502ms,最后 301ms 内出现异常都可能导致不一致; 方案二总执行时间也是 502ms,但最后 1ms 内出现异常才会导致不一致; 虽然没有彻底解决数据的一致性问题,但不一致出现的概率大大降低了! 事务提交后置降低了数据不一致的出现概率。 11、会带来什么副作用呢? 事务提交时会释放数据库的连接,第一种方案,第一个库事务提交,数 据库连接就释放了,后置事务提交的方案,所有库的连接,要等到所有事务执 行完才释放。这就意味着,数据库连接占用的时间增长了,系统整体的吞吐量 降低了。