我有一个使用Seam& Sam的Seam Web应用程序。 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();
}
第一个问题:你觉得这个拦截器会正确地完成这项工作吗?
通过Google搜索并看到Alfresco(with a forum talk here),Bonita和Orchestra有一些方法可以重新运行此类交易,并且它们正在捕获更多例外情况,比如StaleObjectStateException
(我的OptimisticLockException
的原因)。
我的第二个问题如下:对于StaleObjectStateException
(“行已被另一个事务更新或删除(或未保存的值映射不正确)”),通常你不能只是重新运行该事务,因为这是与数据库同步的问题,并且@Version
字段不是吗?例如,为什么Alfresco会尝试重新运行由此类例外引起的此类交易?
编辑:
对于由LockAcquisitionException
引起的SQLServerException
,我查看了网络上的一些资源,即使我应该仔细检查我的代码,它似乎仍然可以发生...这里是链接:
即使微软说“虽然死锁可以被最小化,但是它们无法完全避免。这就是前端应用程序应该被设计来处理死锁的原因。”
答案 0 :(得分:2)
实际上我终于找到了如何躲避着名的“事务(进程ID 64)与其他进程锁定资源上的死锁并被选为死锁受害者。重新运行事务”。
所以我不会真正回答我的问题,但我会解释我所看到的以及我是如何做到的。
起初,我认为我有一个“锁定升级问题”,它会将我的行锁转换为页锁并产生我的死锁(我的JMeter测试在选择行时执行删除/更新的情况下运行,但是删除和更新不一定与选择相同的行。
所以我读了Lock Escalation in SQL2005和How to resolve blocking problems that are caused by lock escalation in SQL Server(由MS),最后是Diagnose SQL Server performance issues using sp_lock。
但在尝试检测我是否处于锁定升级情况之前,我会查看该页面:http://community.jboss.org/message/95300。它讨论“事务隔离”,并且SQLServer有一个特殊的称为“快照隔离”。
然后我找到了Using Snapshot Isolation with SQL Server and Hibernate并阅读了Using Snapshot Isolation(通过MS)。
所以我首先在我的数据库上启用了“快照隔离模式”:
ALTER DATABASE [MY_DATABASE]
SET ALLOW_SNAPSHOT_ISOLATION ON
ALTER DATABASE [MY_DATABASE]
SET READ_COMMITTED_SNAPSHOT ON
然后我必须将JDBC驱动程序的事务隔离定义为4096
...并通过阅读“5.1.6设置隔离级别”一节中的“Hibernate in Action”一书,其中显示:
请注意,Hibernate永远不会更改从托管环境中的应用程序服务器提供的数据源获取的连接的隔离级别。 您可以使用应用程序服务器的配置更改默认隔离。
所以我阅读Configuring JDBC DataSources (for JBoss 4)并最终编辑了我的database-ds.xml
文件以添加此内容:
<local-tx-datasource>
<jndi-name>myDatasource</jndi-name>
<connection-url>jdbc:sqlserver://BDDSERVER\SQL2008;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>
。
然后,我不再有死锁问题了! ...所以我的问题现在对我来说或多或少都没用......但也许有人可以得到真正的答案!