MySQL并发事务带来的问题

 目录

前言

一、写-写情况

二、写-读,读-写情况


前言

事务在并发是可能引发一致性问题的各种现象。并发事务访问相同的数据的情况有三种:

读-读:两个事务同事进行读取。并发事务相继读取相同的记录。读取操作本身不会对记录有任何影响,不会引起什么问题,所以这是允许这种情况发生。

写-写:两个事务一起在写

写-读,读-写,当一个事务在读,一个事务在写的情况。


一、写-写情况

        在写- 写情况下会发生脏写的现象,任何一种隔离级别都不允许这种现象的发生。所以在多个未提交事务相继对一条记录进行改动时,需要让它们排队执行。这个排队的过程其实是通过为该记录加锁来实现的。这个“锁”本质上是一个内存中的结构,在事务执行之前本来是没有锁的,也就一开始是没有锁结构与记录进行关联的。

        当一个事务想对这条记录进行改动时,首先会看看内存中有没有与这条记录关联的锁结构;如果没有,就会在内存中生成一个锁结构与之关联。比如,事务 要对这条记录进行改动,就需要生成一个锁结构与之关联。

        其实锁结构中有很多信息,不过为了方便理解,我们现在只把两个比较重要的属性拿了出来

  • trx信息:表示这个锁结构是与哪个事务关联的。
  • is waiting:表示当前事务是否在等待。

        在事务改动这条记录前,就生成了一个锁结构与该记录关联。因为之没有别的事务为这条记录加锁,所以 is waiting 属性就是 false。我们把这个场景称为获取锁功,或者加锁成功,然后就可以继续执行操作了。

        在事务提交之前,另一个事务也想对该记录进行改动,那么先去看看锁结构与这条记录关联。在发现有一个锁结构与之关联后,也生成了一个锁结构与这条记录美联。不过锁结构的is_waiting属性值为 true,表示需要等待。我们把这个场景称为获取锁失败,成者加锁失败,或者没有成功地获取到锁。

        事务T1提交之后,就会把它生成的锁结构释放掉,然后检测一下还有没有与该记录关联的锁结构。结果发现了事务T2还在等待获取锁,所以把事务T2对应的锁结构的is_waiting属性设置为false,然后把该事务对应的线程唤醒,让T2继续执行。此时事务 T2 就算获取到锁了。

我们总结一下后续内容中可能会用到的几种说法,以免混淆。。

  • 获取锁成功,或者加锁成功:在内存中生成了对应的锁结构,而且锁结构的is waiting属性为 false,也就是事务可以继续执行操作。当然并不是所有的加锁操作都需要生成对应的锁结构,有时候会有一种“加隐式锁”的说法。隐式锁并不会生成实际的锁结构,但是仍然可以起到保护记录的作用。我们把为记录添加隐式锁的情况也认为是获取锁成功。
  • 获取锁失败,或者加锁失败,或者没有获取到锁:在内存中生成了对应的锁结构,不过锁结构的is waiting属性为true,也就是事务需要等待,不可以继续执行操作。
  • 不加锁:不需要在内存中生成对应的锁结构,可以直接执行操作。不包括为记录加隐式锁的情况。

二、写-读,读-写情况

在读- 写或写-读情况下会出现脏读、不可重复读、幻读的现象.
SOL92标准规定,不同隔离级别有如下特点:

  • 在READ UNCOMMITTED 隔离级别下,脏读、不可重复读、幻读都可能发生。
  • 在 READCOMMITTED 隔离级别下,不可重复读、幻读可能发生,脏读不可能发生。
  • 在REPEATABLE READ 隔离级别下,幻读可能发生,脏读和不可重复读不可能发生。
  • 在SERIALIZABLE 隔离级别下,上述现象都不可能发生。

        不过,各个数据库厂商对 SQL 标准的支持可能不一样。MySQL 与 SQL标准不同的一点就是,MySQL在 REPEATABLE READ 隔离级别下很大程度地避免了幻读现象。

        怎么避免脏读、不可重复读、幻读这些现象呢? 其实有两种可选的解决方案。

  • 方案1:读操作使用多版本并发控制 (MVCC),写操作进行加锁。就是通过生成一个 ReadView,然后通过 ReadView找到符合条件的记录版本(历史版本是由 undo 日志构建的)。其实就像是在生成 ReadView 的那个时刻,时间静止了(就像用相机拍了一个快照),查询语句只能读到在生成 ReadView 之前已提交事务所做的更改,在生成 ReadView 之前未提交的事务或者之后才开启的事务所做的更改则是看不到的。写操作肯定针对的是最新版本的记录,读记录的历史版本和改动记录的最新版本这两者并不冲突,也就是采用MVCC 时,读- 写操作并不冲突。
  • 方案2:读、写操作都采用加锁的方式。如果我们的一些业务场景不允许读取记录的旧版本,而是每次都必须去读取记录的最新版本。比如在银行存款的事务中,我们需要先把账户的余额读出来,然后将其加上本次存款的数额,最后再写到数据库中。在将账户余额读取出来后,就不想让别的事务再访问该余额,直到本次存款事务执行完成后,其他事务才可以访问账户的余额。这样在读取记录的时候也就需要对其进行加锁操作,这也就意味着读操作和写操作也得像写- 写操作那样排队执行。

    

    很明显,如果采用MVCC方式,读- 写操作彼此并不冲突,性能更高; 如果采用加锁方式,读-写操作彼此需要排队执行,从而影响性能。一般情况下,我们当然愿意采用 MVCO来解决读-写操作并发执行的问题,但是在某些特殊的业务场景中,要求必须采用加锁的方式执行,那也是没有办法的事。
一致性读

        事务利用MVCC进行的读取操作称为一致性读(Consistent Read),或者一致性无锁读(有的资料也称之为快照读)。所有普通的SELECT 语(plain SELECT)在READ COMMITTEDREPEATABLEREAD隔离级别下都算是一致性读,比如:

  • SELECT * FROM t;
  • SELECT * FROM t1 INNER JOIN t2 ON t1.co11 = t2.col2

        一致性读并不会对表中的任何记录进行加锁操作,其他事务可以自由地对表中的记录进行
改动。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值