嵌入式模式下的 h2 并发更新
Posted
技术标签:
【中文标题】嵌入式模式下的 h2 并发更新【英文标题】:h2 concurrent update in embedded mode 【发布时间】:2017-07-12 15:06:21 【问题描述】:我正在使用 Hibernate 1.4.191 和 Hibernate 4.3.1。我的 h2 连接 url 是 dbc:h2:file:./h2/myDB
- 这是一个未启用 MULTI_THREADED
的嵌入式数据库。我正在运行 MVCC - 因为这是 1.4.191 的默认设置。
在我的应用程序中使用此配置,我在尝试执行更新 HQL 语句时得到以下堆栈跟踪:
2017-07-11 19:38:48 SEVERE Could not set rounding style. org.hibernate.PessimisticLockException: could not execute statement
at org.hibernate.dialect.H2Dialect$2.convert(H2Dialect.java:342)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:126)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:112)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:190)
at org.hibernate.hql.internal.ast.exec.BasicExecutor.doExecute(BasicExecutor.java:109)
at org.hibernate.hql.internal.ast.exec.BasicExecutor.execute(BasicExecutor.java:78)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.executeUpdate(QueryTranslatorImpl.java:445)
at org.hibernate.engine.query.spi.HQLQueryPlan.performExecuteUpdate(HQLQueryPlan.java:347)
at org.hibernate.internal.SessionImpl.executeUpdate(SessionImpl.java:1282)
at org.hibernate.internal.QueryImpl.executeUpdate(QueryImpl.java:118)
at de.oktopos.dataBase.receipts.converter.ReceiptConverter$29.runOperation(ReceiptConverter.java:1214)
at de.oktopos.dataBase.receipts.converter.ReceiptConverter$29.runOperation(ReceiptConverter.java:1210)
at de.oktopos.dataBase.tools.DatabaseOperationRunner.run(DatabaseOperationRunner.java:71)
at de.oktopos.dataBase.receipts.converter.ReceiptConverter.setRoundingStyle(ReceiptConverter.java:1226)
at de.oktopos.dataBase.receipts.converter.ReceiptCacheWriter.setRoundingStyle(ReceiptCacheWriter.java:99)
at de.oktopos.oktoDeskService.remote.OktoDeskModel.setRoundingStyle(OktoDeskModel.java:841)
at net.oktopos.cashdesk.DeskModelProcessor.setRoundingStyle(DeskModelProcessor.java:1648)
at sun.reflect.GeneratedMethodAccessor37.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
at sun.rmi.transport.Transport$1.run(Unknown Source)
at sun.rmi.transport.Transport$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: org.h2.jdbc.JdbcSQLException: Zeitüberschreitung beim Versuch die Tabelle zu sperren
Timeout trying to lock table ; SQL statement:
update Receipt set turnoverType=? [50200-191]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:168)
at org.h2.command.Command.filterConcurrentUpdate(Command.java:307)
at org.h2.command.Command.executeUpdate(Command.java:260)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:160)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:146)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:187)
... 29 more
Caused by: org.h2.jdbc.JdbcSQLException: Gleichzeitige Änderung in Tabelle "RECEIPT": eine andere Transaktion hat den gleichen Datensatz geändert oder gelöscht
Concurrent update in table "RECEIPT": another transaction has updated or deleted the same row [90131-191]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:168)
at org.h2.mvstore.db.MVTable.convertException(MVTable.java:898)
at org.h2.mvstore.db.MVSecondaryIndex.remove(MVSecondaryIndex.java:247)
at org.h2.mvstore.db.MVTable.removeRow(MVTable.java:677)
at org.h2.table.Table.updateRows(Table.java:487)
at org.h2.command.dml.Update.update(Update.java:145)
at org.h2.command.CommandContainer.update(CommandContainer.java:98)
at org.h2.command.Command.executeUpdate(Command.java:258)
... 32 more
Caused by: java.lang.IllegalStateException: Entry is locked [1.4.191/101]
at org.h2.mvstore.DataUtils.newIllegalStateException(DataUtils.java:773)
at org.h2.mvstore.db.TransactionStore$TransactionMap.set(TransactionStore.java:1031)
at org.h2.mvstore.db.TransactionStore$TransactionMap.remove(TransactionStore.java:989)
at org.h2.mvstore.db.MVSecondaryIndex.remove(MVSecondaryIndex.java:241)
... 37 more
我试图理解这是怎么可能的。堆栈跟踪似乎说锁could not be acquired because of another connection (50200)。特别是它说another connection concurrently updated the table (90131)。但是因为数据库是嵌入的,所以只能有一个连接。我在这里遗漏了什么吗?
HQL 查询基本上执行如下(减去错误处理样板,DBReceipt 映射到收据):
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
session.createQuery("UPDATE DBReceipt SET turnovertype = :turnovertype")
.setParameter("turnovertype", turnovertype)
.executeUpdate();
transaction.commit();
session.close();
当然还有许多其他数据库在不同线程的后台写入/读取,但都使用相同的连接。
调用上述部分代码时,错误不断发生。即使重新启动应用程序也无济于事 - 代码的下一次调用将导致相同的异常。只有删除数据库才有帮助。
【问题讨论】:
【参考方案1】:在我的代码库中查看了一下之后,我发现了问题的可能根源。
我正在创建一个LockRequest
并将收据锁定在收据表中。如果这个锁永远不会被释放(因为事务以无限循环结束),那么之后对收据表的任何更新都会导致上面的堆栈跟踪。
我通过删除LockRequest
解决了这个问题(我找不到无限循环的来源)。这是可能的,因为我们最近开始在应用程序级别锁定该收据,还因为在我们的应用程序中使用乐观锁定不会有任何问题。
解决此问题的另一个方法是为锁、事务或会话添加超时。但是,我的环境似乎不支持其中任何一个 - 至少它们都没有在我的测试中起作用。
【讨论】:
以上是关于嵌入式模式下的 h2 并发更新的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Gluon 在 Android 平台上将 H2 数据库连接为嵌入式模式?
如何使用 JDBC 更新/更改 H2 数据库中的用户帐户名?