一、事务的四个隔离级别
1.1 事务并发导致的几类问题
问题 | 描述 |
---|---|
脏读 | 一个事务去更新或者查询了另外一个还没提交的事务更新过的数据;例如: 事务A读取一条记录的值,然后基于这个值做业务逻辑,在事务A提交之前,事务B回滚了该记录,导致事务A读到的这条记录是一个脏数据 |
不可重复读 | 同一个事务里边,2次读取统一记录,但是数据不一样。因为另外一个事务在对此记录进行update |
幻读 | 同一事务里,同样的select语句,执行了两次,返回记录条数不一样。因为另一个事务在进行insert/delete |
丢失更新 | 两个事务同时修改同一记录,事务A的修改被事务B覆盖了。例如:x=5,A和B同时把它读出来,减一,再写回去,得到x=4,实际x的正确值应该是x=3 |
1.2 InnoDB的事务隔离级别
名称 | 解决问题 |
---|---|
RU(Read Uncommited) | 四个问题 都没有解决 |
RC(Read Commited) | 只解决了脏读 |
RR(Repeatable Read) | 几觉了脏读、不可重复读、幻读;默认级别 |
Serialization | 串行化。完全上面四个问题 |
在RR级别下,如何解决最后一个问题。
二、锁
2.1 悲观锁和乐观锁
假设有张用户余额表T :
user_id | banlance |
---|---|
1 | 30 |
2 | 80 |
有2个事务,一个充钱,一个扣钱
事务A
start transaction
int a = select banlance from T where user_id =1
b = b + 50
update T set balance = b where user_id =1
commit
事务B
start transaction
int b = select banlance from T where user_id =1
b = b -50
update T set balance = b where user_id =1
commit
事务A和事务B并行执行,执行结果可能是30(结果正确),也可能是80(事务A给事务B覆盖了),也可能是-20(事务B把事务A的结果覆盖了)。
解决当前问题,有下面几种方法:
方法1:利用单条语句的原子性
事务A
update T set balance = b + 50 where user_id =1
事务B
update T set balance = b - 50 where user_id =1
方法2:悲观锁
事务A
start transaction
int a = select banlance from T where user_id =1 for update
b = b + 50
update T set balance = b where user_id =1
commit
事务B
start transaction
int b = select banlance from T where user_id =1 for update
b = b -50
update T set balance = b where user_id =1
commit
问题:一个事务拿到锁的时候,其他访问该记录的事务都会阻塞,在高并发情况下会造成用户端大量请求阻塞。
方法3:乐观锁(数据库的CAS)
user_id | banlance | version |
---|---|---|
1 | 30 | |
2 | 80 |
事务A
while(!result) {
start transaction
int a, v1 = select banlance,version from T where user_id =1 ;
b = b + 50
result = update T set balance = b, version=version+1 where user_id =1 and version
commit
}
事务B
while(!result) {
start transaction
int a, v1 = select banlance,version from T where user_id =1 ;
b = b -50
result = update T set balance = b, version=version+1 where user_id =1 and version
commit
}
方法4:分布式锁
start_transaction
select xxx from T1
select xxx from T2
根据T1,T2查询结果进行逻辑计算
update T3
commit
2.2 死锁检测
1、2个事务为例
2、多个事务发生死锁的情况
死锁的检测就是发现这种图中是否是有环存在的。检测到死锁后,数据库可以强制让其中某个事务回滚,释放掉锁,把环断开,死锁就解除了
场景1
事务A和事务B,顺序刚好反过来,可能发生死锁
事务A | 事务B |
---|---|
delete from T1 where id=1 | |
update T2 set xxx where id = 5 | |
delete from T1 where id=5 | |
delete from T2 where id = 1 |
场景2
同一张表,在RR隔离级别下,insert操作会增加Gap锁,可能会导致2个事务死锁。
事务A | 事务B |
---|---|
delete from T1 where id =1 | |
update T1 set xxx where id =5 | |
insert into T1 values(…) | |
insert into T1 values(…) |