天天看点

2021-08-08事务的概念事务的状态事务的特性数据库的隔离级别事务处理

事务

  • 事务的概念
  • 事务的状态
  • 事务的特性
  • 数据库的隔离级别
  • 事务处理

事务的概念

事务是一个抽象的概念,它其实对应着一个或多个数据库操作。

事务就是一组原子性的SQL查询,或者说一个独立的工作单元。如果数据库引擎能够成功地对数据库应用该组查询的全部语句,那么就执行该组查询。如果其中有任何一条语句因为崩溃或其他原因无法执行,那么所有的语句都不会执行。也就是说,事务内的语句,要么全部执行成功,要么全部执行失败。 -------高性能MySQL(第3版)
事务(transaction)指的是一组SQL语句,它们是一个执行单位,且在必要时还可以取消。并非所有的语句每次都能执行成功,也可能它们都没什么影响。事务处理是通过使用提交(commit)和回滚(rollback)功能来实现的。如果某个事务里的所有语句都执行成功,那么你可以把它提交到数据库永久性的记录下来。如果在事务执行过程中发生了错误,则可以通过回滚操作取消该事物。在事务里,所有在出错之前执行的语句都将被还原;而数据库也会恢复到事务开始执行之前的那个状态。 -------MySQL技术内幕(第5版)

事务的状态

根据对数据库操作的不同阶段,把事务划分成以下几个状态:

  • 活动的(active)
事务对应的数据库操作正在执行过程中时,我们就说该事物处在活动的状态。
  • 部分提交的(partially committed)
当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。
  • 失败的(failed)
当事务处在活动的或者部分提交的状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。
  • 中止的(aborted)
如果事务执行了半截而变为失败的状态,比如转账A给账户B转账的事务,当账户A的钱被扣除,但是账户B的钱没有增加时遇到了错误,从而当前事务处在了失败的状态,那么就需要把已经修改的账户A余额调整为未转账之前的金额,话句话说,就是要撤销失败事务对当前数据库造成的影响。我们把这个撤销的过程称之为回滚。当回滚操作执行完毕时,也就是数据库恢复到了执行之前的状态,我们就说该事务处在了中止的状态。
  • 提交的(committed)
当一个处在部分提交的状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了提交的状态。

随着事务对应的数据库操作执行到不同阶段,事务的状态也在不断变化,一个基本的状态转换图如下所示:

2021-08-08事务的概念事务的状态事务的特性数据库的隔离级别事务处理

从图中可以看出,只有当事务处于提交的或者中止的状态时,一个事务的生命周期才算是结束了。对于已经提交的事务来说,该事务对数据库所做的修改将永久生效,对于处于中止状态的事务,该事务对数据库所做的所有修改都会被回滚到没执行该事务之前的状态。

事务的特性

一个运行良好的事务处理系统,必须要具备以下“ACID”四种特性:

  • 原子性(atomicity)
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。例如:转账操作是一个不可分割的操作,也就是说要么压根儿没转,要么转账成功,不能存在中间的状态,也就是转了一半的情况。
  • 一致性(consistency)
数据库总是从一个一致性的状态转换到另外一个一致性的状态。 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。数据库世界是现实世界的一个映射,现实世界中存在约束当然也要在数据库中有所体现。
如何保证数据库中的数据的一致性呢?
  1. 数据库本身能为我们保证一部分一致性需求(就是数据库自身可以保证一部分现实世界的约束永远有效)。例如:主键约束、唯一约束、外键约束、非空约束、默认约束、自定义约束等。
  2. 更多的一致性需求需要靠写业务代码来保证。为建立现实世界和数据库世界的对应关系,理论上应该把现实世界中的所有约束都反应到数据库世界中,但是很不幸,在更改数据库数据时进行一致性检查是一个耗费性能的工作,比方说我们为account表建立了一个触发器,每当插入或者更新记录时都会校验一下balance列的值是不是大于0,这就会影响到插入或更新的速度。现实生活中复杂的一致性需求比比皆是,而由于性能问题把一致性需求交给数据库去解决这是不现实的,所以这个锅就甩给了业务端程序员。比方说我们的account表,我们也可以不建立触发器,只要编写业务的程序员在自己的业务代码里判断一下,当某个操作会将balance列的值更新为小于0的值时,不执行该操作就好了嘛!
  3. 数据库某些操作的原子性和隔离性都是保证一致性的一种手段,在操作执行完成后保证符合所有既定的约束则是一种结果。
  • 隔离性(isolation)
通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。 现实世界中的两次状态转换应该是互不影响的,例如:转账操作,账户A给账户B同时进行两次金额为5元的转账(假定可以在两个ATM机上同时操作),那么最后账户A肯定会少10元,账户B肯定会多10元。现实世界中,两次转账操作是不分顺序、独立执行的,无论先执行哪次转账结果都不会改变。但是,真实的数据库中两次操作可能交替执行,最终账户A只扣了5元,但是账户B却多了10元。
  • 持久性(durability)
一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。当把现实世界的状态转换映射到数据库世界时,持久性意味着该转换对应的数据库操作所修改的数据都应该在磁盘上保留下来,不论之后发生了什么事故,本次转换造成的影响都不应该被丢失掉。

数据库的隔离级别

事务隔离级别是指多个事务之间,不同事务中涉及的读写操作互相影响的隔离。其中多个事务中同时对同一条数据或者表进行写操作(insert、update、delete),必定会导致事务间数据的相互影响,导致不可预知问题发生。在SQL标准中的定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。每种存储引擎实现的隔离级别不尽相同。

  • 未提交读(Read Uncommitted),在这个级别事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。安全性最差,可能发生并发数据问题,性能较好。
  • 提交读(Read Committed),大多数数据库系统的默认隔离级别都是 提交读 (但MySQL不是)。提交读 满足隔离性的简单定义:一个事务开始时,只能“看见”已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读,因为两次执行同样的查询,可能会得到不一样的结果。
  • 可重复读(Repeatable Read),这个级别解决了脏读的问题。该级别保证了在同一个事务中多次读取同样的记录的结果是一致的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读(Phantom Read)的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录。当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。InnoDB和XtraDB存储引擎通过多版本并发控制解决了幻读的问题。可重复读是MySQL的默认事务隔离级别。
  • 可串行化(Serializable)是最高的隔离级别。它通过强制事务串行执行,避免幻读的问题。简单的说,可串行化会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才 考虑采用该级别。
隔离级别 脏读可能性 不可重读可能性 幻读可能性 加锁读
未提交读 Yes Yes Yes No
提交读 No Yes Yes No
可重复读 No No Yes No
可串行化 No No No Yes

事务处理

要想使用事务,就必须选用一种支持事务处理的存储引擎,如 InnoDB。而 MyISAM 和 MEMORY 这样的存储引擎就不行。默认情况下,MySQL的运行模式是自动提交,即每条语句所做的更改立刻提交到数据库,并永久保存下来。事实上,这相当于每条语句都被隐式地当做了一个事务来执行。如果想要显示地执行事务,那么需要禁用自动提交模式,并主动告知MySQL何时提交更改或何时回滚更改。

  • 一种常用的执行事务的办法是:先调用start transaction(或begin)语句,挂起自动提交 模式;接着,再执行构成本次事务的各条语句;最后,用commit语句结束事务,从而让所有修改持久化。
start transaction;
insert into t set name = 'William';
insert into t set name = 'Wallace';
commit ; / rollback;
           
  • 另外一个执行事务的办法是,利用set语句直接操作自动提交模式的状态;
set autocommit = o;  # 禁用自动提交模式
set autocommit = 1; # 重新启用自动提交模式
           
set autocommit = o; 
insert into t set name = 'William';
insert into t set name = 'Wallace';
commit ; / rollback;
set autocommit = 1;