多事务执行时那些你避也避不开的问题!| SQL全面教程十三:事务(7)事务的并发控制和并发事务中'丢失更改'不可重复读'幻读'脏读'

1,168 阅读12分钟

并发控制

事务是并发控制的基本单位。并发控制用于处理多于一个事务的交错执行(interleaved execution)

说明,后续中,会用R表示read读数据操作,W表示write写数据操作。

多事务的执行方式

首先说一下数据库的并发。数据库是一个资源(数据)共享、允许多用户的同时访问的数据管理系统。共享可以通过分时共享、同时访问等技术实现。

多用户数据库系统:允许多个用户同时使用同一个数据库的数据库系统。

常见的大多数场景和大多数数据库系统,都是多用户数据库系统,如银行、电子商务网站、飞机火车票订票等使用的DBMS。

这样,就涉及到,多事务的执行方式:

串行执行

  1. 事务串行执行(Serial):每次只有一个事务运行,其他事务必须等到当前事务结束后才能运行。

浪费且不能充分利用系统资源、执行效率慢,不能实现数据库的共享。

交叉并发方式

  1. 交叉并发方式(Interleaved Concurrency)

主要针对的是单核CPU的情况(多核也有这种方式),事务的并行执行是以事务的轮流交叉并行操作运行的。

也就是多个事务分时分片执行,这种情况下的并行事务并没有真正地并行运行,但能减少CPU的空闲时间、提高系统利用率和处理速度等。

同时并发方式

  1. 同时并发方式(simultaneous concurrency)

多核CPU环境中,每个CPU核心可以运行一个事务,多个核心可以同时运行多个事务,实现多个事务真正的并行运行。 这是最理想的并发方式,但也需要更加复杂的并发控制机制。

除了串行执行的方式,多事务在执行时都要处理好并发控制的问题。

非串行执行的并发事务,产生的问题

  • 多个事务同时存取同一数据的情况。
  • 可能会存取和存储不正确的数据,破坏事务隔离性和数据库的一致性。

如下,是一个非常典型的并发事务破坏数据完整性(一致性)的情况:

因此DBMS必须提供对应的并发控制机制。

并发控制机制是衡量一个数据库管理系统性能的重要标志之一

并发操作带来(可能产生)的数据不一致性问题

数据不一致性的产生,是由于并发操作破坏了事务的隔离性,从而造成事务间相互干扰。

事务的一致性问题,也被称为依赖性问题(dependency problemsconsistency problems)。

(个人理解的逻辑上的)并发事务中数据不一致问题的分类

此处的详细说明为个人理解的内容,想说明下各问题对应的情况。

  1. 丢失修改(Lost Update)——修改-更新冲突。如果是单条记录的修改,更新中的删除和修改,都可以导致丢失修改。如果是多条记录的修改,更新中的插入导致修改不完全、删除和修改导致修改丢失。

  2. 不可重复读(Non-repeatable Read)——读-更新冲突,分为读-修改-读、读-插入-读、读-删除-读三种情况,其中读-插入-读、读-删除-读属于幻影读,细分下属于幻影行(Phantom Row)。甚至第一种情况读-修改-读,也是一种幻读,因为读取的不一样,类似幻影。

各处操作中的读,可以不仅仅是读,还可以更改。

  1. 读“脏”数据(Dirty Read)——修改-读-回滚冲突,中间读取的为假的数据。

  2. "幻读"(Phantom Read)——读-插入-读读-删除-读读-修改-读插入/删除-读-回滚等情况属于幻读。前三种属于不可重复读,其中第四种情况插入/删除-读-回滚对应的是脏读的处理情况,只不过插入的数据被回滚,因为不影响最终的数据,中间的读取就像多出来一条数据一样,但因为多出来数据是中间假的数据,严格上也属于“脏”数据;以及删除的数据被回滚,因为也是不影响最终的数据,中间的读取就像少一条数据一样。也将其归类为幻读。

'丢失更改'不可重复读'幻读'脏读'

感觉上面不一致性分类,逻辑上区分是没有问题,可以看到对同一现象的分类并不是绝对的。重点理解不一致产生的原因。

此处再看一下,通常常说的并发事务中产生的'丢失更改'不可重复读'幻读'脏读'等问题。

下面主要参考自维基百科Isolation (database systems)

1. 丢失修改

一个事务对数据的修改保存被另一个事务的修改顶替,导致前一个事务的修改丢失。丢失更改是在2个或多个事务选择了相同的行(或数据),然后在原始值的基础上更新它,最后的事务更新重写了其他事务的更新结果,导致丢失数据。每个事务都没意识到其他的事务。

如果两个事务一前一后,紧接着顺序执行了对同一行同一数据的更新,都不属于丢失更改。这是正常的逻辑操作和事务执行。只有在两个事务选取了相同的数据并各自在原始值的基础上进行修改,才会导致事务更新结果的覆盖,即丢失。

丢失更新——Lost updates,也会称为buried updates,隐藏更新、埋葬更新、湮灭更新。

2. 不可重复读

同一个事务在前后两次读取同一条记录时,读取的数据不一样,通常是两次读取的间隔被其他事务修改。

A non-repeatable read occurs when, during the course of a transaction, a row is retrieved twice and the values within the row differ between reads.

Non-repeatable Read也被称为Inconsistent analysis(不一致分析),事务读取的数据不一致。

不可重复读和脏读有些类似,但是它读取的是已提交的数据,且每次读的都不同。

3. 幻读

同一个事务在前后两次执行相同的查询时,读取到的数据行不一致的情况(多出行或减少了行),类似出现幻觉一样。

A phantom read occurs when, in the course of a transaction, new rows are added or removed by another transaction to the records being read.

关于幻读,不同地方还是有很大的定位差异,下面是一个简短说明。

在同一个事务中,前后两次同样的查询条件,读取到的数据不一致的情况称为幻读和不可重复读。幻读是针对查询到的数据行比上一次的多出来了;不可重复读是针对数据改变了不同了,比如delete、update导致的数据不一致。

幻读针对的是insert,有的地方也会加上针对delete(查出的数据少了,看到的结果不同)。但是其实另一个事务的update语句,也可能导致第二次查询多出数据行,或减少数据。

因此,幻读有的地方会这样定义:

幻读指的是一个事务在前后两次查询同一个条件的时候,后一次查询看到了前一次查询没有看到的行。 或者后一次比前一次多出了数据行。

==许多地方对幻读的定义,都是仅专指由其他事务“新插入的行”,尤其是在关于MySQL的讨论中==。

此处幻读中的查询,使用的是当前读快照读则不会出现这些情况。

4. 脏读

脏读又名未提交依赖(uncommitted dependency),是指一个事务读取了另一个事务已经修改但是还没有提交的数据。通常另一个事务最终没有提交而是回滚了,也有可能数据还会被修改。

A dirty read (aka uncommitted dependency) occurs when a transaction is allowed to read data from a row that has been modified by another running transaction and not yet committed.

在多事务执行时,最不可接受的问题的就是 脏读。也就是,并发问题的严重级别,从高到底为:脏读 - 丢失更改 - 不可重复读 - 幻读,再往下就是可串行化,它不会产生任何并发问题,但同样,性能也是最差的。

“脏”读(Dirty Read)

读“脏” 数据是指:

事务T1修改某一数据,并将其写回磁盘(通常为写入内存);事务T2读取同一数据后,T1由于某种原因被撤销,这时T1已修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致;T2读到的数据就为“脏” 数据,即不正确的数据。

T2读到的是一个不存在的数据,是数据的一个中间状态。

(T1将C值修改为200;T2读到C为200;T1由于某种原因撤销,其修改作废,C恢复原值100;这时T2读到的C-200,与数据库内容不一致,就是“脏”数据)

丢失修改(Lost Update)

两个事务T1和T2读入同一数据并修改,T2(稍后)的提交结果破坏了T1提交的结果,导致T1的修改被丢失。

上一个图中T2覆盖T1的修改,就是丢失修改问题。是修改-更新导致的。

不可重复读(Non-repeatable Read)和幻读(Phantom Read)

不可重复读是指事务T1读取数据后,事务T2执行更新操作,使T1无法再现前一次读取结果。即无法重新读取某一数据。

读-更新导致的。

不可重复读包含三种情况,后两种情况为幻读:

  1. 读-修改冲突:事务1读取某一数据,事务2对其做了修改,当事务1再次读该数据时,得到与前一次不同的值。

(T1读取B=100进行运算;T2读取同一数据B,对其进行修改后将B=200写回数据库;T1为了对读取值校对重读B,B已为200,与第一次读取值不一致)

  1. 读-删除冲突:事务T1按一定条件从数据库中读取了某些数据记录,事务T2删除了其中部分记录,当T1再次按相同条件读取数据时,发现某些记录神秘地消失了。

  1. 读-插入冲突:事务T1按一定条件从数据库中读取某些数据记录,事务T2插入了一些记录,当T1再次按相同条件读取数据时,发现多了一些记录。

后两种不可重复读有时也称为幻影现象(Phantom Row。好像上次的读取多出来的数据行或缺少的数据行类似幻影一样。个人感觉翻译为幻影行更加贴切一些,其也属于**幻读(Phantom Read)**的一种。

并发事务导致的数据不一致总结

可以看到,上面发生的几种并发事务问题,是在两个事务交错执行中,由于各自可能发生的不同时刻(各自执行时间点不同)及不同处理(各自提交或回滚)导致的

并发控制就是要用正确的方式调度并发操作,使一个用户事务的执行不受其他事务的干扰,从而避免造成数据的不一致性。

并发控制在保证数据一致性的同时,会多少影响到系统的执行效率。因此,实际上可以根据实际需要,如果允许某些不一致性(比如大量数据的统计,一些脏数据的处理不影响最终的统计精度),则可以降低数据的一致性要求,提高系统处理效率、减少系统开销等。

并发问题与隔离级别的关系(不同隔离级别对应的数据不一致问题(脏读、丢失更改、不可重复读、幻读))

不同隔离级别对应可能产生的数据不一致问题,如下面的介绍:

  • 读未提交(Read Uncommitted):最低级别,脏读、丢失更改、不可重复读、幻读 均可能发生。
  • 读已提交(Read Committed):可避免 脏读 的发生。
  • 可重复读(Repeatable Read):可避免 脏读、丢失更改、不可重复读 的发生。
  • 串行化(Serializable):可避免 脏读、丢失更改、不可重复读、幻读 的发生。

下面+/-说明:

  • '+' — possible
  • '-' — not possible

             Read phenomena


Isolation level

Dirty reads Lost updates[inconsistent] Non-repeatable reads Phantoms
Read Uncommitted + + + +
Read Committed - + + +
Repeatable Read - - - +
Serializable - - - -
Snapshot - - - -

关于不同隔离级别可能产生的问题,可以通过几个简单的表数据,然后分两个连接登陆RDBMS,进行隔离级别的设置,事务的执行中按不同的步骤查询、插入、修改交替进行操作和提交,查看对应的现象。此处不再详细演示。

并发控制的主要技术

  • 锁(Locking)
  • 时间戳(Timestamp)
  • 乐观控制法(optimistic concurrency control, OCC)
  • 多版本并发控制(MVCC)

对并发控制的处理,最常见也是使用最广泛的,就是“锁”(或着"封锁")及MVCC,两者分别对应悲观并发和乐观并发下的各自主要实现。

参考