事务隔离级别与乐观锁、悲观锁

1.引入

     在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这样会产生冲突,就会造成并发性问题。

     冲突类型:

     (1)脏读

               指一个事务A正在访问数据,并且对数据进行了修改,但是这种修改还没有提交到数据库,这时另一个事务B也访问这个数据,而A事务产生了异常,发生了回滚,但是B事务使用的还是A修改后的数据,这个就是脏读

              例如:张三的工资为5000,事务A把张三的工资改为8000,但是A事务尚未提交,与此同时,事务B正在读取张三的工资,读取到8000,然后事务A发生了异常发生了回滚,将张三的工资又变为5000,那么事务B读到的张三工资为8000就是脏数据,事务B做了一次脏读

     (2)不可重复读

               指在一个事务A中,多次读同一个数据,在这个事务还没有结束时,另一个事务B修改了同一个数据,那么如果在事务A中的两次读数据之间,事务B修改了数据,那么事务A两次读取到的数据可能是不一样的,这就是不可重复读

               例如:在事务A中,读取到张三的工资为5000,事务还没有提交,此时事务B将张三的工资修改为8000,并提交了事务,然后,在事务A中再次读取张三工资,此时读到的是8000,在同一个事务中读到的数据不一致,导致了不可重复读

     (3)幻读

               指在一个事务对一个表中的数据进行了修改,这种修改设计多条数据,此时事务B向表中插入或删除一条新数据,那么操作第一个事务的用户就会发现表中还有或者多出修改的数据行,就好像发生了幻觉一样

               例如:目前工资为5000的有10人,事务A读取所有工资为5000的人数为10人,此时,事务B插入一条工资也为5000的记录,这时事务A再次读取工资为5000的员工,记录为1000人。此时产生了幻觉


       注意:

                  脏读和不可重复读重点是修改:同样的条件,读取过的数据,再读出来不一样了

                  幻读重点是增加或删除:同样的条件,第一次和第二次读出来的记录数不一样了


2.事务隔离级别

     (1)读未提交

               事务最低隔离级别,事务对数据只是修改了还没提交,其他事务就可以读出来。

               脏读、不可重复读、幻读都可能发生

     (2)读已提交

               事务对数据的修改提交了,其他事务就可以看到。

               解决了脏读问题,但是会产生不可重复读、幻读的问题

     (3)可重复读

               事务开始的时候,会创建一个对数据库对应记录的视图,保存了开始时数据库中数据的状态,这样即使其他事务修改了数据,本事务也读取不到改变。

               解决了脏读、不可重复读问题,但是仍然会产生幻读(因为视图只是对应行的)

     (4)串行化

               事务只能一个个来,不能并行执行


3.悲观锁与乐观锁

     (1)乐观锁

              1)定义

                    顾名思义,就是很乐观,我们认为系统中的事务并发更新不会很频繁,即使冲突了也没事,大不了重新再来一次。基本思想就是:每次提交一个事务更新时,我们想看看修改的东西从上次读取以后有没有被其他事务修改过,如果修改过,那么就会更新失败

                    因为乐观锁不会锁定任何记录,所以当数据库的事务隔离级别设置为读已提交或者更低时,是不能避免不可重复读问题的所以采用乐观锁的时候,系统应该容许不可重复读问题的出现

              2)实现方式

                    在数据库表中添加一个version字段来实现。

                    读取数据时,将此版本号一起读出,之后更新时,对此版本号+1。然后将提交数据的版本号和表中记录的版本号进行对比,如果提交的数据版本号大于数据库表当前版本号,则说明没有其他事务修改,允许更新,否则认为是过期数据

                    例子:

                    我们有一个Account的实体类,在Account中多加一个version字段,那么我们JDBC中sql语句将如下写

Select a.version....from Account as a where (where condition..)

Update Account set version = version+1.....(another field) where version =?...(another contidition)
                    这样一来我们就可以通过更新结果的行数来进行判断,如果更新结果的行数为0,那么说明实体从加载以来已经被其他事务更改了,所以就抛出自定义的乐观锁异常

  int rowsUpdated = statement.executeUpdate(sql);

  If(rowsUpdated= =0){

  throws new OptimisticLockingFailureException();

  }
         在是用JDBC的情况下,我们需要在每一个update语句中进行版本字段的更新以及判断,因此如果稍不小心就会出现版本字段没有更新的问题,相反当前的ORM框架为我们做好了一切,我们仅仅需要做的就是在每个实体中增减一个version或者是Date字段

         Hibernate中使用乐观锁:

         如果我们使用Hibernate作为持久层框架,那么乐观锁实现非常简单,框架会帮我们生成相应的sql语句,不仅仅减少了开发人员的负担,而且也不会出错。还是以刚才的Account实体为例:

 public class Account{

  Long id ;

  .......

  @Version //也可以采用XML文件进行配置

  Int version

  .......

  }
          这样一来,每次我们提交事务时,Hibernate内部会生成相应的sql语句将版本字段+1,并进行相应的版本检测,如果检测到发生了乐观锁异常,那么就抛出StaleObjectStateException

     (2)悲观锁

              1)定义

                    顾名思义,就是很悲观,我们认为系统中的事务并发更新很频繁,并且事务失败了以后重来的开销很大,这样一来,我们就需要真正意义上的锁来进行实现。基本思想就是:每次一个事务读取数据后,就会将这些数据锁住,这样其他的事务要想更新,必须等以前的事务提交或者回滚解除锁

                   如果我们的数据库的事务隔离级别设置为读已提交或者更低,那么通过悲观锁,我们控制了不可重复读问题,但是不能解决幻读问题,因为要想避免我们就需要把数据库的事务隔离级别设置为串行化。而一般情况下我们都会采取读已提交或者更低的隔离级别,并且配合乐观锁或者悲观锁来实现并发控制,所以幻读问题不能解决,如果要想避免幻读问题,那么只能设置数据库的事务隔离级别为串行化

              2)实现方式

                   悲观锁的实现,只能依靠数据库提供的锁机制。因为只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则, 即使在本系统实现了加锁机制,也无法保证外部系统不会修改数据
                   在JDBC中使用悲观锁,需要使用select  for update语句,假如我们有一个Account类,我们可以采用如下方式进行查询:

Select * from Account where ...(where condition).. for update.
                   当使用了for update之后,每次在读取一条记录的时候,都会锁住被加载的记录,那么当其他事务如果要更新或者是加载此记录就会因为不能获得锁而阻塞,这样避免了不可重复读和脏读的问题,但是其他事务还是可以插入和删除记录,所以会产生幻读,但这不是悲观锁的问题,是我们数据库事务隔离级别所造成的问题。

                  在每一个事务中,我们必须使用select for update语句来进行数据库操作,如果有一些事务没有使用select for update,那么就会很容易造成错误。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值