我有一个Seam web应用程序与Seam &Hibernate (JDBC到SQLServer).
它工作得很好,但在重负载下(使用JMeter进行压力测试),我有一些LockAcquisitionException
或OptimisticLockException
。
LockAquisitionException
是由SQLServerException
"事务(进程ID 64)在与另一个进程的锁资源上死锁并被选为死锁受害者"引起的。重新运行事务".
我已经写了一个Seam Interceptor来为LockAquisitionException
重新运行这样的事务:
@AroundInvoke
public Object aroundInvoke(final InvocationContext invocationContext) throws Exception {
if (instanceThreadLocal.get() == null && isMethodInterceptable(invocationContext)) {
try {
instanceThreadLocal.set(this);
int i = 0;
PersistenceException exception = null;
do {
try {
return invocationContext.proceed();
} catch (final PersistenceException e) {
final Throwable cause = e.getCause();
if (!(cause instanceof LockAcquisitionException)) {
throw e;
}
exception = e;
i++;
if (i < MAX_RETRIES_LOCK_ACQUISITION) {
log.info("Swallowing a LockAcquisitionException - #0/#1", i, MAX_RETRIES_LOCK_ACQUISITION);
try {
if (Transaction.instance().isRolledBackOrMarkedRollback()) {
Transaction.instance().rollback();
}
Transaction.instance().begin();
} catch (final Exception e2) {
throw new IllegalStateException("Exception while rollback the current transaction, and begining a new one.", e2);
}
Thread.sleep(1000);
} else {
log.info("Can't swallow any more LockAcquisitionException (#0/#1), will throw it.", i, MAX_RETRIES_LOCK_ACQUISITION);
throw e;
}
}
} while (i < MAX_RETRIES_LOCK_ACQUISITION);
throw exception;
} finally {
instanceThreadLocal.remove();
}
}
return invocationContext.proceed();
}
第一个问题:你认为这个拦截器会正确地完成这项工作吗?
通过谷歌搜索,看到Alfresco(这里有一个论坛讨论),Bonita和Orchestra也有一些方法来重新运行这样的事务,并且它们捕获了更多的异常,例如StaleObjectStateException
(我的OptimisticLockException
的原因)。
我的第二个问题如下:对于StaleObjectStateException
("行被另一个事务更新或删除(或未保存的值映射不正确)"),通常你不能只是重新运行事务,因为它是与数据库和@Version
字段同步的问题,不是吗?为什么Alfresco会尝试重新运行由这些异常引起的事务?
编辑:对于SQLServerException
引起的LockAcquisitionException
,我在网上看了一些资源,即使我应该仔细检查我的代码,似乎它无论如何都可能发生……以下是链接:
- 一篇关于该主题的文章(附带评论说它也可能在资源耗尽时发生)
- 另一篇带有子链接的文章:
- 微软正在support.microsoft.com上讨论这个
- 一种配置事务的方法
- 以及一些减少此类问题的建议
甚至微软也说"尽管死锁可以最小化,但它们不能完全避免。这就是前端应用程序应该设计为处理死锁的原因。"
实际上我终于找到了如何避开著名的"事务(进程ID 64)在锁定资源上与另一个进程死锁,并被选为死锁受害者。重新运行事务"。
所以我不会真正回答我的问题,但我会解释我所看到的以及我是如何做到的。
起初,我认为我有一个"锁升级问题",它会将我的行锁转换为页锁并产生死锁(我的JMeter测试运行在一个场景中,在选择行时执行删除/更新,但删除和更新不一定与选择的行相同)。
所以我阅读了SQL2005中的锁升级和如何解决SQL Server中锁升级引起的阻塞问题(由MS),最后使用sp_lock诊断SQL Server性能问题。
但是在尝试检测我是否处于锁升级情况之前,我落在了那个页面:http://community.jboss.org/message/95300。它讨论了"事务隔离",并且SQLServer有一个特殊的"快照隔离"。
然后我发现使用快照隔离与SQL Server和Hibernate和读取使用快照隔离(由MS)。
所以我首先在我的数据库上启用了"快照隔离模式":
ALTER DATABASE [MY_DATABASE]
SET ALLOW_SNAPSHOT_ISOLATION ON
ALTER DATABASE [MY_DATABASE]
SET READ_COMMITTED_SNAPSHOT ON
然后我必须为4096
定义JDBC驱动程序的事务隔离…通过阅读"Hibernate in Action"一书中的"5.1.6设置隔离级别",它是:
请注意,Hibernate从不更改从托管环境中的应用程序服务器提供的数据源获得的连接的隔离级别。可以使用应用程序服务器的配置更改默认隔离。
所以我阅读了配置JDBC数据源(用于JBoss 4),最后编辑了我的database-ds.xml
文件,添加了以下内容:
<local-tx-datasource>
<jndi-name>myDatasource</jndi-name>
<connection-url>jdbc:sqlserver://BDDSERVERSQL2008;databaseName=DATABASE</connection-url>
<driver-class>com.microsoft.sqlserver.jdbc.SQLServerDriver</driver-class>
<user-name>user</user-name>
<password>password</password>
<min-pool-size>2</min-pool-size>
<max-pool-size>400</max-pool-size>
<blocking-timeout-millis>60000</blocking-timeout-millis>
<background-validation>true</background-validation>
<background-validation-minutes>2</background-validation-minutes>
<idle-timeout-minutes>15</idle-timeout-minutes>
<check-valid-connection-sql>SELECT 1</check-valid-connection-sql>
<prefill>true</prefill>
<prepared-statement-cache-size>75</prepared-statement-cache-size>
<transaction-isolation>4096</transaction-isolation>
</local-tx-datasource>
最重要的部分当然是 <transaction-isolation>4096</transaction-isolation>
然后,我再也没有死锁问题了!…所以我的问题现在对我来说或多或少没用了…但也许有人会有一个真正的答案!