ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

java代码中避免oracle锁表的措施(事务)、@Transaction、@TransactionRollback

java代码中避免oracle锁表的措施(事务)、@Transaction、@TransactionRollback 文章目录场景原因分析人为for update忘记提交代码导致的锁表临时方案临时方案一临时方案二代码剖析异常未被捕获到导致回滚代码未执行异常未被捕获到用finally中代码回滚可以么用Exception捕获所有未知异常Transactional注解控制事务总结Transactional 事务Transactional 事务设置超时时间手动事务for update语句后面加nowaitfor update后面加skip lockedTransactionRollback锁表原因分析通过任意包含commit的语句如update语句等来终止事务通过transactionManager.commit(status); 显式的终止事务。通过Transactional注解实现事务一定终止通过业务来实现锁的控制其他事务可以被多次终止么transactionManager的isComplete()方法55Transactional事务 细粒度不足forupdate 会被update语句结束表锁for update no wait 获取不到会抛出什么异常Transactional加Transactional一定为了回滚么?Transactional 设置超时时间Transactional设置触发回滚的类型rollbackFor设置触发回滚的异常类为什么设置了异常回滚类还是不回滚如果rollbackFor没有抓到对应的类事务会回滚么?会结束么?noRollbackFor设置不触发回滚的异常类for循环中的事务的回滚问题场景项目中某些操作失败看日志发现是锁表了。原因分析人为for update忘记提交这个就比较恶劣了例如在pl/sql中对某条记录执行了for update然后忘了提交。它是不会自动提交的直到session关闭也就是关闭掉这个查询窗口(或者关闭掉pl/sql应用也行)。这种情况要坚决避免修改完语句要尽快提交。代码导致的锁表主要是事务没有结束引起的可能的情况有2种原因特点异常未被捕获导致事务终止代码未执行这种很隐蔽尤其是代码多的时候不好找到错误忘记提交或回滚了细心点可以避免临时方案临时方案一oracle命令行kill掉所有锁住的session临时方案二重启项目释放掉引起锁住的服务。这个要偷偷的来啊别让客户发现了如果有2个应用那么没事一个一个重启不影响使用。代码剖析虽然有临时方案但是从根本上解决问题才是最好的办法先分析下。异常未被捕获到导致回滚代码未执行有一点一定要注意 for update不会自动的提交要提交for update可以有几种方法1、显式的事务终止如 transactionManager.commit()或者rollback()2、update执行完毕后会隐式的关闭当前事务也会关闭for update。3、Transactional注解会把当前方法的所有代码都包在一个事务中。方法完成或异常都会终止事务。模拟下异常未被捕获到回滚代码是否执行。代码// 异常未被捕获到回滚代码是否执行ResponseBodyRequestMapping(/demo1)publicStringdemo1(){TransactionStatusstatustransactionManager.getTransaction(newDefaultTransactionDefinition());try{userMapper.forupdate(张三);// for update占用这条记录System.out.println(0/0);// 模拟异常Thread.sleep(1000);transactionManager.commit(status);System.out.println(已提交);}catch(InterruptedExceptione){e.printStackTrace();transactionManager.rollback(status);System.out.println(异常了 回滚);}returndemo1;}执行一下发现请求一直得不到回应。2句日志也都没有打印出来。原因catch中只捕获了Thread.sleep的InterruptedException异常。0/0 的算术异常没有捕获到。 异常阻断代码的执行按道理该报错并返回500。 但是因为事务未终止(rollback或commit都可以终止事务)造成代码一直在等待事务回滚。这个service一直不停止请求也一直得不到返回。 这个for update也一直不停止。异常未被捕获到用finally中代码回滚可以么finally表示代码总是会被执行如果异常没被捕获到我加个finally来关闭事务可以吧。如下代码// 异常未被捕获到 再finally中添加回滚代码有用么ResponseBodyRequestMapping(/demo2)publicStringdemo2(){TransactionStatusstatustransactionManager.getTransaction(newDefaultTransactionDefinition());try{userMapper.forupdate(张三);// for update占用这条记录System.out.println(0/0);// 模拟异常Thread.sleep(1000);transactionManager.commit(status);System.out.println(已提交);}catch(InterruptedExceptione){e.printStackTrace();transactionManager.rollback(status);System.out.println(异常了 回滚);}finally{if(!status.isCompleted()){System.out.println(事务未完成 进行回滚);transactionManager.rollback(status);}}returndemo2;}实测无效finally表示总是执行但异常后事务会等待回滚一直卡住了根本执行不到finally。用Exception捕获所有未知异常对于不确定的异常用Exception来捕获然后回滚事务可以么。代码// 异常未被捕获到用Exception捕获所有异常然后执行回滚代码ResponseBodyRequestMapping(/demo3)publicStringdemo3(){TransactionStatusstatustransactionManager.getTransaction(newDefaultTransactionDefinition());try{userMapper.forupdate(张三);// for update占用这条记录System.out.println(0/0);// 模拟异常Thread.sleep(1000);transactionManager.commit(status);System.out.println(已提交);}catch(InterruptedExceptione){e.printStackTrace();transactionManager.rollback(status);System.out.println(InterruptedException异常了 回滚);}catch(Exceptione){e.printStackTrace();transactionManager.rollback(status);System.out.println(Exception异常了 回滚);}returndemo3;}实测有效。catch (Exception e) 能够捕获所有异常然后执行回滚代码。Transactional注解控制事务手动控制事务太麻烦用注解试试。配置类先加上EnableTransactionManagement启动注解服务。service或者方法上添加Transactional,代码// 事务注解看能搞定么 只要抛出了异常事务就会触发回滚TransactionalResponseBodyRequestMapping(/demo4)publicStringdemo4(){try{userMapper.forupdate(张三);// for update占用这条记录System.out.println(0/0);// 模拟异常Thread.sleep(1000);System.out.println(已提交);}catch(InterruptedExceptione){e.printStackTrace();System.out.println(异常了 回滚);}returndemo4;}实测有效。代码中未被catch的异常会触发回滚。总结综上手动事务还是Transactional 事务都是可以的。二者没有绝对好坏要看应用场景。Transactional 事务使用简单代码量少而且不易出错。机制就是有异常就触发回滚。Transactional 事务设置超时时间相当于给方法加一个定时器如果超时就会触发SQLTimeoutException。例如Transactional(timeout 10)手动事务更加灵活可以处理复杂的场景例如一个方法中多次事务。但是需要一定功底用不好的话代码量会大很多而且容易挖坑。for update语句后面加nowait一方面要少用for update如果非用不可最好在后面加nowait。nowait如果获取不到权限会立刻报错返回这样即使有个别数据被锁住了也会阻止后续的数据被锁住。select*fromt_userforupdatenowait;for update后面加skip lockedfor update skip locked 这样如果一条记录已经锁住就不会查询出来。select*fromt_userwheresex男forupdateskip lockedTransactionRollbackspring中并没有这个注解这是自定义的注解。应用场景如果一个多维查询需要临时表数据用这个注解实现自动删除临时表。注解定义Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)publicinterfaceTransactionRollback{}aop代码AspectComponentpublicclassTransactionRollbackAspect{AutowiredprivatePlatformTransactionManagertxManager;Around(annotation(cn.minmetals.fm.fim.bud.biz.common.annotation.TransactionRollback))publicObjectaround(ProceedingJoinPointjp)throwsThrowable{DefaultTransactionDefinitiondefnewDefaultTransactionDefinition();def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);TransactionStatusstatustxManager.getTransaction(def);try{returnjp.proceed();}finally{txManager.rollback(status);}}}锁表原因分析经常遇到锁表最主要的原因就是for update之后事务没有终止。通过任意包含commit的语句如update语句等来终止事务一定要保证update的终止。例如下面例子查询id为1的数据是存在的。 如果age大于9999就改为99。这里有个问题实际中age不会大于9999所以不会执行update。 这个事务就终止不了一直缩下去。UserBeanuserBeannewUserBean();userBean.setId(1l);ListUserBeanusersuserMapper.forupdateUser(userBean);// for update占用全表if(!CollectionUtils.isEmpty(users)){if(users.get(0).getAge()9999){// 如果大于9999users.get(0).setAge(99l);// 改为99userMapper.updateUser(userBean);// 更新}}通过transactionManager.commit(status); 显式的终止事务。如果不好控制可以采用个懒人通用模板。就是一个大try 给他括起来然后catch 所有Exception。 有个问题就是多次return的代码里不好用。代码如下TransactionStatusstatustransactionManager.getTransaction(newDefaultTransactionDefinition());try{// 代码...// 代码...}catch(Exceptione){e.printStackTrace();System.out.println(异常了);if(!status.isCompleted()){transactionManager.rollback(status);// 回滚}}finally{if(!status.isCompleted()){transactionManager.commit(status);// 提交}}通过Transactional注解实现事务一定终止如何理解Transactional 呢 其实就是相当于把代码用一个事务包上。savepoint a;// 开启事务try{//code//code}catch(RuntimeExceptione){rollbacktoa;// 运行时异常触发回滚}commit;// 提交Transactional 保证了事务的完整性除了细粒度不够之外还算不错。通过业务来实现锁的控制是这样的数据库级别的锁是最准确的。如果不涉及到多业务并发只对于一个业务来说用redis锁给票据维度加锁就完全够用即该业务发生时先判断锁不允许同票据同时发生业务这样基本也满足需求而且实现起来简单。其他事务可以被多次终止么一个事务只能被终止(回滚或提交)一次多次操作会报错。所以代码中rollback或commit不要重复了。如果重复操作提示如下donot call commit or rollback more than once per transactiontransactionManager的isComplete()方法该方法判断事务是否完成(回滚或提交)。该方法一个主要的作用就是可以避免重复终止引起的报错。如代码if(!status.isCompleted()){System.out.println(事务未完成 进行回滚);transactionManager.rollback(status);}55select * from system where rownum 10;Spring默认的事务传播行为是REQUIRED我这里使用的方法是在更新日志表的方法上加上Transactional(rollbackFor Exception.class, propagation Propagation.REQUIRES_NEW)Transactional事务 细粒度不足它只会整个提交或者回滚如果我第一步想提交第二步想回滚就做不到。例如select*from t_userforupdate;// 锁定t_user表update t_user set age‘35’ where id1;// 更新一条数据System.out.println(0/0);update t_user set age‘40’ where id2;// 在更新一条数据forupdate 会被update语句结束表锁forupdate表之后只要有条update语句就会释放掉。不再锁住了。所以如果forupdate多条的时候最好用事务控制下。for update no wait 获取不到会抛出什么异常CannotAcquireLockException 会报这个异常。Transactional加Transactional一定为了回滚么?这个还真不一定。例如为了避免锁表加Transactional(timeout 10 ) 保证事务一定结束。Transactional 设置超时时间timeout属性可以设置超时时间。相当于给方法加一个定时器如果超时就会触发SQLTimeoutException。例如Transactional(timeout 10)timeout 超时异常会被try catch捕获么超时异常会发生在引起超时的代码上所以如果用try到了代码会捕获到不用担心代码失控。Transactional设置触发回滚的类型实际中有的业务需要回滚有的不需要。需要回滚的业务转账业务(a账户转出b账户要收到才一致)不要回滚的业务下单支付业务(支付不成功下单业务当然不用撤销只需再付款即可)rollbackFor设置触发回滚的异常类Transactiona(rollbackFor{RuntimeException.class})默认是所有RuntimeException都会回滚。如果要添加其他触发回滚类不要忘记默认的 RuntimeException.class Transactional(rollbackFor{RuntimeException.class,ClassNotFoundException.class})为什么设置了异常回滚类还是不回滚这原因就多了但是主要就一条被捕获的异常类和设置的不匹配。1、如果使用了aop要注意下如果用around注解的方法里面try catch了异常那么么异常不会继续抛出。 事务就无法触发了。如果rollbackFor没有抓到对应的类事务会回滚么?会结束么?例如 锁表引起的SQLTimeoutException就不是运行时异常这样事务不会回滚但是仍然会结束。所以还是有用的因为他会终止事务避免持续锁表。noRollbackFor设置不触发回滚的异常类Transactiona(noRollbackFor{RuntimeException.class})for循环中的事务的回滚问题for循环中的事务会遇到这样的问题一条回滚其他也会被回滚。解决方案1、for 中的代码写为service方法然后在那个service上加Transaction。这种其实不好操作。 因为要加接口加方法。 而且service中有service的情况也很多。2、for循环中的代码加try catch只记录并不影响整体。这种可以第一for循环中单条有记录。其他可以继续执行。第二只要不抛出异常那么事务也不会回滚。但是这有一个问题不抛出异常那么本行代码也不会回滚。 如果是复杂的转账业务。 那么这笔相当于错了。
返回列表