MySQL高并发事务问题及解决方案

事务的概念

  1. 事务 能够理解为一个 独立的工做单元, 在这个独立的工做单元中, 有一组操做; 放在事务(独立工做单元)中的多个操做, 要么所有执行成功, 要么所有执行失败。
  2. 难免俗套, 这仍是经过最经典的银行转帐应用来解释一下mysql

    • 假设有两个角色 'Iron Man'(余额500), 'Wolverine'(余额15), 如今 Iron Man 经过该银行应用给 Wolverine 转帐100元, 那么本次转帐操做至少须要三个步骤:sql

      检查`Iron Man`余额`>=100`元
      从`Iron Man`余额中`-100`元
      给`Wolverine`余额`+100`元
    • 注意: 上面的三个步骤的操做必须打包在一个事务中, 从而能够做为一个 独立的工做单元 来执行。在这个 独立工做单元(即事务) 中的这三个操做, 只要有任何一个操做失败, 则事务就总体就是失败的, 那就必须回滚全部的步骤。
    • 假设第二步操做成功, 可是第三步操做失败, 那么整个事务也就应该是失败的, 那就必须将第二步的操做也回滚。(到这里咱们也看到了事务最基本的特性之一: 保证数据的一致性)
  3. 要知道, 在真实的高并发场景下, 事务须要作的事情其实不少不少, 由于高并发会出现不少意想不到的问题, 接下来会分析这些问题。

事务的ACID特性

在分析高并发事务的问题前, 咱们要先知道事务的几个标准特性, 由于一个运行良好的事务处理系统必须具有这些标准特性, 并且这些问题的解决离不开事务的这几个标准特性!!!数据库

  1. Atomicity 原子性
    一个事务必须被视为一个不可分割的最小工做单元, 整个事务中的全部操做要么所有提交成功, 要么所有失败回滚。对于一个事务来讲, 不能只成功执行其中的一部分操做, 这就是事务的原子性。
  2. Consistency 一致性
    虽然可数据表中的数据可能一直在变化, 可是事务的一致性特性会保证 数据库老是从一个一致性的状态 转换到 另外一个一致性的状态;segmentfault

    好比在以前的转帐例子:并发

    转帐前的一致性状态是: 'Iron Man'(余额500), 'Wolverine'(余额15)
    转帐成功后的一致性状态是: 'Iron Man'(余额400), 'Wolverine'(余额115)
    转帐若是失败的话, 一致性的状态应该回滚到转帐前的状态: 'Iron Man'(余额500), 'Wolverine'(余额15)
  3. Isolation 隔离性高并发

    • 一般来讲, 一个事务所作的修改在最终提交之前, 对其余事务是不可见的;
      好比在以前的转帐例子中, 在执行完成第二步, 可是第三步还没开始的时候, 此时有另外一个帐户汇总的程序开始运行, 那么这个程序所拿到的A帐户余额应该是没有被 -100 的余额才对
    • 后面咱们还会详细讨论事务隔离性隔离级别, 到时候就知道这里为何说一般来讲对其余事务是不可见的; (也就是还有特例, 好比最低隔离级别 READ UNCOMMITTED, 对其余事务的可见就形成了脏读问题的出现)
    • 事务有四种隔离级别(从低到高: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE)
  4. Durability 持久性性能

    一旦事务被最终提交, 则在事务这个独立单元中的全部操做所作的修改将会 `永久保存到数据库中`; (这里所说的`永久`应该能够理解为 被事务修改的数据 是真正存放到了表中, 而不是存放在了诸如临时表之类的地方)

高并发事务的问题

在并发量比较大的时候, 很容易出现 多个事务同时进行 的状况。假设有两个事务正在同时进行, 值得注意的是: 它们二者之间是互相不知道对方的存在的, 各自都对自身所处的环境过度乐观, 从而并无对本身所操做的数据作必定的保护处理, 因此最终致使了一些问题的出现;
接下来, 在分析高并发事务的问题时, 你可能已经了解了一些关于锁的概念, 可是在分析这些问题的时候, 先不要带入锁的概念, 本小节只会列出问题, 并直接告诉你各个问题是使用事务隔离性的哪一个隔离级别解决掉的, 锁是解决方案, 若是带入锁的概念, 是没法去分析这些问题的。因此本节不须要带入!
[下一篇文章]()将会分析这些解决方案(各隔离级别)具体是如何解决问题的。spa

脏读

  1. 若是mysql中一个事务A读取了另外一个并行事务B未最终提交的写数据, 那事务A的此次读取就是脏读。(由于事务A读取的是'脏数据', 是'非持久性'的数据)code

    • 之因此说是'非持久性数据', '脏数据', 是由于事务B最终可能会由于内部其余后续操做的失败或者系统后续忽然崩溃等缘由, 致使事务最终总体提交失败, 那么事务A此时读取到的数据在表中其实会被回滚, 那事务A拿到的天然就是脏的数据了。
    • 图示:
      clipboard.png
  2. 事务A在T4阶段读取库存为20, 这个库存其实就属于脏数据, 由于事务B最终会回滚这个数据, 因此若是事务A使用库存20进行后续的操做, 就会引起问题, 由于事务A拿到的数据已经和表中的真实数据不一致了。
  3. 那么这个问题如何解决呢?
    在MySQL中, 其实事务已经用自身特性(隔离性的 -- READ COMMITED或以上隔离级别)解决了这个问题;blog

    **`READ COMMITED`级别保证了, 只要是当前语句执行前已经提交的数据都是可见的**。注意和`REPEATABLE READ`级别的区!!!

不可重复读

  1. 假设如今上面的 脏读问题 已经被彻底解决了, 那就意味着事务中每次读取到的数据都是 持久性 的数据(被别的事务最终 提交/回滚 完成后的数据)。
  2. 可是你还须要知道的是: 解决了脏读问题, 只是能保证你在事务中每次读到的数据都是持久性的数据而已!!!!
  3. 若是在一个事务中屡次读取同一个数据, 正好在两次读取之间, 另一个事务确实已经完成了对该数据的修改并提交, 那问题就来了: 可能会出现屡次读取结果不一致的现象。
    clipboard.png
  4. 那么这个问题如何解决呢?
    在MySQL中, 其实事务已经用自身特性(隔离性的 -- REPEATABLE READ或以上隔离级别)解决了这个问题;
    REPEATABLE READ级别保证了, 只要是当前事务执行前已经提交的数据都是可见的。注意和READ COMMITED级别的区!!!

幻读 (间隙锁)

对于幻读, 可能不少人常常和不可重复读区分不开, 详情能够参考本人写的此篇文章https://segmentfault.com/a/11...

更新丢失

  1. 最后聊一下高并发事务的另外一个问题 -- 丢失更新问题, 该问题和以前几个问题须要区分开, 由于解决方案不是一类!
  2. 第一类丢失更新: A事务撤销时, 把已经提交的B事务的更新数据覆盖了。
    clipboard.png
    不过, 经过后面MVCC相关文章最后的小结你会了解到, 这类更新丢失问题是不会出现的, 由于InnoDB存储引擎的隔离级别都使用了排他锁, 即便是 MVCC也不是纯MVCC, 也用到了排他锁! 这样的话事务A在未完成的时候, 其余事务是没法对事务A涉及到的数据作修改并提交的。
  3. 第二类丢失更新: A事务覆盖B事务已经提交的数据,形成B事务所作操做丢失。
    clipboard.png

    此类更新丢失问题, 没法依靠前三种隔离级别来解决, 只能用最高隔离级别 Serializable 或者手动使用乐观锁, 悲观锁来解决。

  4. 最高隔离级别Serializable在实际应用场景中并不被采用, 对于手动使用乐观锁, 悲观锁的方案, 将会在之后关于锁的文章中一并给出!

参考资料:

  • 《高性能MySQL》
相关文章
相关标签/搜索