使用 Seam & 重试事务的最佳方式休眠

发布于 2024-11-19 14:45:19 字数 4558 浏览 2 评论 0 原文

我有一个与 Seam & 一起使用的 Seam Web 应用程序。 Hibernate(从 JDBC 到 SQLServer)。

它运行良好,但在重负载下(使用 JMeter 进行压力测试),我遇到了一些 LockAcquisitionExceptionOptimisticLockException

LockAquisitionException 是由 SQLServerException “事务(进程 ID 64)在锁资源上与另一个进程发生死锁,并已被选为死锁受害者。重新运行事务

然后,我编写了一个 Seam 拦截器来重新运行 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();
}

第一个问题:您认为这个拦截器会正确完成这项工作吗?

通过谷歌搜索并看到 <一个href="http://www.koders.com/java/fid249E28267D98E75C62D00C627BCB92D6FD888F82.aspx?s=hibernate#L43" rel="nofollow">户外 (此处有论坛讨论),博尼塔Orchestra 也有一些方法来重新运行此类事务,并且它们捕获更多的异常,例如 StaleObjectStateException例如(我的 OptimisticLockException 的原因)。

我的第二个问题如下:对于 StaleObjectStateException“行已被另一个事务更新或删除(或未保存的值映射不正确)”),通常你不能只是重新运行事务,因为这是与数据库和 @Version 字段同步的问题,不是吗?例如,为什么 Alfresco 会尝试重新运行由此类异常引起的此类事务?

编辑 : 对于由SQLServerException引起的LockAcquisitionException,我查看了网络上的一些资源,即使我应该仔细检查我的代码,似乎它仍然可能发生。 ..以下是链接:

甚至微软也表示“尽管死锁可能是最小化,它们无法完全避免,这就是为什么前端应用程序应该设计为处理死锁。”

I've got a Seam web application working with Seam & Hibernate (JDBC to SQLServer).

It's working well, but under heavy load (stress test with JMeter), I have some LockAcquisitionException or OptimisticLockException.

The LockAquisitionException is caused by a SQLServerException "Transaction (Process ID 64) was deadlock on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction".

I've then written a Seam Interceptor to rerun such transactions for 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();
}

First question : do you think this interceptor will correctly do the job ?

By googling around and saw that Alfresco (with a forum talk here), Bonita and Orchestra have some methods to rerun such transactions too, and they are catching much more Exceptions, like StaleObjectStateException for instance (the cause of my OptimisticLockException).

My 2nd question follows : for the StaleObjectStateException ("Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)"), normaly you can't just rerun the transaction, as it's a problem of synchronisation with the database and @Version fields isn't it ? Why Alfresco for instance tries to rerun such Transactions caused by such Exceptions ?

EDIT :
For LockAcquisitionException caused by SQLServerException, I've looked at some some resources on the web, and even if I should double check my code, it seems that it can happend anyway ... here are the links :

Even Microsoft says "Although deadlocks can be minimized, they cannot be completely avoided. That is why the front-end application should be designed to handle deadlocks."

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

中性美 2024-11-26 14:45:19

实际上,我终于找到了如何避免著名的“事务(进程 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

然后我必须将 JDBC 驱动程序的事务隔离定义为 4096 ...并阅读“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://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>

当然,最重要的部分是<事务隔离>4096

然后,我就不再遇到僵局问题了! ...所以我的问题现在对我来说或多或少毫无用处...但也许有人可以有真正的答案!

Actually I finally found how to dodge the famous "Transaction (Process ID 64) was deadlock on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction".

So I will not really answer my question but I will explain what I saw and how I manage to do that.

At first, I thought that I had a "lock escalation problem" which would transform my row locks into page locks and produce my deadlocks (my JMeter test runs on a scenario which does delete / update while selecting rows, but the deletes and updates don't concern necessarily the same rows as the selects).

So I read Lock Escalation in SQL2005 and How to resolve blocking problems that are caused by lock escalation in SQL Server (by MS) and finally Diagnose SQL Server performance issues using sp_lock.

But before trying to detect if I was in a lock escalation situation, I fall on that page : http://community.jboss.org/message/95300. It talks about "transaction isolation" and that SQLServer has a special one which is called "snapshot isolation".

I then found Using Snapshot Isolation with SQL Server and Hibernate and read Using Snapshot Isolation (by MS).

So I first enabled the "snapshot isolation mode" on my database :

ALTER DATABASE [MY_DATABASE]
SET ALLOW_SNAPSHOT_ISOLATION ON

ALTER DATABASE [MY_DATABASE]
SET READ_COMMITTED_SNAPSHOT ON

Then I had to define transaction isolation for JDBC driver to 4096 ... and by reading the book "Hibernate in Action" on paragraph "5.1.6 Setting an isolation level", it reads :

Note that Hibernate never changes the isolation level of connections obtained from a datasource provided by the application server in a managed environment. You may change the default isolation using the configuration of your application server.

So I read Configuring JDBC DataSources (for JBoss 4) and finally edited my database-ds.xml file to add this :

<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>

The most important part is of course <transaction-isolation>4096</transaction-isolation>.

And then, I got no more deadlock problem anymore ! ... so my question is now more or less useless for me ... but perhaps someone could have a real answer !

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文