Transaction-事务

事务

  数据库事务,是指作为单个逻辑工作单元的一系列操作,要么完全执行,要么完全不执行。事务中有的操作没有成功完成,整个事务中的所有操作都需要被回滚。


image.png | center | 429x418

ACID

  数据库事务拥有以下四个特性,习惯上称为:ACID。在数据库领域,ACID模型是最古老并且最重要的概念之一。

  • 原子性(Atomicity):原子在当时被认为是最小的物质,也就意味着不可在分。事务被视为是不可分割的最小单元,事务中的所有操作要么全部提交成功,要么全部失败回滚。

  • 一致性(Consistency)事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。

  • 隔离性(Isolation):主要描述多个事务之间的关系,多个事务并发执行时,一个事务的执行不应该影响其他事务的执行。也就是说一个事务所做的修改在commit之前,对其他事务是不可见的。

  • 持久性(Durability):已经被提交的事务对数据的修改应该永久保存在数据库中,即使系统崩溃,事务执行结果也不能丢失。

  事务的ACID特性概念定义简单,但是有的特性并不是很好理解,因为这些特性不是平级关系,比如一致性。一致性C是事务最终的目的,而AID都是手段。
  一致性状态实际上依赖于应用层,也就是开发者自己去定义的。举个栗子,大家最喜欢用的银行转账:

A向B转账100元,那么对应的操作应该有两步:1,A账户-100元;2,B账户+100元。那么我们这个事务中的两步操作是为了满足事务前后的一致性状态:总金额一致,但是这个一致性状态本身和数据库的约束没有关系,是由我们的业务决定的。


image.png | center | 625x317

  1. 事务的最终目的是要满足一致性,这样的执行结果才是正确的(符合我们业务要求)
  2. 如果事务串行执行,只要事务满足原子性,其执行结果就可以满足一致性
  3. 如果事务并行执行,也就是并发的情况下,既要满足原子性又要满足隔离性,才能最终满足一致性
  4. 持久性是指要永久保存执行结果,即使数据库崩溃,可以理解为执行结果要保存到磁盘中

并发一致性

  上文提到事务串行执行,只要满足原子性就可以满足一致性,但是如果在并发情况下,还需要满足隔离性才能满足一致性,而隔离性恰恰是很慢保证的,因此并发情况下会产生很多一致性问题。

  事务并发执行,在写数据时可能会出现“丢失修改”的问题:

  • 丢失修改:T~1~和T~2~两个事务同时对一个变量进行修改,T~1~先修改、T~2~后修改,T~2 ~会覆盖T~1~的修改,导致T~1~修改丢失。


image.png | center | 447x566

  ANSI/ISO standard SQL 92 标准针对并发情况下事务读取数据时可能出现的问题,描述了以下三种现象:

  • 脏读(Dirty Reads):发生在一个事务读取另一个事务尚未提交的数据时,如T~1~ 修改一个数据,T~2~ 随后读取这个数据。如果 T~1~ 回滚,那么 T~2~ 读取的数据是脏数据。


image.png | center | 495x572

  • 不可重复读(Nonrepeatable Reads):发生在一个事务重复读取同一个变量,但每次获取的数据不相同时。如T~2~ 读取一个数据,T~1~ 对该数据做了修改。如果 T~2~ 再次读取这个数据,此时读取的结果和第一次读取的结果不同。


image.png | center | 515x586

  • 幻读(Phantoms Reads):是不可重复读的一种特殊情况。如T~1~查询一些符合特定条件的数据,第一次查询结束后,T~2~插入了符合T1查询条件的新数据。这时如果T~1~再进行相同的查询,会发现一些新添加的数据,像出现幻影一样,因此称之为幻读。

并发控制

  上文中提到了事务并发不一致性问题的四种现象,其主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。如何进行并发控制是数据库领域中非常重要的问题之一,现在已经有了很多成熟的解决方案,下面主要介绍三种并发控制机制。

  悲观并发控制是最常见的并发控制机制(也就是采用锁的方式),乐观并发控制也叫乐观锁,但是实际上并不是一种正式存在的锁,而是一种并发控制思想。多版本并发控制(MVCC)可以与前两者中任意一种机制结合使用,来提高数据读性能。

悲观(Pessimistic)

  并发控制最简单、应用最广的方式就是采用锁,当事务需要对一个资源进行操作时需要先获取该资源的锁,保证对其资源的独占性。悲观并发控制中,事务对资源被修改保持悲观态度,无论最终数据是否被修改,都先锁定来解决资源竞争问题。

粒度

  加锁可以解决资源并发读写问题,锁的粒度越小,系统的并发程度越大。但是加锁也会带来一些副作用,加锁本身需要消耗资源,锁的各种操作(获取锁、释放锁等)都会增加系统开销,锁的粒度越小系统开销就越大。因此锁开销和并发程度两者不可兼得,需要在两者之间做一个权衡,考虑锁的粒度问题。


image.png | center | 336x269

读写锁

  事务对数据的操作无非是读写两种,对应的锁也分为两种:共享锁、独占锁,这两种锁均属于行锁。

  • 共享锁(Shared):简写为S锁,一个事务获得共享锁之后,只可以进行读操作,因此也称读锁。
  • 独占锁(Exclusive):简写为X锁,一个事务获得独占锁之后,可以进行读、写操作,因此也称为写锁。
兼容性 共享锁 独占锁
共享锁 YES NO
独占锁 NO NO
  • 一个事务如果对数据资源加了S锁,其他事务可以对其加S锁,但不能加X锁。
  • 一个事务如果对数据资源加了X锁,其他事务不可以加任何锁。

意向锁(Intention Locks)

  意向锁可以更容易的支持多粒度封锁,对应的锁也可以分为两种:意向共享锁、独占共享锁,均属于表锁。

  • 意向共享锁:表明一个事务想要在表中的某一行设置共享锁
  • 意向独占锁:表明一个事务想要在表中的某一行设置独占锁

  如果在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。因此在原来X/S锁的基础上引入了IX(Intention Shared)、IS(Intention Exclusive)。

  意向锁的规则:

  • 一个事务在获取表中某行的共享锁之前,必须先获取一个表的意向共享锁(IS)或者级别更高的锁
  • 一个事务在获取表中某行的独占锁之前,必须先获取一个表的意向独占锁(IX)

  兼容性关系:

兼容性 独占锁 意向独占锁 共享锁 意向共享锁
独占锁 NO NO NO NO
意向独占锁 NO YES NO YES
共享锁 NO NO YES YES
意向共享锁 NO YES YES YES

封锁协议

  • 一级:事务 T 要修改数据 A 时必须加 X 锁,直到事务结束才释放锁。可以解决丢失修改问题,因为不能同时有两个事务对同一个数据进行修改,那么事务的修改就不会被覆盖。

  • 二级:事务T读取数据A时必须加S锁,读取完马上释放S锁。可以解决脏读问题,因为根据一级协议,当某个事务修改数据A时,其他事务无法读取数据A,也就不会出现脏读问题。

  • 三级:事务T读取数据时必须加S锁,直至事务结束后才释放锁。可以解决不可重复读问题,因为事务T执行结束前,其他事务不能添加X锁,也就无法修改数据。

乐观(Optimistic)

  乐观并发控制实际上是基于验证的协议,在乐观并发控制中,事务对资源被修改保持乐观态度,先假定不会发生冲突,提交的时候再进行验证。
  在多数应用的事务中,读操作占大多数,并发时因为写操作造成的冲突可能非常小,这时候添加大量的锁反而增加了系统的开销,影响程序性能。乐观并发控制就可以很好的解决这个问题。

  乐观并发控制通常会将事务的执行分为三个阶段:


循环结构流程图.png | center | 496x130

  • 读取:数据库会执行事务中所有的读写操作,并将所有写后的值存入临时变量中,并不会真正修改数据库中的内容。
  • 校验:同步校验所有事务,如果事务所读取的数据在读取后又被其他事务修改过,则产生冲突,事务中断回滚。
  • 写入:如果事务通过校验,其所有存入临时变量的值全部写入数据库。

  乐观并发控制要正常执行,还需要知道每个事务不同阶段的时间戳,这个三个时间戳可以保证任意冲突的事务不会同时写入数据库,一旦由一个事务完成了验证阶段就会立即写入,其他读取了相同数据的事务会回滚执行。

多版本(Multiversion)

  多版本并发控制(MVCC)并不是一个与乐观、悲观并发控制对立的机制,它能够与两者很好的结合以增加事务的并发量。在MVCC中,每一个写操作都会创建一个新版本的数据,也就是说写操作不会产生冲突,读操作会从有限多个版本的数据中挑选一个最合适的结果返回,管理和快速挑选数据的版本成为了MVCC需要解决的主要问题。

  不同的数据库具体采用的MVCC实现不同,如MySQL和PostgreSQL就分别采用了基于悲观和乐观的多版本并发控制方式。

隔离级别

  数据库管理系统为了使并发控制相对简单,提供了事务的几种隔离级别,用户可以通过设置相应的隔离级别来保证事务的隔离性。

  ANSI/ISO standard SQL 92 标准描述了四种不同的隔离级别(isolation levels):

  • 未提交读(Read Uncommitted):事务中的修改,即使没有提交,对其他事务也是可见的。
  • 提交读(Read Committed):一个事务只能读取已经提交事务所做的修改,也就是说,一个事务所做的修改在提交之前对其他事务是不可见的。
  • 可重复读(Repeatable Read):保证同一事务中多次读取同一数据结果是一样的。
  • 串行化(Serializable):强制事务串行执行,不会出现并发一致性问题。
隔离级别 脏读 不可重复读 幻影读
未提交读 YES YES YES
提交读 NO YES YES
可重复读 NO NO YES
可串行化 NO NO NO

参考资料

  1. ANSI isolation levels
  2. A beginner’s guide to ACID and database transactions
  3. 浅谈数据库并发控制 - 锁和 MVCC
  4. 如何理解数据库事务中的一致性的概念?

猜你喜欢

转载自blog.csdn.net/u013201439/article/details/80869554