我们可以通过哪些方式减少死锁的可能性?

时间:2018-05-05 05:35:51

标签: java spring jpa

我们的应用程序面临死锁情况

Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Transaction (Process ID 62) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
    at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:197) ~[sqljdbc4.jar:?]
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1493) ~[sqljdbc4.jar:?]
    at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(SQLServerPreparedStatement.java:390) ~[sqljdbc4.jar:?]
    at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(SQLServerPreparedStatement.java:340) ~[sqljdbc4.jar:?]
    at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:4575) ~[sqljdbc4.jar:?]
    at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:1400) ~[sqljdbc4.jar:?]
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeCommand(SQLServerStatement.java:179) ~[sqljdbc4.jar:?]
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeStatement(SQLServerStatement.java:154) ~[sqljdbc4.jar:?]
    at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.executeUpdate(SQLServerPreparedStatement.java:308) ~[sqljdbc4.jar:?]
    at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105) ~[commons-dbcp-1.4.jar:1.4]
    at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105) ~[commons-dbcp-1.4.jar:1.4]
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3069) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2948) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3328) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:145) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:447) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:333) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:335) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1224) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:464) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2894) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2270) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:146) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:230) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65) ~[hibernate-core-5.0.2.Final.jar:5.0.2.Final]
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61) ~[hibernate-entitymanager-5.0.2.Final.jar:5.0.2.Final]
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517) ~[spring-orm-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    ... 52 more

我们正在使用Spring JPA事务进行提交。僵局不会一直发生。当这么多请求来自Web服务请求以及同时在GUI中执行其他一些活动时,就会发生这种情况。因此,有可能同时从GUI和Web服务访问相同的表。 分析日志文件时,我们也无法获取有关哪个表被锁定的信息。对此提出任何建议。另请告诉我们为减少死锁可能性应该考虑哪些事项?

1 个答案:

答案 0 :(得分:0)

一般的解决方案是对表和表内使用严格的锁定顺序。即如果要更新表A,B和C,首先锁定A然后锁定B然后锁定C,如果要更新A中的两个记录,请先锁定具有最低主键值的记录。我见过以这种方式设计的应用程序,但它非常昂贵,而且几乎从未在实践中完成。更实用的方法是使用警卫。

例如,假设用户界面和Web服务与客户和订单一起使用。您可以决定将客户实体变为警卫。要更新订单,首先必须锁定订单的客户。对客户的地址,订单行等使用相同的保护意味着只有一个实体必须为多个用例锁定。

然后,受影响的用例将首先识别他们的警卫(在这种情况下为客户)并对其进行悲观锁定(https://docs.oracle.com/javaee/7/tutorial/persistence-locking002.htm)。然后他们可以继续进行更改代码。当每个人开始尝试获得相同的锁定时,一些事务将不得不等待,但是当他们按顺序锁定警卫时不存在死锁。

性能可能会受到影响,因为这会降低并发性,但前提是您可以找到一个好的防护,它应该摆脱死锁。

另一个选择是捕获错误并在可能的情况下重试事务。

编辑:

如果你可以跟踪,你可以从SQL Server(https://docs.microsoft.com/en-us/sql/tools/sql-server-profiler/analyze-deadlocks-with-sql-server-profiler?view=sql-server-2017)获得死锁图,但只是查看代码,你应该看到你正在更新的表。

相关问题