数据库事务
事务是由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元。这些操作作为一个整体一起向系统提交,要么都执行,要么都不执行。
ACID原则
原子性
事务中包含的各项操作在一次执行过程中,要么全部成功执行,要么全部不执行。
事务中任何一项操作失败,都将导致整个事务失败,同时其他已经被执行的操作都将被撤销并回滚,只有所有的操作全部成功,整个事务才算是成功完成。
一致性
事务的一致性是指事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。也就是说,事务执行的结果必须是使数据库从一个一致性状态转变到另一个一致性状态。
事务的一致性强调的是数据库经过事务操作后由一个状态转变到另一个状态,但数据任然要保持一致,一个有名的例子就是:转账事务执行前,A有800元,B有200元,加起来是1000元,那么A向B转账300元的这个事务执行完成后,两个人的总金额没有因为转账而发生了变化。这体现在约束规则上。
隔离性
事务的隔离性是指在并发环境下,并发的事务是相互隔离的,一个事务的执行不能被其他事务干扰。即,不同的事务并发操纵相同的数据时,每个事务都有各自完整的数据空间,一个事务内部的操作和使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能相互干扰。
四个事务隔离级别:
读未提交
描述:一个事务可以读取另一个未提交事务的中间数据。可能出现脏读。
例子:老婆银行账户存有300元,准备购买一套500元的化妆品,钱不够,于是通知老公转换200元过来。接下来,老公开始转账200元过来(这一过程是一个转账事务),老婆在老公转账的事务完成前,启动了支付事务,正常情况下,完成500元支付这个操作,要在转账事务完成后才能执行,但这个时候隔离级别是读未提交
,在老婆支付事务过程中能读到转账事务过程的老婆账号增加200元
操作的结果,那么就可以完成500元的支付操作,但有可能在完成500元支付操作后,由于系统问题,老公转账失败,导致事务回滚,200元还原到老公账号,但结果老婆的确是能把500元花,老公的钱一分不会少,最后亏的是银行。
这里的老婆支付事务读取的那500元是属于脏读。
脏读:A事务执行过程,B事务读取了A事务修改的数据。但由于某些原因,A事务没能完成提交,发生回滚, 那么B事务所读取的数据是无效的,这个未提交数据就是脏读。脏读一般是针对于update操作的。
读已提交
描述:一个事务要等另一个事务提交后才能读取数据。解决了脏读的问题,但可能出现不可重复读。
可重复读
描述:在事务处理过程中,多次读取同一个数据是一样的,其值和事务开始时刻是一致的。解决了脏读和不可重复读问题,可重复读虽然能够防止其它事务修改它的数据,但不能防止其它事务在它读取数据范围插入数据,因此可能出现幻读。
串行化
描述:要求所有事务都被串行执行,即事务只能一个接一个地进行处理,不能并发执行。最严格的事务隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | ✔ | ✔ | ✔ |
读已提交 | ✘ | ✔ | ✔ |
可重复读 | ✘ | ✘ | ✔ |
串行化 | ✘ | ✘ | ✘ |
事务隔离级别越高,就越能保证数据的完整性和一致性,但同时对并发性能的影响也越大。
持久性
事务的持久性是指一个事务一旦提交,它对数据库中对应数据的状态变更就应该是永久性的。即,一旦某个事务一旦成功结束,那么它对数据库所作的更新就必须被永久保存下来。
并发事务带来的问题
脏读
A事务执行过程,B事务读取了A事务修改的数据。但由于某些原因,A事务没能完成提交,发生回滚,那么B事务所读取的数据是无效的,这个未提交数据就是脏读。脏读一般是针对于update操作的。
不可重复读
指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。比如事务A读取了某一条数据,然后其他逻辑的时候,事务B修改了该条数据,事务A再次读取的时候,发现数据不一样了,就是不可重复读。
一种更易理解的说法是:在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据并修改数据。那么,在第一个事务的两次读数据之间。由于另一个事务的修改,那么第一个事务两次读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。
幻读
A事务读取了两次数据,在这两次读取过程中B事务添加了数据,A事务的这两次读取出来的集合不一样。幻读一般是针对于insert操作的。
第一类丢失更新(回滚丢失)
在完全未隔离事务的情况下,两个事务更新同一条数据资源,某一事务完成,另一事务异常终止,回滚造成第一个完成的更新也同时丢失 。这个问题现代关系型数据库已经不会发生。
第二类丢失更新(覆盖丢失)
当两个或多个事务并发执行过程中查询了某一条数据,然后各自基于查询到的结果对数据进行更新操作,后完成的事务会覆盖掉先完成的事务的更新结果。
例如:A、B事务并发执行,都查询了一条数据X的值为100,然后A、B事务分别对数据X进行加100的操作,假设A事务先完成了加100的操作,这个时候X=200,接着B事务也完成了加100操作,这个时候X依然是等于200的,因为A、B事务不知道有其他事务在操作数据X,于是都在X=100的基础上进行操作。