Как мы можем уменьшить возможности тупиковой ситуации - PullRequest
0 голосов
/ 05 мая 2018

Наше приложение сталкивается с тупиковыми ситуациями

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 для фиксации. Тупик не бывает всегда. Это происходит, когда из запроса веб-службы поступает так много запросов и одновременно выполняется несколько других действий в GUI. Таким образом, существует возможность доступа к одним и тем же таблицам, одновременно используя графический интерфейс и веб-сервис. Анализируя файлы журналов, мы также не получаем информацию о том, какая таблица заблокирована. Любые предложения по этому вопросу. Также, пожалуйста, дайте нам знать, что мы должны учитывать, чтобы уменьшить вероятность тупиковой ситуации?

1 Ответ

0 голосов
/ 05 мая 2018

Общее решение заключается в использовании строгого порядка блокировки как для таблиц, так и для таблиц. То есть если вы собираетесь обновить таблицы A, B и C, сначала заблокируйте A, затем заблокируйте B, затем заблокируйте C, и если вы собираетесь обновить две записи в A, сначала заблокируйте запись с наименьшим значением первичного ключа. Я видел приложения, разработанные таким образом, но это очень дорого и практически никогда не делалось на практике. Более прагматичный подход - использовать охранников.

Например, предположим, что пользовательский интерфейс и веб-службы работают с клиентами и заказами. Вы можете решить превратить клиента в охранника. Чтобы обновить заказ, сначала нужно заблокировать клиента заказа. Использование одного и того же предохранителя для адреса клиента, строк заказа и т. Д. Означает, что для нескольких вариантов использования должен быть заблокирован только один объект.

Затем эти варианты использования сначала идентифицируют свою защиту (в данном случае клиента) и получают пессимистическую блокировку (https://docs.oracle.com/javaee/7/tutorial/persistence-locking002.htm)). Затем они могут продолжить работу без дальнейших изменений кода. Когда все начнут пытаться получить та же самая блокировка, некоторые транзакции должны будут ждать, но не будет никаких взаимоблокировок, поскольку они блокируют охрану по порядку.

Производительность может быть затронута, поскольку это может уменьшить параллелизм, но при условии, что вы можете определить хорошего защитника, он должен избавиться от тупиков.

Другой вариант - перехватить ошибку и повторить транзакцию, если это возможно.

EDIT:

Если вы можете отследить, вы можете получить график взаимоблокировки с SQL Server (https://docs.microsoft.com/en-us/sql/tools/sql-server-profiler/analyze-deadlocks-with-sql-server-profiler?view=sql-server-2017),, но, просто взглянув на код, вы увидите, какие таблицы вы обновляете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...