数据库事务

事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。例如,银行转账工作:从一个账号扣款并使另一个账号增款,这两个操作要么都执行,要么都不执行。所以,应该把它们看成一个事务。事务是数据库维护数据一致性的单位,在每个事务结束时,都能保持数据一致性。

       针对上面的描述可以看出,事务的提出主要是为了解决并发情况下保持数据一致性的问题。

       事务具有以下4个基本特征。

●   Atomic(原子性):事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要么全部成功,要么全部失败。

●   Consistency(一致性):只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初状态。

事务的一致性理解
 
事务执行的结果必须是使 数据库从一个一致性状态变到另一个一致性状态。 
  保证数据库一致性是指当事务完成时,必须使所有数据都具有一致的状态。在关系型数据库中,所有的规则必须应用到事务的修改上,以便维护所有数据的完整性。
   www.2cto.com  
  保证数据库的一致性是数据库管理 系统的一项功能.比如有两个表(员工\职位),员工表中有员工代码、姓名、职位代码等属性,职位表中有职位代码、职位名称、职位等级等属性。你在其中员工表中进行了插入操作,你插入了一个新员工的信息,而这个新员工的职位是公司新创建的一个职位。如果没有一致性的保证,就会出现有这么一个员工,但是不知道他到底担当什么职责!这个只是它的一个小小方面。 
 
  读一致性也是数据库一致性的一个重要方面,在实际中,我们会遇到这种情况:我们对一个表中的某些数据进行了更新操作,,但是还没有进行提交,这时另外一个用户读取表中数据.这个时候就出现了读一致性的问题:到底是读什么时候的数据呢?是更新前的还是更新后的?在DBMS中设有临时表,它用来保存修改前的值,在没有进行提交前读取数据,会读取临时表中的数据,这样一来就保证了数据是一致的.(当前用户看到的是更新后的值)   www.2cto.com  
 
  但是还有一种情况:用户user1对表进行了更新操作,用户user2在user1还没有进行提交前读表中数据,而且是大批量的读取(打个比方:耗时3分钟)而在这3分钟内user1进行了提交操作,那又会产生什么影响呢?这个时候怎么保证读写一致性呢?这个时候DBMS就要保证有足够大的临时表来存放修改前的数值,,以保证user2读取的数据是修改前的一致数据.然后下次再读取时候就是更新后的数据了

 

●   Isolation(隔离性):事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。

●   Durability(持久性):事务结束后,事务处理的结果必须能够得到固化。

2.事务的语句
 
开始事物:BEGIN TRANSACTION
 提交事物:COMMIT TRANSACTION
 回滚事务:ROLLBACK TRANSACTION
3.事务的4个属性
     ①原子性(Atomicity):
事务中的所有元素作为一个整体提交或回滚,事务的个元素是不可分的,事务是一个完整操作。
  
②一致性(Consistemcy):事物完成时,数据必须是一致的,也就是说,和事物开始之前,数据存储中的数据处于一致状态。保证数据的无损。
  
③隔离性(Isolation):对数据进行修改的多个事务是彼此隔离的。这表明事务必须是独立的,不应该以任何方式以来于或影响其他事务。
  
④持久性(Durability):事务完成之后,它对于系统的影响是永久的,该修改即使出现系统故障也将一直保留,真实的修改了数据库



在一个应用中,依据事务的隔离级别将会有三种情况发生。 

  ◆ 脏读(dirty read): 当一个事务读取另一个事务尚未提交的修改时,产生脏读。 
  ◆  不可重复读(non-repeatable read): 同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。: 
  ◆  幻像读(phantom read): 同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻像读。 

数据库事务的隔离级别有4个,由低到高依次为Read uncommittedRead committedRepeatable readSerializable,这四个级别可以逐个解决脏读不可重复读幻读这几类问题。


√: 可能出现    ×: 不会出现

脏读 不可重复读 幻读
Read uncommitted
Read committed ×
Repeatable read × ×
Serializable × × ×

 

注意:我们讨论隔离级别的场景,主要是在多个事务并发的情况下,因此,接下来的讲解都围绕事务并发。

Read uncommitted 读未提交

公司发工资了,领导把5000元打到singo的账号上,但是该事务并未提交,而singo正好去查看账户,发现工资已经到账,是5000元整,非常高兴。可是不幸的是,领导发现发给singo的工资金额不对,是2000元,于是迅速回滚了事务,修改金额后,将事务提交,最后singo实际的工资只有2000元,singo空欢喜一场。

 

出现上述情况,即我们所说的脏读,两个并发的事务,“事务A:领导给singo发工资”、“事务B:singo查询工资账户”,事务B读取了事务A尚未提交的数据。

当隔离级别设置为Read uncommitted时,就可能出现脏读,如何避免脏读,请看下一个隔离级别。

Read committed 读提交

singo拿着工资卡去消费,系统读取到卡里确实有2000元,而此时她的老婆也正好在网上转账,把singo工资卡的2000元转到另一账户,并在singo之前提交了事务,当singo扣款时,系统检查到singo的工资卡已经没有钱,扣款失败,singo十分纳闷,明明卡里有钱,为何......

出现上述情况,即我们所说的不可重复读,两个并发的事务,“事务A:singo消费”、“事务B:singo的老婆网上转账”,事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。

当隔离级别设置为Read committed时,避免了脏读,但是可能会造成不可重复读。

大多数数据库的默认级别就是Read committed,比如Sql Server , Oracle。如何解决不可重复读这一问题,请看下一个隔离级别。

Repeatable read 重复读

当隔离级别设置为Repeatable read时,可以避免不可重复读。当singo拿着工资卡去消费时,一旦系统开始读取工资卡信息(即事务开始),singo的老婆就不可能对该记录进行修改,也就是singo的老婆不能在此时转账。

虽然Repeatable read避免了不可重复读,但还有可能出现幻读

singo的老婆工作在银行部门,她时常通过银行内部系统查看singo的信用卡消费记录。有一天,她正在查询到singo当月信用卡的总消费金额(select sum(amount) from transaction where month = 本月)为80元,而singo此时正好在外面胡吃海塞后在收银台买单,消费1000元,即新增了一条1000元的消费记录(insert transaction ... ),并提交了事务,随后singo的老婆将singo当月信用卡消费的明细打印到A4纸上,却发现消费总额为1080元,singo的老婆很诧异,以为出现了幻觉,幻读就这样产生了。

注:Mysql的默认隔离级别就是Repeatable read。

Serializable 序列化

Serializable是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。



一般的应用作隔离级别时,往往采用(2),(3)两种,(1),(4)两种前者轻后者重,但是Hibernate为了提高performance和scalability,在数据库一层中采用的是(2)的隔离级别,然后在程序中进行控制,从而实现了(3)的隔离级别,因此提高了企业应用的事务处理效率。当然Hibernate对于数据库一层的隔离级别也可以显示指定。Hibernate在程序中的控制方法有:version number, timestamp,对于遗留database也有optimistic-lock,而version number有时不能解决特殊的事务并发引起来的(3)问题,那么就需要针对特殊情况进行细粒度的事务控制,可以看一下LOCKMODE。

 

分析: 
        隔离级别为提交读的事务在每次查询时都会申请获取共享锁,而未提交读
则不会申请,而是直接执行语句 
         在提交读隔离级别下,事务读取数据时,对数据加上共享锁,查询结束后
立刻释放共享锁(共享锁的锁定时间与事务的隔离级别有关,见注1),在其他一个或者多个事务对某数据拥有共享锁期间,无法获得排它锁,即无法对数据进行修改。 
         当其他事务对数据不拥有共享锁时事务对数据进行更新操作,将获得排它
锁,直到事务提交才将锁释放。 
         若某一个事务获得排他锁时,则其他事务是不能获得共享锁的(即排他锁
与共享锁不能兼容)     
所以提交读就保证了不会出现脏读的情况   

可重复读的上锁情况是 
事务在查询时候获取共享锁,直到事务提交才将锁释放,在此期间其他事务无法获得排他锁,所以无法进行更新操作,进入等待,知道其他事务将共享锁释放。这样避免了问题     
 
可串行读(serializable) 
仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。   
总结:隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。   
注1: 
共享锁的锁定时间与事务的隔离级别有关,如果隔离级别为Read Committed的默认级别, 
只在读取(select)的期间保持锁定,即在查询出数据以后就释放了锁;如果隔离级别为 
更高的Repeatable read或Serializable,直到事务结束才释放锁。另说明,如果select 
语句中指定了HoldLock提示,则也要等到事务结束才释放锁。

你可能感兴趣的:(数据库事务)