数据库事务及其并发控制

Posted by icoding168 on 2020-03-18 17:19:22

分类: MySQL  

数据库事务

概念

数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

数据库事务通常包含了一个序列的对数据库的读/写操作。包含有以下两个目的:

  • 为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
  • 当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。

当事务被提交给了数据库管理系统,则系统需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要回滚,回到事务执行前的状态;同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。

举例

某人要在商店使用电子货币购买100元的东西,当中至少包括两个操作:

  • 该人账户减少100元
  • 商店账户增加100元

支持事务的数据库管理系统就要确保以上两个操作(整个“事务”)都能完成,或一起取消;否则就会出现100元平白消失或出现的情况。

特性

并非任意的对数据库的操作序列都是数据库事务,数据库事务必须拥有以下四个特性,简称 ACID 特性:

  • 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。

并发事务带来的问题

多个事务并发运行,经常会操作相同的数据来完成各自的任务,按照事务对数据的读写操作情况,可以把事务的并发场景分为三种:

  • 读-读:不存在任何问题,也不需要并发控制
  • 读-写:有线程安全问题,可能遇到脏读,幻读,不可重复读等问题
  • 写-写:有线程安全问题,可能会存在丢失修改问题

脏读(Dirty Read)

一个事务访问了某个数据并且对数据进行了修改,但这个修改还没有提交到数据库中,此时另一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的就是脏数据,根据脏数据执行的业务逻辑可能是不正确的。

不可重复读(Unrepeatable Read)

一个事务访问了某个数据,在这个事务还没有结束时,另一个事务也访问该数据并且修改了数据,第二个事务的修改可能会导致第一个事务前后两次读取的数据不一样,这就是不可重复读。

幻读(Phantom Read)

一个事务读取了几行数据后,接着另一个事务插入了一些数据。在随后的查询中,第一个事务发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

丢失修改(Lost Update)

一个事务访问了某个数据并且对数据进行了修改,在第一个事务中修改了这个数据后,第二个事务也修改了这个数据,这样就造成第一个事务的修改结果丢失了。 例如:事务 1 读取某表中的数据 A = 20,事务 2 也读取到 A = 20,事务 1 修改 A = 20 + 3,事务 2 修改 A = 20 - 1,最终结果 A = 19,然而正确结果应该是 A = 22。

不可重复读和幻读的区别

不可重复读的关注点是修改,幻读的关注点是新增和删除。

并发控制相关概念

在计算机科学中,特别是程序设计、操作系统、多处理机和数据库等领域,并发控制(英语:Concurrency Control)是确保及时纠正由并发操作导致的错误的一种机制,悲观并发控制、乐观并发控制是两种常用的并发控制策略。

数据库管理系统中并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和数据的一致性,具体使用的并发控制技术有两阶段锁、基于时间戳排序、基于有效性检查、快照隔离、MVCC 等。

悲观并发控制(Pessimistic concurrency control,PCC)

从一开始,即检查每一项操作是否会违反隔离性和完整性约束,如果可能违反,则阻塞这样的操作。如两阶段封锁,用读锁来阻塞另外一个事务的写锁。两阶段锁技术属于悲观的方法,提前对异常现象进行了预防,基于时间戳排序的并发控制技术也是悲观的。

乐观并发控制 (Optimistic concurrency control,OCC)

从一开始,每一项操作都允许进行,但在事务提交的时刻,进行隔离性和完整性约束的检查,如果有违反则事务被中止。显然,如果并发冲突少的场景,乐观并发控制方法是适合的,基于有效性确认的并发控制方法就是乐观的。

死锁

指当两个以上的运算单元,彼此都在等待对方停止运行以获取系统资源,但是没有任何一方停止运行。

可串行化

多个事务的并发执行是正确的,当且仅当其结果与按某一次序串行地执行它们时的结果相同,我们称这种调度策略为可串行化(Serializable)。

事务隔离

数据库是一个高并发类型的应用,同一时间会有大量的并发访问。如果没有正确地使用并发控制,会极大地降低数据库的并发处理能力。SQL 国际标准定义了多种事务隔离级别来约束数据库事务的并发控制程度,多数的数据库事务都避免高等级的隔离级别来减少系统开销,因为更高的隔离级别会增加死锁发生的几率。在数据库事务的 ACID 四个特性中,隔离性是最常放松的一个。

事务隔离级别

SQL 国际标准定义的数据库事务隔离级别如下:

  • read-uncommitted(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • read-committed(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • repeatable-read(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • serializable(可串行化): 最高的隔离级别,完全服从acid的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
隔离级别脏读(Dirty Read)不可重复读(NonRepeatable Read)幻读(Phantom Read)
未提交读(read uncommitted)可能可能可能
已提交读(read committed)不可能可能可能
可重复读(repeatable read)不可能不可能可能
可串行化(serializable )不可能不可能不可能

两阶段锁

两阶段锁(two-phase locking,2PL)是一种常用于关系式数据库的并发控制技术,为了访问一个数据库对象,事务首先要获得这个对象的锁。对于不同的访问类型(如对对象的读写操作)和锁的类型,如果另外一个事务正持有这个对象的锁,获得锁的过程会被阻塞或者延迟。

两阶段锁主要由两个阶段构成:

  • 加锁阶段:在该阶段进行加锁操作,在对任何数据进行读操作之前要申请并获得共享锁,其它事务可以继续加共享锁,但不能加排它锁。在进行写操作之前要申请并获得排它锁,其它事务不能再获得任何锁。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。
  • 解锁阶段:当事务释放了一个锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。
事务加锁/解锁处理
begin;
insert into test …..加insert对应的锁
update test set…加update对应的锁
delete from test ….加delete对应的锁
commit;事务提交时,同时释放insert、update、delete对应的锁

两阶段锁无法避免死锁,但是可以保证事务的并发控制可串行化。

快照隔离

快照隔(snapshot isolation)是指,写一个值时,不要在这个值上面做修改,而是创建一个新的版本,也就是快照,而读一个值会读取最近提交成功了的那个版本的数据。读-读肯定是没问题的,读-写也没问题,读到的是最后提交的版本。至于写-写,如果在提交的时候检测到有冲突,将这个事务取消掉就行了。所有的操作完全不阻塞,相比加锁等待的方案,性能一下子就上去了。

快照隔离可以解决可重复读这个级别的一些问题,但是还达不到可串行化,主要是存在写偏序问题。可串行化快照隔离(serializable snapshot isolation)解决了写偏序的问题,能够支持串行化。

MVCC

多版本并发控制(Multiversion concurrency control, MCC 或 MVCC),是数据库管理系统常用的一种并发控制技术,也用于程序设计语言实现事务内存。

MVCC 是一种用来解决读-写冲突的无锁并发控制技术,通过对数据做多版本快照管理,在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能,同时还可以解决脏读,幻读,不可重复读等事务隔离问题。对于丢失修改问题,则在 MVCC 的基础上再结合悲观锁或者乐观锁来解决。

MVCC 不是一个可独立使用的事务并发控制技术,而是需要基于其他并发控制技术,如基于时间戳的称为“多版本时间戳排序机制(multiversion timestamp-ordering scheme)”,基于两阶段封锁协议的称为“多版本两阶段封锁协议(multiversion two-phase locking protocol)”。

不仅是 MySQL,包括 Oracle、PostgreSQL 等其他数据库系统也都实现了MVCC,但各自的实现机制不尽相同,因为 MVCC 没有一个统一的实现标准。

本文参考资料

数据库事务 - 维基百科,自由的百科全书

事务隔离 - 维基百科,自由的百科全书

JavaGuide/事务隔离级别(图文详解).md at master · Snailclimb/JavaGuide
https://github.com/Snailclimb/JavaGuide/

多版本并发控制 - 维基百科,自由的百科全书

Concurrency control - Wikipedia
https://en.wikipedia.org/wiki/Concurrency_control

李海翔. “数据库事务处理的艺术:事务管理与并发控制 (数据库技术丛书)。”

Innodb中的事务隔离级别和锁的关系 - 美团技术团队
https://tech.meituan.com/2014/08/20/innodb-lock.html

InnoDB存储引擎MVCC实现原理 | 刘正阳
https://liuzhengyang.github.io/2017/04/18/innodb-mvcc/

正确的理解MySQL的MVCC及实现原理_数据库_xupeng1644的博客-CSDN博客
https://blog.csdn.net/xp178171640/article/details/104812870