1. 脏读
所谓的脏读就是指一个事务读取了另一个事务未提取的数据。
试想一下:a账户要给b账户100元购买商品,如果a账户开启一个事务,执行下面的update语句做了如下转账的工作:
update account set money=money-100 where name='a'; update account set money=money+100 where name='b';
如果a账户先不提交事务,通知b账户来查询,由于b的隔离级别比较低,此时就会读取a事务中未提交的数据,发现a确实给自己转了100元,然后给a发货,等b发货成功之后,a再将事务回滚,此时b就会受到损伤,这就是脏读造成的。
为了演示上面的情况,这里我们开启了两个命令行窗口(相当于开启两个线程),分别模拟a账户和b账户,如下:
(1)设置b账户中事务的隔离级别
大家都知道MySQL的默认隔离级别是Repeatable Read(可重复读),该级别是可以避免脏读的,因此需要将b账户中事务的隔离级别设置为Read Uncommitted(读未提交),具体语句如下:
set session transaction isolation level read uncommitted;
如下:
上述语句之中,session表示当前会话,transaction就表示事务,isolation表示隔离,level表示级别,read uncommitted表示当前的隔离级别,该语句执行成功之后,使用select语句查询事务的隔离级别,结果如下:
select @@tx_isolation;
如下:
从上述结果可以看出,b账户的事务隔离级别以及修改为Read Uncommitted,接下来就是演示脏读的情况
(2)演示脏读
b账户:为了证明出现了脏读的情况,首先在b账户中开户一个事务,并在该事务中查询当前账户的余额信息,查询结果如下:
start transaction; select * from account;
如下:
a账户:在a账户中开启一个事务,并在当前窗口中执行转账功能,具体语句如下:
start transaction; update account set money=money-100 where name='a'; update account set money=money+100 where name='b';
如下:
需要注意的是:此时不要提交事务,如果提交事务就无法演示出现脏读的情况。
b账户:a账户执行完转账语句后,b账户查询当前账户,如下:
从上面的查询结果来看,a账户已经成功给b账户转账了100元,这是由于b账户的事务隔离级别比较低,因此才读取了a账户还没有提交的数据内容,出现了脏读的情况,这时候,b误以为a账户以及转账成功,便会给a发货,当b发货之后a如果不提交事务将事务回滚,b就会受到损失。
上面演示完毕了,需要将a账户中的事务回滚,b账户中的事务提交。
(3)设置b账户的事务隔离级别
为了防止脏读发生,可以将b账户中的事务隔离级别设置为Read Committed(读提交),该级别会避免脏读,具体语句如下:
set session transaction isolation level read committed;
上述的语句执行成功之后,b账户的隔离级别已经设置成Read Committed
(4)验证是否出现脏读
b账户:为了说明没有出现脏读的情况,首先要在b账户中开启一个事务,并在该事务中查询各账户的余额情况,查询结果如下:
a账户:在a账户中重新开启一个事务,实现了转账功能,如下:
start transaction; update account set money=money-100 where name='a'; update account set money=money+100 where name='b';
b账户:当a 账户转账成功之后,可以在b账户中再次查询各账户的余额信息,查询结果如下:
通过上面的对比两次查询结果可以发现,b账户在同一个事务中的查询结果是一致的,并没有查询到a账户中未提交的内容,因此可以说明Read Committed 隔离级别可以避免脏读,最后分别将a账户和b账户中的事务回滚(方便以后演示)。