注册 登录
  • 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

MySQL InnoDB事务中锁问题(三)

MySQL 彭东稳 5797次浏览 已收录 0个评论

通过读写锁机制可以实现事务的隔离性要求,使得事务可以并发地工作。锁提高了并发,但是却会带来潜在的问题。不过好在因为事务隔离性的要求,锁只会带来以下几种问题,如果可以防止这几种问题的发送,那将不会产生并发异常。

一、丢失修改(lost update

丢失修改简单来说就是一个事务的更新操作会被另一个事务的更新操作所覆盖。从而导致数据的不一致。例如:事务A与事务B从数据库中读入同一数据并修改,事务A修改完提交事务,随后事务B也修改完提交事务,结果就是事务B提交的结果覆盖了事务A提交的结果。这既是丢失更新问题。

举例:比如就拿订票系统来说,现订票系统还有20张票,两个用户同时各自提交了一个事务。

1步:事务A查看系统剩余票数还有20张。那么事务A做更新操作(买一张票,那么系统会把它读到的总票数减1,还剩19张)。此时还没有提交事务。

2步:事务B查看系统剩余票数还有20张。那么事务B也做更新操作(买1张票,那么系统也会把它读到的总票数减1,也剩余19张)。此时还没有提交事务。

3步:事务A提交事务。

4步:事务B提交事务。

这个时候发生了什么情况?两个人一共买了两张票而数据库中的记录只是减掉了1张票,这个就是丢失修改问题。

但是,在当前数据库的任何隔离级别下,都不会导致数据库理论意义上的丢失更新问题。这是因为,即使是READ UNCOMMITTED的事务隔离级别,对于行的DML操作,需要对行或其他粗粒度级别的对象加锁(U锁)。因此上面的事务B并不能对事务A正在操作的数据进行更新操作,其会被阻塞,直到事务A结束。

虽然数据库可以阻止丢失更新问题的产生,但是在生成应用中还有另一个逻辑意义的丢失更新问题,而导致该问题的并不是因为数据库本身的问题。实际上,在所有多用户计算机系统环境下都有可能产生这个问题。简单地来说,出现下面的情况时,就会发生丢失更新:

第一步:事务A查询一行数据,放入本地内存,并显示给一个终端用户UserA

第二步:事务B也查询该行数据,并将取得的数据显示给终端用户UserB

第三步:UserA修改这行记录,更新数据库并提交。

第四步:UserB修改这行记录,更新数据库并提交。

显然,这个过程中用户UserA的修改更新操作“丢失”了。这个结果对于银行系统或其他事务系统来说也是不可接受的。要避免丢失更新的发生,其实需要让这种情况下的事务变成串行操作,而不是并发的操作。即在上述四步操作时的第(1)步对用户读取的记录加上一个排他锁(FOR UPDATE),同样,发生第(2)步情况下的操作时,用户也需要加上一个排他锁(FOR UPDATE)。这样一来,第(2)步就必须等待第(1)、(3)步完成,最后完成第(4)步。

有的人可能会奇怪,在上述的例子中为什么不直接使用UPDATRE语句,而首先要进行SELECT的操作。的确,直接使用UPDATE可以避免丢失更新问题的产生,然而在实际应用中,应用程序可能需要首先检测用户的余额信息,查看是否可以进行转账操作,然后再进行最后的UPDATE操作,因此在SELECTUPDATE操作之间可能还存在一些其他的SQL操作。

二、脏读(dirty read

在理解脏读之前,需要理解脏数据的概念。但是别把脏数据和脏页混淆,脏页指的是在缓冲池中已经被修改的页,但是没有刷新到磁盘中,即数据库实例内存中的页和磁盘中的页的数据是不一致的,当然在刷新到磁盘之前,日志都已经被写入到了redo日志文件中。而所谓脏数据是指事务对缓冲池中行记录的修改,并且还没有被提交(commit)。

对于脏页的读取,是非常正常的。脏也是因为数据库实例内存和磁盘的异步造成的,这并不影响数据的一致性(或者说两者最终会达到一致性,即当脏页都刷回到磁盘)。并且因为脏页的刷新是异步的,不影响数据库的可用性,因此可以带来性能的提高。

但脏数据却截然不同,脏数据是指未提交的数据,如果读到了脏数据,即一个事务可以读到另一个事务中未提交的数据,则显然违反了数据库的隔离性标准。简单来说“脏读”指的是一个事务可以读到另一个事务中未提交的数据。如,事务1修改某一数据并写回缓存池,然后事务2又读取该数据。事务1由于某种原因被撤销,数据恢复原值,从而导致事务2读取的数据为错误数据,也就是脏数据。一般出现“脏读”都是在Read Uncommitted隔离级别下才会发生,也就是事务会读取到另一个事务没有提交的数据。但是MySQL默认是RR隔离级别,而Oracle则在RC隔离级别,所以基本不会出现“脏读”的问题。

三、不可重复读(non-repeatable read

不可重复读是指在一个事务内多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间,由于第二个事务的修改,第一个事务两次连续读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为“不可重复读”。

不可重复读和脏读的区别是:脏读是读到未提交的数据,而“不可重复读”读到的确实是一件提交的数据,但是其违反了ACID中的I,即隔离性。下面通过一个例子来观察不可重复读的情况,在《InnoDB行锁算法介绍》这篇博客中也详细介绍了“不可重复读”问题。所以目前不可重复读只有在RC隔离级别下才会发生。如下实例:

# 事务A

# 事务B

# 事务A

一般来说,不可重复读的问题是可以接受的,因为其读到的是已经提交的数据,本身并不会带来很大的问题。因此,很多数据库厂商将其数据库事务的默认级别设置为“读已提交”,在这种隔离级别下允许不可重复读的现象。而MySQL使用的是“可重复读”隔离级别。

InnoDB存储引擎中,通过使用Next-Key Lock算法来避免不可重复读的问题。在MySQL官方文档中将不可重复读的问题定义为Phantom Problem,即幻象问题。在Next-Key Lock算法下,对于索引的扫描,不仅是锁住扫描到的索引,而且还锁住这些索引覆盖的范围(gap)。因此在这个范围的插入都是不允许的。这样就斌免了另外的事务在这个范围内插入数据导致的不可重复读的问题。因此,InnoDB存储引擎的默认事务隔离级别时REPEATABLE READ,采用Next-Key Lock算法,避免了不可重复度的问题。

四、幻读

幻读,是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

对于“不可重复读”及“幻读”问题,有时候很容易搞混淆。但从定义上来看,“不可重复读”主要针对update操作,对上一次读到的数据再次读取时发生了改变;而“幻读”主要针对insert及delete操作,对上一次读取到的数据条数变多或变少了。

InnoDB存储引擎中,通过使用Next-key Lock锁来避免不可重复读和幻读的问题。在MySQL官方文档中,将不可重复读定义为Phantom Problem,即幻象问题。在Next-key Lock算法下,对于索引的扫描,不仅仅是锁住扫描到的索引,而且还锁住这些索引覆盖的范围。因此对于这个范围内的插入都是不允许的。这样就避免了另外的事务在这个范围内插入数据导致的不可重复读的问题。因此,InnoDB存储引擎的默认事务隔离级别是REPEATABLE READ,采用了Next-key Lock算法,就避免了不可重复度的现象。


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (1)or分享 (0)
关于作者:

您必须 登录 才能发表评论!