我们知道通过锁机制可以使得对共享资源进行并发访问时,能够提供数据的完整性和一致性。实现事务的隔离性要求,可以使得事务可以并发工作。但是锁提高了并发的提示,也引发了一些问题。主要有以下三种:
脏读:在不同的事务下,当前事务可以读到另外事务未提交的数据,即可以读到脏数据
不可重复读:在一个事务内,两次读到的数据不一样
丢失更新:一个事务的更新操作会被另一个事务的更新操作所覆盖,从而导致数据的不一致
下面分析一下三种问题。
在讨论事务相关问题时,我们一定要谨记大前提,那就是在各种事务隔离级别下。抛开隔离级别谈事务问题,纯属扯淡。
脏读
在不同的事务下,当前事务可以读到另外事务未提交的数据,即可以读到脏数据。何为脏数据?脏数据是指事务对缓冲池中的行记录的修改,并且还没有提交。脏读违反了事务的隔离性要求。
脏读发生在事务隔离级别是READ UNCOMMITED条件下。而目前绝大多数的数据库的事务隔离级别都至少设置成了READ COMMITED。所以脏读在生产环境中不常发生。
不可重复读
在一个事务A内,我们可能会多次读取同一数据。在这个事务没有提交之前,可能有其他事务B对数据进行了操作,并且提交了。当事务A再次读取这份数据时,发现两次读到的数据是不一致的,这种情况即为不可重复读。
不可重复读和脏读的区别:脏读是读到未提交的数据,违反了数据库事务的隔离性,而不可重复读读到的却是已经提交的数据,违反了数据库事务一致性的要求。
一般来说,不可重复读的问题是可以接受的,因为读到的是已经提交的数据,本身不会带来很大问题。因此,很多数据库厂商(如Oracle、Microsoft SQL Server)将其数据库事务的默认隔离级别设置为READ COMMITED,在这种隔离级别下允许不可重复读的现象。
在InnoDB存储引擎中,默认的隔离级别是RR,通过使用next-key lock算法避免了不可重复读的问题。在next-key lock算法下,对于索引的扫描,不仅是锁住扫描到的索引,而且还锁住这些索引覆盖的范围。因此在这个范围内的插入都是不允许的,这样就避免了其他事务在这个范围内插入数据而导致的不可重复读问题。
举例:
事务A | 事务B |
---|---|
begin | begin |
select * from books.stu;//返回6行 | |
insert into book.stu select 7,‘lujie’; | |
commit; | |
select * from books.stu; //返回7行 |