如果所有的操作都是依次进行的,或者说mysql的server是单线程的,就不会有并发问题,也就不需要事务和锁了。然而事实上,是多客户端,多线程的,所有必须要考虑并发问题,数据安全问题和同步问题。
脏读是在(读未提交)模式下,读到其他事务尚未提交的数据,当其他事务发生回滚时,读取到的数据就不准确了。
此处为什么是在读未提交的模式下?这是因为只有这个模式下才能读到其他事务还没有提交的数据,其他模式下不存在这个问题。
事务1 | 事务2 |
begin | |
select age from t1 where id = 1; return 26; | begin; |
update t1 set age = 27 where id = 1 | |
select age from t1 where id = 1; return 27; | |
rollback; |
这里事务1读到了事务2尚未提交的修改,但是事务2发生了回滚,这个时候事务1读取到的值就是脏数据,称之为脏读。
在同一个事务内,对同一条记录,前后两次读取到的值不一样,称之为不可重复读。
不可重复读在(读未提交)(读已提交)两个模式下都存在。
事务1 | 事务2 |
begin; | |
select age from t1 where age between 10 and 30; return 26; | begin; |
update t1 set age = 27 where id = 1; commit; | |
select age from t1 where id = 1; return 27; | |
commit; |
乍一看好像跟脏读差不多?其实区别在于红色的部分,那就是事务2是否提交。脏读是指这个age数据尚未提交就被读取到了,而本例中是已经提交的修改,并非脏读。而不可重复读指的是事务1中前后两次读取到的值不一样。
幻读是不可重复读的一种特殊情况,不可重复读特别注重数据记录本身的不可复现,而幻读指数据的条数不可稳定复现。
事务1 | 事务2 |
begin; | |
select age from t1 where age between 26 and 30; return 27; | begin; |
insert into t1 (id, age) values(2, 28); commit; | |
select age from t1 where age between 26 and 30; return [27,28]; | |
commit; |
在事务1的两次查询中,事务2插入了一条数据,并提交事务。而事务1第二次读取时,存在两条符合条件的数据,前后不相等。
我们发现,脏读、不可重复读、幻读,三者均存在于“一个事务写,另外一个事务读” 的场景。
丢失更新分为两类,一类叫做回滚覆盖,一类叫做提交覆盖。
事务1首先读取到初始值1000,而期间事务2完成了 读取+更新(1100),然后事务1继续执行扣减,预期事务1应该在事务2提交的1100的基础上扣减,现在变成了基于1000扣减,相当于事务2的提交被覆盖掉了,这就是提交覆盖。
事务1首先读取到初始值1000,而期间事务2完成了 读取+更新(1100),然后事务1发生回滚,预期事务2提交的1100会是最终的结果,现在回滚成了1000,相当于事务2的提交被回滚了,这就是回滚覆盖。
提交覆盖是因为在两个事务同时读写数据时,一个事务执行完毕,另外一个事务基于旧数据进行操作,导致已经提交的事务的修改被抹掉了。
read uncommitted: 所有事务都可以看到其他未提交事务的执行结果。
read committed: 一个事务只能看见已提交事务所做的改变。
repeatable read: 确保在同一事务内相同的查询语句的执行结果一致,读到的是事务开启的那一瞬间的快照,并且是稳定的快照。
serializable: 它通过强制事务排序,使之不可能相互冲突。
隔离级别/操作类型 | 读 | 写 |
读未提交 | 不加锁 | 加行共享锁,事务结束时释放 |
读已提交 | 加行共享锁,读完释放 | 加行互斥锁,事务结束时释放 |
可重复读 | 加行共享锁,事务结束时释放 | 加行互斥锁,事务结束时释放 |
串行化 | 加表级共享锁,事务结束时释放 | 加表级互斥锁,事务结束时释放 |
串行化会把所有的数据处理串行化,避免数据并发的问题。
事务1:查询所有数据
事务2:条件查询数据
事务1:修改数据
这条更新会被阻塞,因为两边的查询都会隐式加锁,排斥数据更新操作。
可重复读采用gap-lock,或者next-key lock保证事务内多次读取到的数据是稳定的,与开启事务那一刻的数据状态保持一致,并不会因为其他事务的操作而发生变化。
事务1:查询指定范围的数据
事务2:更新此查询范围内的某条数据并提交
事务1:再次查询,发生数据并没有发生变化
读已提交模式下,每次读取到的数据是最新的快照。不可避免的出现不可重复读和幻读的问题。没有间隙锁,无法阻拦前后位置插入新的数据。
事务1:读取到指定的记录
事务2:更新这条数据并提交
事务1:再次查询,发现是更新后的数据
读未提交时,不涉及到锁,因此读到的都是最新的数据,但是由于其他事务可能还没有提交就被读到了,会出现脏读。
事务1:查询指定的数据
事务2:修改这个数据,但是先不提交
事务1:读取到的数据是事务2修改了的数据