事物、数据锁

1、事务隔离级别

     事务有四大特性:原子性、一致性、隔离性、持久性。其中事务的隔离比较重要,事务隔离性处理在数据并发量较大的系统显得比较重要。

     数据库在事务隔离这块提供了四种事务隔离级别,分别是:读取未提交内容(Read Uncommitted),读取已提交内容(Read Committed),可重读(Repeatable Read),串行化(serializable)。

  不同的数据库关于事务隔离的默认级别不同,Oracle数据库默认的隔离级别为Read Committed, MySql的默认隔离之别则为Repeatable Read;

 

2、事务隔离级别对应产生的问题

     为什么不同的数据库采用了不同的默认隔离级别?因为不同的隔离级别在并发操作数据库时会产生不同的问题。 也就是由隔离级出现对应的问题:脏读、不可重复读及幻读

  先了解这几个问题的概念及产生。引用自:MySql事务隔离详解  

     (1) 脏读:在事务并发处理过程中,某一事务读取了另一个事务未提交的数据。如事务A读取到了事务B修改过的数据,但事务B未进行提交。

时间点事务A事务B
1select t.money  from t_account t where t.name='scl' 
2 update t_account t set t.money = t.money+1000 where t.name = 'scl'
3select t.money from t_account t where t.name = 'scl' 
4 commit

    假如 在两事务A、B 都未执行前,scl账号有1000元, 在事务开始后,如果事务A第一次读取到的数据为1000,第二次读到的数据为2000。那么这就出现了脏读。因为在两个不同事务里面应该是相互独立的。不应该读到其他事务的数据。

 

  (2)  不可重复读:在事务并发处理过程中,事务A在某时间点1内读到的数据跟时间点2读到的数据不一致。跟脏读的区别是,事务A先进行查询,然后事务B提交了某一行记录的修改,事务A再次查询数据发现两次读出来的数据不一致。(事务A读取了事务B提交的内容,两次查询某一行或几行数据内容不一致)

时间点事务A事务B
1select t.money  from t_account t where t.name='scl' 
2 update t_account t set t.money = t.money+1000 where t.name = 'scl'
3 commit;
4 select t.money from t_account t where t.name = 'scl' 

         假如 在两事务A、B 都未执行前,scl账号有1000元, 在事务开始后,如果事务A第一次读取到的数据为1000,第二次读到的数据为2000。那么这就出现了不可重复读。因为在      两个不同事务里面应该是相互独立的。A读到了B提交的事务,导致两次读取结果内容不一致。

    (3)  幻读:某事务A进行a表数据查询,然后事务B在a表内插入了一些新数据并且提交,事务A两次查询的数据条数存在差异。 与不可重复读得区别是数据条数增加了。

时间点事务A事务B
1select *  from t_account t   
2 

insert into t_account values ("1000","张三",2000);

insert into t_account values ("1000","李四",3000);

insert into t_account values ("1000","王五",4000);

insert into t_account values ("1000","钱六",6000);

3 commit;
4select *  from t_account t  

           假如 在两事务A、B 都未执行前,使用sql:select * from t_account 返回的是3条数据,在两事务开启后,事务A第一次读取到3条数据,在事务B提交后,事务A读取到了7条数据,那么久出现了幻读现象。

 

3、事务隔离级别应对事务并发产生的问题

  既然事务隔离级别不同会导致事务并发产生问题,那么四个事务隔离级别分别会产生什么问题?在开发时应选择什么哪个隔离级别方式?

隔离级别脏读不可重复读幻读

读取未提交内容(Read Uncommitted)

  √       √ √
读取已提交内容(Read Committed)  X       √ √
可重读(Repeatable Read)  X       X √
串行化(serializable)  X       X X

 

 

 

 

 

       

        可重读这个隔离级别有可能会出现幻读,有可能不出现。取决于数据库当时的实现。

4、事务隔离级别的查询与修改

   Mysql事务隔离有全局事务,会话事务之分。可以通过以下指令查询Mysql里面事务的隔离级别:

         1.SELECT @@global.tx_isolation; 

         2.SELECT @@session.tx_isolation;

         3.SELECT @@tx_isolation;

 
   session 指的是当前连接的事务级别,global则是全局的隔离级别。应该使用gobal来设置全局事务隔离级别。并进行相关的测试。
 
   修改事务隔离级别使用指令:set tx_isolation='read-committed';
   或者完整的事务隔离更改方法: set global transaction isolation level READ COMMITTED;




  需要注意的是:即使使用了全局事务隔离级别设置,但开启的窗口可能有session级别的缓存,把查询窗口关闭再进行一次查询即可确认事务级别是 否正确修改。(在64位Mysql5.5下测试过,无法在已开窗口下正确查询事务隔离级别) 



在事务的隔离级别内容中,能够了解到两个不同的事务在并发的时候可能会发生数据的影响。细心的话可以发现事务隔离级别章节中,脏、不可重复、幻三个问题都是由事务A对数据进行修改、增加,事务B总是在做读操作。如果两事务都在对数据进行修改则会导致另外的问题:丢失更新。这是本博文所要叙述的主题,同时引出并发事务对数据修改的解决方案:锁机制。

1、丢失更新的定义及产生原因。

   丢失更新就是两个不同的事务(或者Java程序线程)在某一时刻对同一数据进行读取后,先后进行修改。导致第一次操作数据丢失。可以用图表表示如下:

时间点事务A                                 事务B                                             
1 start transcaction; 
2 start transcaction;
3update t_customer ts set ts.name='张三' where ts.id='10'; 
4 update t_customer t set t.name='李四',t.age=20 where ts.id='10'
5commit; 
6 commit;

 

 

 

 

 

 

   

     

 

       假如原来t_customer表内id为10的行,是一条{id:10,name:"王五",age:15} 的数据,经过事务A修改后变成{id:10,name:"张三",age:15}。事务B提交后,该数据变成了{id:10,name:"李四",age:20}。由事务A所执行的操作在事务B的提交后,数据被冲掉了。这个现象就叫做丢失更新。

     备注:事实上Mysql数据库会在事务里面默认添加写锁,上面的现象是没法重现的。了解即可。丢失更新就是由并发修改数据造成的。如下图所示:

    

 

2、如何避免丢失更新的发生。

  2.1 避免丢失更新有两种方法:

            (1) 不使用事务。[最不可能的方法]

            (2)使用数据库锁机制防止丢失更新 

 

  2.2 数据库的锁

          解决丢失更新的方法有好几个,先来了解下数据库里面的"锁"。从数据库功能上面来看,数据库设计上分为两种锁:读锁(共享锁)和写锁(排它锁)。

            数据库在设计这两种锁的时候,这两种锁间的关系如下:读锁与读锁可以共存,读锁与写锁互斥,写锁与写锁互斥。(这种设计跟Java线程锁机制是一样的)。

      使用数据库添加读锁和写锁的方法很简单 , 但需要注意的是锁必须在事务内进行声明。在事务外声明的锁将不具备效应。

      为表添加读锁的方法:select * from t_account lock in share mode; (读锁与他人共享读操作,很容易导致死锁。)

      为表添加写锁的方法:select * from t_account for update;

         

       在JDBC这块,事务一般通过sql脚本去控制处理。因此可以在sql脚本处添加上锁去进行控制。(后面会整理通过ORM框架:hibernate对事务进行控制)

  2.3 通过数据库的锁机制解决丢失更新

       解决丢失更新,主要使用两种方法:

             1. 使用排它锁。经过上面基于数据库锁的介绍可知,丢失更新可以使用写锁(排它锁)进行控制。因为排它锁添加到某个表的时候,事务未经提交,其他的事务根本没法获取修改权,因此排它锁可以用来控制丢失更新。需要说明的是有时候,当知道某一行会发生并发修改的时候,可以把锁定的范围缩小。例如使用select * from t_account t wheret.id='1' for update; 这样能够比较好地把控上锁的粒度,这种基于行级上锁的方法叫"行级锁"。

    2. 使用乐观锁。

           乐观锁的原理是:认为事务不一定会产生丢失更新,让事务进行并发修改,不对事务进行锁定。发现并发修改某行数据时,乐观锁抛出异常。让用户解决。可以通过给数据表添加自增的version字段或时间戳timestamp。进行数据修改时,数据库会检测version字段或者时间戳是否与原来的一致。若不一致,抛出异常。

  

时间点事务A事务Bversion值
   1
1提出修改 :update t_account t set t.money=t.money+100 where t.name ='a'提出修改 :update t_account t set t.money=t.money+100 where t.name ='a'1
2commit; 校验事务A与version值,version字段值都为"1",通过提交内容,version字段值更新为2
3 commit;

校验事务B与version值,事务B提交前的version字段值为1,但当前version值为2,禁止事务B提交.抛出异常让用户处理

 

 

 

 

 

 

 

    

       时间戳与version的判断都一样,时间戳记录的是当前时间。提交前数据库会校验提交前的时间戳是否与当前的一致。若一致则更新时间戳。

      以上为本次对数据库并发更新的总结,后面补充在ORM框架下如何上锁。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值