不同的数据库隔离级别会出现不同的问题,如下:
隔离级别 |
脏读 |
不可重复读 |
幻读 |
READ UNCOMMITTED(读未提交) |
√ |
√ |
√ |
READ COMMITTED(读提交) |
× |
√ |
√ |
REPEATABLE READ(可重复读) |
× |
× |
√ |
SERIALIZABLE(可串行化) |
× |
× |
× |
接下来我们使用实际场景来解释脏读,不可重复读,幻读.
A事务未提交的数据被B事务读取,随后A进行了回滚操作,B事务拿到数据就成了脏数据,这就是'脏读'.
好似人事给员工发激励奖,小明看到了自己名字,心里偷偷乐着,,,,人事后来突然发现写错了,不应该有小明,于是删掉了小明,小明看到的数据就是脏数据.
在READ UNCOMMITTED(读未提交)这个隔离级别会出现这种情况.我们以实例来演示:
设置A客户端为读未提交
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
在B客户端查询student表中的数据,看到sid为2学生姓名是李哈:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
然后修改sid为2的这条数据,将原名字改为'貂蝉',但并不commit(提交):
mysql> update student set sname='貂蝉' where sid=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
这时候在B客户端再次查看数据,发现原来的李四变成了貂蝉:
随后我们在A客户端回滚事务:
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
B客户端再次查看,名字又成了'李哈':
这样,B读取的数据就是垃圾数据,没用的数据,我们称此现象为脏读.
那么如何避免脏读现象的发生?我们可以设置数据库隔离级别为READ COMMITTED(读提交)隔离级别,
mysql> set global transaction isolation level read committed;
我们重复上述操作:
这个时候我们在A客户端先开启事务:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
然后修改sid为2的这条数据,将原名字改为'貂蝉',但并不commit(提交):
mysql> update student set sname='貂蝉' where sid=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
这时候在B客户端再次查看数据,发现这次没有变化:
但是,这样虽然避免了'脏读'的发生,但还会有另外的问题,那就是'不可重复读'.
在A事务内,因为B事务对数据进行了修改,导致A事务两次读取的数据不一样.
首先,设置数据库的隔离级别为READ COMMITTED(读提交):
mysql> set global transaction isolation level read committed;
我们在A客户端开启事务,并第一次查看student相关数据:
我们在B客户对'李哈'这条数据进行修改:
我们在A客户端进行第二次读取,我们发现,在同一个事务A内,我们对一条数据进行两次读取后得到的结果竟然不一样!似乎这种情况并不严重,这你就错了!
这就好比客户从超市买了两包5块钱的娃娃菜正在结账,恰好自助结算机刚好扫完第一包,你后台居然在修改价格,那客户第二包这个时候价格就会发生变化,你说客户是提把40米长的刀还是50米长的刀....
那么同理,这种情况能避免发生吗?
回答是当然可以的,我们可以设置数据库为REPEATABLE READ(可重复读)隔离级别,这个也是mysql数据库默认的隔离级别:
mysql> set global transaction isolation level repeatable read;
修改完后我们再次进行刚才的操作:
A事务开启事务后第一次查询:
B事务对'李某'数据进行修改:
A事务第二次查询,数据并没有因为B事务对数据的更新而受到影响:
这样,不可重复读的现象就可以避免了,但是新问题又来了,那就是'幻读'.
当A事务想对某表添加某条不存在的数据时,首先查询这条数据不存在,随后打算进行添加,但在这个过程中,B事务却对表添加了这条不存在的数据,这个时候A事务再次添加就会出错,A很诧异,如同见鬼一般,明明刚才查询是没有这条数据的,为什么现在插不进去啊!!!
我们看下具体情况,开启事务,A事务查询是否存在sid=8的数据,返回结果是不存在:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from student where sid=8;
Empty set (0.00 sec)
这时候我们通过B客户端对该表添加sid=8的数据:
A此时正要添加这条数据,却提示sid=8的数据已经存在:
mysql> insert into student value (8,'你打我啊',1,'紫禁城','难以辨别');
ERROR 1062 (23000): Duplicate entry '8' for key 'PRIMARY'
那么这个时候A已经发生了'幻读'.
那么,'幻读'这个问题能不能解决呢?so easy,将数据库的隔离级别设置为SERIALIZABLE(可串行化)即可,这是MySQL的最高的隔离级别,它对事务进行强制性的排序,使这些事务不会相互冲突,从而解决幻读问题。但是,这样,容易出现超时和锁竞争现象。
首先,设置mysql的隔离级别为
mysql> set global transaction isolation level serializable;
重复上述操作:
开启事务,A事务查询是否存在sid=9的数据,返回结果是不存在:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from student where sid=9;
Empty set (0.00 sec)
这时候我们通过B客户端对该表添加sid=9的数据,发现添加失败,这是锁等待超时,是当前事务在等待其它事务释放锁资源造成的。
采用这个级别的隔离等级,A事务在开始的时候就会拿到锁,不允许其他事务更新该条数据,直到该事务提交后才会释放锁.(只会锁行,不锁表).
好了,终于写完了.如果大家有不认同的地方,欢迎留言指正!