全局锁和悲观锁的异常处理
来源:互联网 发布:电力数据库 编辑:程序博客网 时间:2024/06/05 05:04
悲观锁一般用在事务中“独占的”持有一个资源,这样是为了在并发操作中保护数据一致性。悲观锁的使用非常常见,但是我们代码中对悲观锁的认识上存在不足,导致对异常的处理其实是不到位的,比如以下代码:
protected void lockInvoiceAndSet(ApInvoiceOperateContext context) { ApInvoice invoice = apInvoiceRepository.lockByInvoiceId(context.getInvoiceId()); if (invoice == null) { LoggerUtil.warn(logger, "锁定发票失败,", "发票id:", context.getInvoiceId()); throw new GFCenterException(GFCenterErrorCodeEnum.AP_INVOICE_LOCK_ERROR); } context.setOrigInvoice(invoice); }
这种代码很常见。思路无外乎是:1. 锁, 2.判, 3.决策。
1. “锁”,很直观,就是直接调用lock方法,这个方法要么是对select for update或者select for updat nowait/waitxxx的包装。
2. “判”,判什么?我觉得需要判两种可能:
· 锁失败:数据是存在于数据库中的,只是因为已经被其他资源占用无法获取独占锁。
· 锁不到:数据不存在于数据库中,select都select不到,更别说for update了。
1. “决策”,根据“判”出来的不同场景做不同的决策。根据实际需要或返回失败,或包装业务异常抛出去,或返回获取的业务资源,等等。
现在再看最初的代码有没有什么问题?我直接一点说结果,就不卖关子了。
· 问题1:没有判“锁失败”的场景。
· 问题2:对于“锁不到”的场景处理,是错误的。
为什么说没有判“锁失败”的场景?
因为这段代码根本没有搞清楚“锁失败”会发生什么,所以导致了在“锁不到”的场景中,做了“锁失败”的决策。——以为invoice == null就是“锁失败”,其实这是“锁不到”。
来看看真正锁失败的时候是什么样子:
对于“锁失败”真正的表现是会抛出CannotAcquireLockException(Springframework),无论是尝试拿锁(select for update)失败,还是等待锁超时(select for updatenowait/wait xxx)失败,都会返回这个异常。
我们来看看Spring怎么解释这个异常的:
/** * Exception thrown on failure to aquire a lock during an update, * for example during a "select for update" statement. * * @author Rod Johnson */public class CannotAcquireLockException extends PessimisticLockingFailureException {/** * Constructor for CannotAcquireLockException. * @param msg the detail message */public CannotAcquireLockException(String msg) {super(msg);}/** * Constructor for CannotAcquireLockException. * @param msg the detail message * @param cause the root cause from the data access API in use */public CannotAcquireLockException(String msg, Throwable cause) {super(msg, cause);}}
“当拿锁(selectfor update)失败时抛出此异常”——简单明了。
我们注意到CannotAcquireLockException继承自PessimisticLockingFailureException,中文名叫:“悲观锁失败异常”,好像很屌的样子,为啥同样是悲观锁失败,抛CannotAcquireLockException而不是PessimisticLockingFailureException呢?
来看一下代码:
/** * Exception thrown on a pessimistic locking violation. * Thrown by Spring's SQLException translation mechanism * if a corresponding database error is encountered. * * <p>Serves as superclass for more specific exceptions, like * CannotAcquireLockException and DeadlockLoserDataAccessException. * * @author Thomas Risberg * @since 1.2 * @see CannotAcquireLockException * @see DeadlockLoserDataAccessException * @see OptimisticLockingFailureException */public class PessimisticLockingFailureException extends ConcurrencyFailureException {/** * Constructor for PessimisticLockingFailureException. * @param msg the detail message */public PessimisticLockingFailureException(String msg) {super(msg);}/** * Constructor for PessimisticLockingFailureException. * @param msg the detail message * @param cause the root cause from the data access API in use */public PessimisticLockingFailureException(String msg, Throwable cause) {super(msg, cause);}}
代码里发现PessimisticLockingFailureException有三个子类:
· CannotAcquireLockException
· CannotSerializeTransactionException
· DeadlockLoserDataAccessException
CannotSerializeTransactionException:这种是在串行(serialized)的事务隔离级别中,由于update竞争失败抛出来的异常(Exception thrown on failure to complete a transaction in serializedmode due to update conflicts)
DeadlockLoserDataAccessException:这种是在当前线程由于死锁失败,且事务已经被回滚的情况下抛出来的异常(Generic exception thrown when the current process was a deadlockloser, and its transaction rolled back)
所以总结下来“悲观锁失败异常”之下还有三个子类,他们分别代表着在不同的场景下悲观锁失败的异常。显然CannotAcquireLockException更加常见且通用;因为CannotSerializeTransactionException要求数据库的隔离级别要在“串行”(serialized),这种隔离级别是数据库的最高事务隔离级别,以牺牲性能为代价完全避免了“脏读”、“幻读”和“不可重复读”,由于代价太高,实际应用场景中几乎不会选择这种。而一般较为常用的隔离级别仅仅是“读提交”(read committed),也就是我们现在生产库中的事务隔离级别。
有点扯远了,回到原来的那段代码,看看如何完善:
/** * 锁定发票放到上下文中 * *a @param context AP发票处理上下文 */ protected void lockInvoiceAndSet(ApInvoiceOperateContext context) { ApInvoice invoice = null; try { invoice = apInvoiceRepository.lockByInvoiceId(context.getInvoiceId()); } catch (CannotAcquireLockException e) { LoggerUtil.warn(logger, "锁定发票失败[并发锁失败]"); // do something else } if (invoice == null) { LoggerUtil.warn(logger, "锁定发票失败[根据发票ID找不到发票], 发票id:", context.getInvoiceId()); throw new GFCenterException(GFCenterErrorCodeEnum.AP_INVOICE_LOCK_ERROR); } context.setOrigInvoice(invoice); }
以上代码修复了几个问题:
· 识别并处理“锁失败”
· 识别并正确处理“锁不到”
· 使用合理的日志级别,并修改了日记记录信息,表达更明确。
另外,我写完这篇总结,这里就可以大大方方的catch对应的异常,不用这么hack了:
- 全局锁和悲观锁的异常处理
- springMVC全局的异常处理
- Springmvc的全局异常处理
- android 处理程序全局异常和错误
- android 处理程序全局异常和错误
- 悲观和乐观锁
- 乐观锁和悲观锁 对多并发处理
- Oracle的悲观锁和乐观锁
- 悲观锁和乐观锁的实现
- Oracle的悲观锁和乐观锁
- Hibernate的悲观锁和乐观锁
- hibernate的 悲观锁和乐观锁
- Oracle的悲观锁和乐观锁
- Oracle的悲观锁和乐观锁
- Hibernate的悲观锁和乐观锁
- hibernate的悲观锁和乐观锁
- oracle的乐观锁和悲观锁
- Oracle的悲观锁和乐观锁
- 如何让AI成为梵高?
- TestNG的Eclipse插件概述
- 有道云笔记同步出现红色叹号的解决办法(以win10为例)
- 南阳OJ 题目111:分数加减法
- 废话记
- 全局锁和悲观锁的异常处理
- 欢迎使用CSDN-markdown编辑器
- java排序
- spring-mvc中的将data转换为json
- #770 –LayoutTransform和RenderTransform属性的区别(Difference Between LayoutTransform and RenderTransform)
- 树-堆结构练习——合并果子之哈夫曼树
- 集合---List
- canvas画柱状图及createLinearGradient颜色渐变的效果
- MongoDB:副本集的配置