其实操作了这么久mysql一直也没有把mysql中事务跟锁的关系弄得特别清楚。然后搜到美团这篇文章,顺便结合一下自己遇到的问题总结一下。
首先事务有四种隔离级别 如下表所示:
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
下面我们一种一种来理一下。
先来分别介绍一下脏读,不可重复读,和幻读。
1. 脏读:脏读是指一个事务读取到了另外一个未提交事务的数据。什么意思呢,就是事务b操作一条数据比如往一个房间里面放入一个人的记录,但是还没有提交。这个时候事务a读取该房间人数的时候,读取到了b还没有提交的事务的数据,这个就是脏读。
2. 不可重复读:不可重复读是指在同一事务中,两次读取同一数据,得到内容不同。什么意思呢,同样是a,b两个事务,a事务先查询了一次房间里其中一个人穿什么颜色衣服,得到的是绿色衣服。这个时候b事务跑过来更新了这个人的衣服颜色,将其设置为蓝色,然后提交了。a事务继续执行到下面又查了一次这个人穿什么衣服,发现她穿的是蓝色衣服 而不是之前查询到的绿色衣服。这个就叫不可重复读。
3. 幻读:幻读特别容易和不可重复读弄混,因为他们实在有点相似。但是幻读偏向的是insert插入数据,向数据表里面增加新数据引起的问题。而不可重复读是更新和删除数据的时候会发生的问题。例如事务a首先查询了一下房间里面有多少个人发现是4个人,然后事务b这个时候往房间里面增加了一个人的数据,然后提交了。事务a重新在当前事务里查询了一下房间里面有多少人发现这次的结果是5个人,这便是幻读。
然后再来说下这四种数据库事务隔离级别:
1. 未提交读(Read uncommitted 以下简称Ru): Ru是完全不加锁的一种事务隔离级别。在这种级别下事务在未提交的时候的修改数据可能被其他事务读取到。就是脏读可能发生的事务级别。一般不会使用这种事务隔离级别。
2. 已提交读(Read committed 以下简称Rc): Rc是可以避免脏读的事务隔离级别。在Rc模式下,没有提交的数据不会被其他事务查询到。在Rc模式下,我们在事务a中使用update更新一条数据,这个时候数据被加更新锁,可读不可写。如果正好事务b这个时候也想更新这条数据,那么事务b将会被锁住。直到事务a提交事务之后,事务b才可以修改这个数据。但是如果在更新锁发生的时候事务b只是想读取该数据,那么数据可以被读取到。但是如果你之前读取过该值,现在这样会出现不可重复读的情况。
3. 可重复读(Repeatable read 以下简称Rr):Rr是可以避免不可重复读和脏读的隔离级别。默认情况下,使用InnoDB引擎mysql处于该事务隔离级别下面。在Rr事务级别下面,事务a读取到了一条记录,事务b修改并提交之后,事务a再次读取该记录不会受到事务b修改的影响。
我们来看个实验:
# 关闭掉自动提交功能方便我们测试SET SESSION autocommit=offmysql> show variables like 'autocommit';+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | OFF |+---------------+-------+1 row in set (0.00 sec) # 设置当前session的事务隔离级别为Rr SET SESSION transaction isolation level repeatable read; Query OK, 0 rows affected (0.00 sec)
事务A | 事务B | 事务C | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
begin; | begin; | begin; | |||||||||
select id,class_name,teacher_id from class_teacher where teacher_id=1;
| |||||||||||
update class_teacher set class_name='初三三班' where id=1; commit;
| |||||||||||
insert into class_teacher values (null,'初三三班',1); commit; | |||||||||||
select id,class_name,teacher_id from class_teacher where teacher_id=1;
没有读到事务B修改的数据,和第一次sql读取的一样,是可重复读的。 没有读到事务C新添加的数据。 | |||||||||||
commit; |
这里直接借用了reference里面文章的图片了,大家可以清晰的看到。测试结果是如果你是在Rr级别操作。事务a不会受到事务b事务c提交数据的影响,事务开始的时候查出来是什么数据,结束的时候就是什么数据。
也就是说我们在Rr级别上就无需再担心自己查询出来的数据被别人update掉。只要事务开始我就认为这个无法被改变了。但是这里你可能会想到有个问题,比如只有库存问题,假设库存只有一个了,事务b在事务a之前减掉了这个库存,那么库存就只有一个了。但是这个时候a查询出来不会知道没有库存了,他会始终看到还有一个库存因为事务开始的时候就有,如果代码层面写得不好,就可能会减成负数。但是rr能保证不会出现这种情况,如果提交的时候发现数据已经变动,commit会失败。
另外经过测试mysql InnoDB在Rr模式下,竟然也会屏蔽幻读的情况。也就是说mysql InnoDB在Rr模式下解决了幻读问题。
回过头,我查询了一下公司使用的mysql,都被改到了Rc模式。并没有使用默认的Rr模式,可能也是想从代码层面使用乐观锁来解决冲突问题,并不想从数据库层面来屏蔽数据修改带来的冲突问题。
之后应该会再补一篇详细的各种锁的详解。感觉对锁那一部分的理解也非常重要。
以上。
Reference:
http://tech.meituan.com/innodb-lock.html Innodb中的事务隔离级别和锁的关系(这篇文写得真是好)
http://www.cnblogs.com/zhaoyl/p/4121010.html mysql事务和锁InnoDB