数据库事务

一、事务的特性

  ACID:

    1、原子性(Atomicity)

      事务是最小的不可分割单元。只有做完和回滚,没有中间状态。

    2、一致性(Consistency)

      数据库总是从一个一致的状态转换到另一个一致的状态。这个有些容易和隔离性弄混淆,其实二者有内在的因果关系。正是隔离性的级别,造成了一致性的差异,这里的一致性可以简单理解为在某个时间后,同样的读取操作可以得到同样的结果。

      强一致性:读操作可以立即读到提交的更新操作。
      弱一致性:提交的更新操作,不一定立即会被读操作读到,此种情况会存在一个不一致窗口,指的是读操作可以读到最新值的一段时间。
      最终一致性:是弱一致性的特例。事务更新一份数据,最终一致性保证在没有其他事务更新同样的值的话,最终所有的事务都会读到之前事务更新的最新值。如果没有错误发生,不一致窗口的大小依赖于:通信延迟,系统负载等。
      其他一致性变体还有:
      单调一致性:如果一个进程已经读到一个值,那么后续不会读到更早的值。
      会话一致性:保证客户端和服务器交互的会话过程中,读操作可以读到更新操作后的最新值。
 
    3、隔离性(isolation)
      一个事务未提交前对其他事务事不可见的。在事务并发操作时,可能出现的问题有:
  脏读:事务A修改了一个数据,但未提交,事务B读到了事务A未提交的更新结果,如果事务A提交失败,事务B读到的就是脏数据。
  可重复读:在同一个事务中,对于同一份数据读取到的结果不一致。比如,事务B在事务A提交前读到的结果,和提交后读到的结果可能不同。不可重复读出现的原因就是事务并发修改记录,要避免这种情况,最简单的方法就是对要修改的记录加锁,这回导致锁竞争加剧,影响性能。另一种方法是通过MVCC可以在无锁的情况下,避免不可重复读。
  幻读:在同一个事务中,同一个查询多次返回的结果不一致。事务A新增了一条记录,事务B在事务A提交前后各执行了一次查询操作,发现后一次比前一次多了一条记录。幻读是由于并发事务增加记录导致的,这个不能像不可重复读通过记录加锁解决,因为对于新增的记录根本无法加锁。需要将事务串行化,才能避免幻读。
 
  简单来说脏读时读到了未提交事务修改的数据,最终事务被回滚,中间态的数据被读出。可重复读是数据库事并行时嵌套影响,某个事务中前后两次读取,因为另一个事务提交数据更新而不同(修改加锁)。幻读粗看很像不可重复读,二者区别在于,不可重复读是对同一份数据读取的结构前后不一致,幻读是同一个查询前后返回结果不一致,前者说的是本身被数据修改,后者说的除了数据本身被修改,还包含数据条数增加或减少。这里之所以要分成两个级别是因为,不可重复读通过更新时加锁涉及到的数据或类似的思路即可解决,但是幻读中增加数据的情况,在读之前数据压根不存在,无从加锁,解决上必须另想办法(限制事务并行)。
 
  为解决上述问题,数据库有4种不同的隔离级别:  
  Read Uncommitted:最低的隔离级别,什么都不需要做,一个事务可以读到另一个事务未提交的结果。所有的并发事务问题都会发生。
  未提交读,即不做隔离控制。
  Read Committed:只有在事务提交后,其更新结果才会被其他事务看见。可以解决脏读问题。
  提交读,Oracle的默认事务级别,可解决脏读问题,即不存在被读后,事务又回滚的情况。
  Repeated Read:在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。可以解决脏读、不可重复读。
  可重复读,MySQL的默认事务级别,可解决脏读和不可重复读的问题,但无法解决幻读(幻行)问题,幻读数据库通过多版本并发控制(MVCC,Mutiversion Concurrency Control)解决。
  Serialization:事务串行化执行,隔离级别最高,牺牲了系统的并发性。可以解决并发事务的所有问题。
  可串行化,最高的隔离级别,强制事务串行化。
  通常,在工程实践中,为了性能的考虑会对隔离性进行折中。
 
综上:
  

4、持久性(durability)

  事务提交后,事务所做的修改将一直保存在数据库中。
 
二、MySQL关于不可重复读与幻读的解决方案
 
1、加锁:
  如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。所以说不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。
  
2、MVCC:   

 悲观锁和乐观锁

    • 悲观锁

  正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

  在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。

  即上面说的互斥读写锁。

    • 乐观锁

  相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

  这里类似于并发思想中的CAS(Compare and swap),更新时,数据与期待一致(这里是版本号)才更新。

  要说明的是,MVCC的实现没有固定的规范,每个数据库都会有不同的实现方式,这里讨论的是InnoDB的MVCC。

更详细解读:https://blog.csdn.net/whoamiyang/article/details/51901888
    
 
 
参考
  1.https://www.zhihu.com/question/31346392/answer/59815366;
  2.<<高性能MySQL>>第三版,p6-9;
  3.http://www.cnblogs.com/itcomputer/articles/5133254.html;
  4.https://blog.csdn.net/whoamiyang/article/details/51901888;
原文地址:https://www.cnblogs.com/lyInfo/p/9114558.html