数据库的隔离级别,脏读,不可重复读,幻读

不同的数据库隔离级别会出现不同的问题,如下:

隔离级别

脏读

不可重复读

幻读

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学生姓名是李哈:

数据库的隔离级别,脏读,不可重复读,幻读_第1张图片这个时候我们在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客户端回滚事务:

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

 

B客户端再次查看,名字又成了'李哈':

数据库的隔离级别,脏读,不可重复读,幻读_第2张图片

这样,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客户端再次查看数据,发现这次没有变化:

数据库的隔离级别,脏读,不可重复读,幻读_第3张图片

但是,这样虽然避免了'脏读'的发生,但还会有另外的问题,那就是'不可重复读'.

 

 

 

不可重复读:

在A事务内,因为B事务对数据进行了修改,导致A事务两次读取的数据不一样.

首先,设置数据库的隔离级别为READ COMMITTED(读提交):

mysql> set global transaction isolation level read committed;

我们在A客户端开启事务,并第一次查看student相关数据:

数据库的隔离级别,脏读,不可重复读,幻读_第4张图片

我们在B客户对'李哈'这条数据进行修改:

数据库的隔离级别,脏读,不可重复读,幻读_第5张图片

我们在A客户端进行第二次读取,我们发现,在同一个事务A内,我们对一条数据进行两次读取后得到的结果竟然不一样!似乎这种情况并不严重,这你就错了!

这就好比客户从超市买了两包5块钱的娃娃菜正在结账,恰好自助结算机刚好扫完第一包,你后台居然在修改价格,那客户第二包这个时候价格就会发生变化,你说客户是提把40米长的刀还是50米长的刀....

数据库的隔离级别,脏读,不可重复读,幻读_第6张图片

 

 那么同理,这种情况能避免发生吗?

回答是当然可以的,我们可以设置数据库为REPEATABLE READ(可重复读)隔离级别,这个也是mysql数据库默认的隔离级别:

 

mysql> set global transaction isolation level repeatable read;

修改完后我们再次进行刚才的操作:

A事务开启事务后第一次查询:

数据库的隔离级别,脏读,不可重复读,幻读_第7张图片

B事务对'李某'数据进行修改:

A事务第二次查询,数据并没有因为B事务对数据的更新而受到影响:

数据库的隔离级别,脏读,不可重复读,幻读_第8张图片

这样,不可重复读的现象就可以避免了,但是新问题又来了,那就是'幻读'.

 

 

幻读:

当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事务在开始的时候就会拿到锁,不允许其他事务更新该条数据,直到该事务提交后才会释放锁.(只会锁行,不锁表).

数据库的隔离级别,脏读,不可重复读,幻读_第9张图片

好了,终于写完了.如果大家有不认同的地方,欢迎留言指正!

你可能感兴趣的:(数据库篇,mysql)