在数据库的使用中,咱们经常把一系列操做的集合看做是一个独立的单元,这种构成单一逻辑工做单元的集合被称为事务。sql
一个数据库系统须要维护事务的如下四种特性,它们被合称为**"ACID",分别对应原子性**(Atomicity),一致性(Consistency),隔离性(Isolation),持久性(Durability)。数据库
咱们用T来指定这个模型,它的SQL大体是如下形式:bash
-- 操做1:扣除A帐户10元 UPDATE account SET amount = amount - 10 WHERE user_name = 'A' -- 操做2:增长B帐户10元 UPDATE account SET amount = amount + 10 WHERE user_name = 'B' 复制代码
T是一个最简单的转帐模型,A用户将10元转到了B用户的帐本上。服务器
以后咱们就用这个事务模型来解释一下事务的四种特性。markdown
原子性表示,一个事务所包含的操做集合是单一的,不可拆分的。并且事务中任何一个操做失败,都要保证数据库回滚到整个事务执行前的状态。并发
在T事务中,有两个操做,一个是在A帐户扣除10元,一个是在B帐户增长10元。这个两个操做密不可分。性能
若是咱们将这两个操做当作两个独立的事务,那么假设初始状态:spa
A帐户:100元
B帐户:100元
复制代码
咱们如今执行了操做A,A帐户余额变为90元。而后,而后!服务器室因为某些不可控力,发生了爆炸。那么最终结果将会变成:code
A帐户:90元
B帐户:100元
复制代码
A和B就此开始了无止境的撕逼。。orm
B:你快给我转钱啊!
A:我转了啊!你看个人帐户已经扣了10元了!
B:我这里没有收到啊!你本身看,仍是100元!
......
一致性原则要求事务的执行不改变数据库的一致。即事务执行前若是数据库一致,事务执行后,这种一致性仍然存在。
以T事务为例,T执行前,A和B的帐户余额总和为200元,那么咱们要保证在T执行后,A和B的帐户余额总合仍然为200元。
持久性的原则要求一旦事务成功完成执行,而且提交到数据库,那么这一次更新将会持久的。也就是说,只要事务成功执行,任何的系统故障都不能撤销这一次事务的提交。
这个概念可能出现一些小漏洞,好比若是事务的结果储存在内存中,那么一旦宕机,全部的数据都会消失,咱们须要将数据提交到外部磁盘上,而且作好更新信息的记录,使数据库在宕机后重启凄然能恢复到以前的状态。因为这个概念不属于咱们的讨论范围,这里也就再也不赘述。
隔离性确保事务并发执行后的系统状态与这些事务以某种次序串行执行之后的状态是等价的。
若是有多个事务并发执行,即便咱们确保事务的原子性和一致性,这些操做在执行时也并非严格的串行,而是以某种不可见的形式交叉执行,这种不可见行极可能会致使最终状态的不一致。
举个栗子,咱们将以前的事务T记做事务T1,并将T1中的操做细分,他们在系统中的实际操做应该大体是这样的:
/** * read(x):从数据库中将x传送到执行read操做的事务的主存缓冲区中 * * write(x):从执行write的事务的主存缓冲区中将x取出并写回数据库(其实还有一个commit过程,这里先忽略) */ read(A); A := A-10; write(A); read(B); B := B+10; write(B); 复制代码
除此之外,咱们再定义一个T2,计算A+B的值:
read(A); read(B); A := A+B; 复制代码
并行的事务会如何执行呢?若是运气好,它可能会按照T1,T2的顺序完整执行,那么最终咱们获得的temp的状态应该是200。
可是若是出现一种状况,当T1中的A扣款成功,并切入数据库,而在执行给B增长余额的操做时,并无所有完成,而是执行完B := B+10
之后,开始执行T2,虽然B变量确实发生了改变,可是它尚未被写进数据库中,因此T2中计算出的temp变成了90+100=190。
大体流程会是这个样子:
-- T1 read(A): A := A-10; write(A); --这里A在数据库中的值变成了90 read(B); B := B+10; --这里B确实发生了改变,可是并未提交至数据库 -- T2 read(A); --A = 90 read(B); --B = 100(不是110) temp := A+B; --获得190 --T1 write(B) --这里B的修改被提交到数据库 复制代码
为了确保隔离性,数据库系统中存在一种并发控制系统,来完成这一职责。
在介绍事务的隔离级别前,先来介绍一下脏读,幻读,不可重复读的概念。
-- T1 read(A); A := A+1; write(A); --T2 read(A); A := A+2; write(A); commit; -- T1 commit; 复制代码
-- 假设初始状态,A=10 -- T1 read(A); -- A = 10; -- T2 read(A); A = A+10; write(A); commit; -- A = 20; -- T1 read(A); -- A = 20,与第一次不一样 复制代码
-- T1 UPDATE users SET status = 1; -- T2 insert users (`status`) values ('0') 复制代码
而后执行T1操做的用户惊奇的发现,明明把全部的user状态都置1了啊,怎么还有一个0 ??????
可串行化(Serializable):sql中最高的隔离性级别,可以避免脏读,幻读,不可重复读。代价也相对沉重,会大大影响数据库的性能。
可重复读(Repeatable read):只容许读取已提交的数据,并且在一个事务两次读取一个数据项期间,其余事务不得更新该数据。这种状态不能避免幻读。
已提交读(Read committed):只容许读取已提交数据,但不要求可重复读。这种状态只能避免脏读。
未提交读(Read uncommitted):容许读取未提交的数据。这是最低的隔离级别,脏读,幻读,不可重复读都没法避免。
Attention:全部的隔离级别都不容许脏写,即若是一个数据项已经被另一个还没有提交或终止的事务写入,则不容许其余事务对它进行写入。