1、脏读
脏页只是在缓冲池中已经修改的页但是没有刷新到磁盘中,即数据库实例内存中的页和磁盘中的页事不一致的,当然在刷新到磁盘之前,日志都已经被写入到了重做日志文件中,而所谓的脏数据是指事务对缓冲池中行记录的修改,但并没有被提交
对于脏页的读取,是非常正常的。脏页是因为数据库实例内存和磁盘异步造成的,这并不影响数据的一致性(或者说两者最终会达到一致性,当脏页刷新回到磁盘中)。并且因为脏页的刷新时异步的,不影响数据库的可用性,带来了性能的提高
脏数据就截然不同,脏数据是未提交的数据,如果读到了脏数据,即一个事务可以读到另一个事务中未提交的数据,则显然违反了数据库的隔离性
脏读是值在不同的事务下,当前事务可以读到另外事务未提交的数据,简单来说就是可以读到脏数据。
表t中的事务隔离级别有默认的RR改成READ UNCOMMITED ,因此在会话A中,在事务未提交的前提下,会话B中的两次SELECT操作取得了不同的结果,并且2这条记录是在会话A中并未提交的数据,即产生了脏读,违反了事务隔离性
脏读隔离看似毫无用处,但在一些比较特殊的情况下还是可以将事务隔离级别设置成READ UNCOMMITTED。例如replication环境中的slave节点,并且该slave的查询不需要特别精确的返回值
2、不可重复读
不可重复读是指在一个事务内多次读取同一数据集合,在这个事务还没有结束时,另外一个事务也访问该同一数据集合,并做了一些DML操作,因此,在第一个事务中两次数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能不一样。这样就发生了在一个事务内两次读到的数据不一样的情况,称为不可重复读
不可重复读和脏读的区别:脏读是读到未提交的数据,而不可重复读读到的是已经提交的数据,但是其违反了数据库一致性的要求
在会话A中开始一个事务,第一次读到的1,另一个会话B中开始另一个事务,插入一条2的记录,在没有提交之前,对会话A中的事务进行再次读取是,读到的记录还是1,没有发生脏读的现象,但在会话B中事务提交后,在会话A中的事务进行读取时,这是读到的是1和2这两条记录,这个例子的前提是,会话A和会话B的事务隔离级别是RC
一般来说,不可重复读是可以接收的,因为其读到的是已经提交的数据,本身不会带来很大的问题,因此很多数据库产商将其事务隔离级别默认设置成RC,在这种隔离级别下允许不可重复读的现象
在InnoDB存储引擎中,通过Next-Key Lock算法来避免不可重复读的问题,在MySQL官方文档将不可重复读的问题定义为Phantom Problem,即幻像问题。在Next-Key Lock算法下,对于索引的扫描,不仅是锁住扫描的索引,还是锁住这些索引覆盖的范围gap,因此这个范围内的插入都是不允许的,这样就避免了另外的事务在这个范围内插入数据导致不可重复读的问题。因此InnoDB存储引擎的默认事务隔离级别是RR,采用Next-Key Lock算法,避免不可重复读的现象
3、丢失更新
丢失更新是另一个锁导致的问题,简单来说其就是一个事务的更新操作会被另一个事务的更新操作锁覆盖,从而导致数据的不一致,例如
事务T1将行记录r更新为v1,但是事务T1并未提交
与此同时,事务T2将行记录r更新为v2,事务T2未提交
事务T1提交
事务T2提交
但是在当前数据库的任何隔离级别下,都不会导致数据库理论上的丢失更新问题。这是因为,即使是READ UNCOMMITTED的事务隔离级别,对于行的DML操作,需要对行或者其他粗粒度级别的对象加锁,因此在上述步骤B中,事务T2并不能对行记录r进行更新操作,其余被阻塞,直到事务T1提交
虽然数据库能阻止丢失更新问题的产生,但是在生产应用中还有另一个逻辑意义的丢失更新问题呢,而导致该问题并不是因为数据库本身的问题。实际上,在所有多用户计算机系统环境下都有可能产生这个问题。简单来说,出现下面的情况,就会发生丢失更新
A. 事务T1查询一行数据,放入本地内存,并显示给一个终端用户User1
B. 事务T2也查询该行数据,并将取得的数据显示给终端用户User2
C. User1修改这行的记录,更新数据库并提交
D. User2修改这行的记录,更新数据库并提交
显然,这个过程中用户User1的修改更新操作会丢失了,而这可能会导致一个恐怖的结果,设想银行发生丢失更新的现象。例如一个用户账号有10 000人民币,他用两个网上银行的客户端分别进行转账操作。第一次转账9000,因为网络和数据的关系,这需要等待,但这是用户操作另一个网上银行客户端,转账1元,如果这两笔操作都成功,用户的余额应该是9999,第一次转的9000并没有得到更新,但是在转账的另一个账户却受到了这9000,这导致的结果是钱变多,而账不平。也许读者会说,不对,我的网银是USB Key的,不会发生这种情况,是的,通过Usb key登录也许可以解决这个问题,但是最重要的是在数据库层解决这个问题,避免任何可能发生更新的情况
要避免丢失更新发生,需要将事务在这种情况下操作变成串行化,而不是并行操作,即在上述步骤的1中,对用户读取的记录加一个排他X锁,同样,在步骤2的操作过程中,用户同样需要加一个排他X锁,通过这种方式,步骤2就必须等待1和步骤3的完成,最后完成步骤4