一、基本概念
假设用户A要从他的账户里面给B转账1000元,那么就需要两步来实现,首先从A的账号减去1000元,再给B账号加1000元。这两个步骤中,任何一步都不能少或者出错,这两步要么都得到成功操作完成,要么什么都不做,中途出错也要回滚到转账开始之前的状态。这样的一个过程就是我们经常说的事务。根据这个例子,下来我们就给事务定义如下:
事务(transaction)是为了保证数据的一致性和完整性,由一系列数据操作或指令组成的一个不可分割的逻辑单元,这些操作或指令要么全都得到执行,要么全都不执行。如果事务成功执行,则该事务中对所有数据所做的更改均会成为数据库中持久性更改;如果事务执行过程中遇到错误,则所有执行的操作和更改均应得到取消,回滚到事务执行前的状态。从定义看,事务应该满足四个属性,即ACID,也就是原子性、一致性、隔离性、持久性。
-
原子性(Atomicity,或称不可分割性):事务中的所有操作,要么全部都成功执行,要么全部都没有执行完成。这种不可中断的一系列操作就是原子性。转账过程中,A账号减去1000元和B账号加1000元,必须都得到执行,或者都不执行,否则都不符合逻辑和实际情况。
-
一致性(Consistency):事务开始之前和事务完成以后,所有相关的数据状态都会保持一致,不会使数据库的完整性得到破坏。原子性会确保数据保持一致性,转账过程产生中断,第一步执行了,第二步没有执行,这个时候,只是A账号少了1000元,而B账号没有加1000元,这个时候,数据就不一致了。
-
隔离性(Isolation,又称独立性):数据库中多个并发事务同时执行时,一个事务的执行不能受到其他事务的干扰,也就是说,一个事务内的操作和使用的数据对其他事务是隔离的,不会受到其他并发的事务影响。例如:事务查看数据所处的状态,要么是另一个并发事务的修改之前的状态,要么就是另一个事务修改之后的状态,不会是中间状态的数据。
-
持久性(Durability):事务执行完后,对数据的修改就是永久的,即便系统出现故障,不会产生回滚,也不会影响到执行结果。
二、事务的运行模式
通常,事务以3种模式运行,他们分别是:自动提交事务、显式事务和隐式事务。
1. 自动提交事务
每一条单独的SQL语句都在其执行完成后进行自动提交事务,即执行 SQL 语句后就会马上自动隐式执行 COMMIT 操作。如果出现错误,则进行事务回滚至之前状态。
2. 显式事务
通过指定事务开始语句来显式开启事务来作为开始,并由以提交命令或者回滚命令来提交或者回滚事务作为结束的一段代码就是一个用户定义的显式事务。
3. 隐式事务
在隐式事务中,无需使用BEGIN TRANASACTION 来开启事务,每个SQL语句第一次执行就会开启一个事务,直到用COMMIT [TRANSACTION]来提交或者ROLLBACK [TRANSACTION]来回滚结束事务。
在ORACLE、MYSQL和SQL SERVER中,这几种模式的开启和关闭方式都有着自己不同的命令,具体命令请参见《关系型数据库事务详解之三:事务运行模式》
三、事务的运行状态
事务在执行过程中有着不同的状态:
活动状态
事务初始和执行时所处于的状态。
部分提交状态
事务执行完最后一条语句,直到操作实际输出全部从内存写入到磁盘这一过程,处于部分提交状态。
提交状态
事务对数据的更改全部写入磁盘,并写入相关事务日志。事务就进入提交状态。
失败状态
事务未正常执行完,事务就进入失败状态。这种状态的事务必须回滚。
终止状态
事务进行了回滚,并且数据库已经恢复到事务执行前的状态。这个时候处于终止状态。
四、事务的并发控制
数据库允许多个事务可以同时并发执行。当多个事务同时执行操作同一个数据,会造成以下并发问题:脏读、不可重复读和幻读。
脏读 是指一个事务读取到了另一个事务尚未提交的数据。
不可重复读 是指受到其他事务更改数据的影响,一个事务多次读到的同一个数据的值不一致。
幻读 是指因为受到其他事务插入或者删除操作的影响,一个事务读到的两次的数据记录数目不一样。
数据库通过设置隔离级别来应对这几种问题。事务的隔离级别分别4种,按级别从低到高依次是Read uncommitted 、Read committed 、Repeatable read 、Serializable 。
Read Uncommited 就是一个事务在其他事务还没有提交更改时,可以读取数据。这种级别,以上三种并发问题都会出现。
Read Committed 就是一个事务要等其他事务提交后才能读取数据。在这种隔离级别下有可能发生不可重复读和幻读。
Repeatable Read 就是在一个事务开始读取数据时,不再允许其他事务有修改数据的操作。这种级别就不会发生不可重复读,只有可能出现幻读。
serializable 就是所有的事务都是串行执行的,一个事务执行完了,再接着执行其它的事务。这样可以解决以上说有并发问题,但是因为通过大量的加锁来实现的,所以效率很低。