事务的概念
事务是工作中的基本逻辑单元,一个事务可能包含一系列的数据库操作,而一个完整的事务保证这些操作都被正确地同步到数据库中,不会受到干扰而发生数据不完整或错误。如果事务中的某个SQL语句执行失败,就必须撤销整个工作单元,称之为事务回滚,因为事务中某一点执行的所有更改都会回到上一个保存点或事务的起始点,成功完成事务称为提交。
事务有4个重要特性好
●原子性:即作为一个事务,它是一个不可分割的整体,只有全部操作都完成了,才算结束:其中任何一个操作执行失败,整个事务都要撤销。
●一致性: 即事务不能破坏数据库的完整性和业务逻辑的一致性。 事务不管成功还是失败,事务结束时,整个数据库内部数据都是正确的。
●隔离性:即在并发数据库操作中, 不同事务操作相同的数据时,每个事务都有自己完整的数据空间。一个事务不会看到或拿到另一个事务正修改到一半的数据,这些数据要么是一个事务修改前的,要么是另一个事务修改后提是交的。拥有这个特性,是为了保证所有并发操作的正确性。
●持久性:即事务成功提交后,数据就被永久地保存到数据库,重新启动数据库系统后,数据仍然保存在数据库系统中。
并发产生的问题
在数据库读/写过程中,读/写的并发性问题是不可避免的。这样一来,同时运行的事务对数据库中的相同数据产生并发访问的可能性会很大,如果没有良好的隔离机制,则很容易出现各种并发带来的问题。
一般情况下,数据库并发产生的问题可以分为四种:更新丢失、脏读、虚读、不可重复读。
1.更新丢失
当多个事务同时操作同一数据时,由于事务之间完全没有进行隔离,撤销其中一个事务,结果覆盖了其他事务已经提交的并成功更新的数据,对其他事务而言造成了数据丢失。
例如,再取款和存款的情况下,如果没有采取措施,就会出现并发所带来的问题。
通过该表的操作后,账户余额为1000元。但实际上账户余额应为1100元,这种情况就属于更新丢失问题。
2.脏读
当多个事务同时操作同一数据时,如果事务A读到事务B尚未提交的数据,且对去进行操作,当事务B撤销了更新后,事务A所操作的数据便成了无效数据(脏数据)。同样以存取款为例。
通过该表的操作后,账户余额为1000元。但实际上账户余额应为1100元。
3.虚读
当多个事务同时操作同一数据时,如果事务A再操作的过程中进行了两次查询,很有可能第二次查询到的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为,在两次查询的过程中由事务B插入了新数据造成的。
再两次查询的过程中,得出了不同的结果,导致不知道账户余额到底是1100元还是1000元。
4.不可重复读
当多个事务同时操作同一数据时,如果事务A对同一行数据重复读取两次,去得到了不同的结果,有可能在事务A两次读取的过程中,由事务B对该行数据进行了修改,并成功提交。
若按照上面的步骤操作后,账户余额为1100元,但是实际上应该是1200元。
解决方案
为避免出现上面的几种情况,Hibermate 为应用程序提供了设置隔离级别及锁的功能。
1.隔离级别
标准SQL规范中提供了4种事务隔高级别,可以通过Hibermate的配置文件来设置。
●串行化(Serializable):提供严格的事务隔离。它要求各事务串行化执行,事务只能一个接着一个地串行执行,不能并发执行。当数据库采用此隔离级别时,只要有一个事务在操作某个数据,其他欲操作此数据的事务必须停下来等待,直至那个事务结束,有效地防止了所有可能出现的并发问题,但并发性能较低。
●可重复读取(Repeatable Read):当数据库采用此隔离级别时,一个事务在执行过程中可以访问其他事务成功提交的新插入的数据,但不能访问成功修改的数据,因而有效地防止了不可重复读取和脏读两类并发问题的发生。
●读已提交数据(Read Comitted):当数据库采用此隔离级别时,一个事务在执行过程中既可以访问其他事务成功提交的新插入的数据,又可以访问成功修改的数据,因而有效地防止了脏读。
●读未提交数据(Read Uncomitted):当数据库采用此隔离级别时,一个事务 在执行过程中既可以访问其他事务未提交的新插入的数据,又可以访问未提交的修改数据,因而仅仅防止了更新丢失的发生。
在实际应用中,隔离级别越高,越能保证数据的完整性和一致性,但对并发性能的影响也越大。对于大多数应用程序,可以优先考虑把隔高级别设为“读已提交数据”,它能够避免胜读,且具有较好的并发性能。尽管它会导致不可重复读、虚读和更新丢失等问愿,但在可能出现这类问题的个别场合下可在应用程序中采用“锁”来加以控制。
2.锁
业务逻辑的实现过程中,往往需要保证数据访问的排他性,如在金融系统的日终结算处理中,希望对某个结算时间点的数据进行处理,而不希望在结算过程中(可能是几秒,也可能是几个小时)数据再发生变化。此时,需要通过一些机制来保证这些数据在某个操作过程中不会被外界修改,这样的机制就是所谓的“锁”,即给选定的目标数据上锁,使其无法被其他程序修改。
Hibemate支持两种锁机制:悲观锁(Pessimistic Locking)和乐观锁(Opimistic Locking)。
悲观锁是指对数据被外界修改持保守态度。假定任何时刻存取数据时,都可能有一个客户也正在存取同一数据,为了保持数据被操作的一致性,于是对数据采取了数据库层次的锁定状态,依靠数据库提供的锁机制来实现。
乐观锁则乐观地认为数据很少发生同时存取的问题,因而不做数据库层次上的锁定。为了维护正确的数据,乐观锁采用应用程序上的逻辑实现版本控制的方法。Hibemate 中以通过版本号检索来实现更新为主(Hibemate推荐的方式)。在数据库中假如有一一个 Version记录,在读取数据时连带版本号一同读取,并在更新数据时递增版本号,然后比较此版本号和数据库中的版本号,如果大于数据库中的版本号,则给子更新,否则就报错误。
比如,有两个客户端,A客户先读取了账户余额为200元,之后B客户也读取了账户余额为200元的数据,在并发情况下,A客户提取了100元,对数据做了变更,此时数据库中的数据余额为100元: B客户也要提取80元,根据所取得的资料,(200-80) 将为120元,再对数据库进行变更,最后的余额肯定不正确。
若采用乐观锁,A客户读取账户余额200元,并连带读取版本号为5,B客户此时也读取账号余额为200元,版本号也为5。A客户在领款后账号余额为100,此时将版本号变为6,而数据库中的版本号为5,所以准予更新,更新后的余额为100,数据库版本号为6。B客户领款后要变更数据库,其版本号为5,但是数据库中的版本号为6,则不予更新。如果B客户试图更新数据,将会引发异常,可以捕捉这个异常,在处理数据时重新读取数据库中的数据。