lealone / Lealone

比 MySQL 和 MongoDB 快10倍的 OLTP 关系数据库和文档数据库
Other
2.47k stars 518 forks source link

使用二次索引时造成 java.nio.BufferOverflowException #56

Closed cxwxz closed 9 years ago

cxwxz commented 11 years ago

使用多个实例时行并发测试会出现 java.nio.BufferOverflowException

根据提示应该是 二级 索引时引起的。

org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [INSERT INTO HBASE_USER (ID,NAME,ORG,DISTRICT) VALUES (?,?,?,?)]; SQL state [HY000]; error code [50000]; General error: "java.nio.BufferOverflowException"; SQL statement: INSERT INTO HBASE_USER (ID,NAME,ORG,DISTRICT) VALUES (?,?,?,?) [50000-171]; nested exception is com.codefollower.lealone.jdbc.JdbcSQLException: General error: "java.nio.BufferOverflowException"; SQL statement: INSERT INTO HBASE_USER (ID,NAME,ORG,DISTRICT) VALUES (?,?,?,?) [50000-171] at com.codefollower.lealone.message.DbException.getJdbcSQLException(DbException.java:330) at com.codefollower.lealone.message.DbException.get(DbException.java:159) at com.codefollower.lealone.message.DbException.convert(DbException.java:282) at com.codefollower.lealone.hbase.command.CommandParallel.throwException(CommandParallel.java:193) at com.codefollower.lealone.hbase.command.CommandParallel.execute(CommandParallel.java:186) at com.codefollower.lealone.hbase.dbobject.table.HBaseTable.addRow(HBaseTable.java:412) at com.codefollower.lealone.command.dml.Insert.insertRows(Insert.java:116) at com.codefollower.lealone.command.dml.Insert.update(Insert.java:85) at com.codefollower.lealone.hbase.command.dml.HBaseInsert.internalUpdate(HBaseInsert.java:70) at com.codefollower.lealone.hbase.command.dml.InsertOrMergeSupport.update(InsertOrMergeSupport.java:123) at com.codefollower.lealone.hbase.command.dml.HBaseInsert.update(HBaseInsert.java:55) at com.codefollower.lealone.hbase.command.RetryableCommand.execute(RetryableCommand.java:44) at com.codefollower.lealone.hbase.command.RetryableCommand.updateInternal(RetryableCommand.java:30) at com.codefollower.lealone.command.CommandContainer.update(CommandContainer.java:76) at com.codefollower.lealone.command.Command.executeUpdate(Command.java:235) at com.codefollower.lealone.command.BackendBatchCommand.executeUpdate(BackendBatchCommand.java:91) at com.codefollower.lealone.server.TcpServerThread.executeBatch(TcpServerThread.java:264) at com.codefollower.lealone.server.TcpServerThread.process(TcpServerThread.java:473) at com.codefollower.lealone.server.TcpServerThread.run(TcpServerThread.java:136) at java.lang.Thread.run(Thread.java:662) Caused by: java.nio.BufferOverflowException at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:165) at java.nio.ByteBuffer.put(ByteBuffer.java:813) at com.codefollower.lealone.hbase.dbobject.index.HBaseSecondaryIndex.encode(HBaseSecondaryIndex.java:240) at com.codefollower.lealone.hbase.dbobject.index.HBaseSecondaryIndex.getKey(HBaseSecondaryIndex.java:180) at com.codefollower.lealone.hbase.dbobject.index.HBaseSecondaryIndex.getKey(HBaseSecondaryIndex.java:163) at com.codefollower.lealone.hbase.dbobject.index.HBaseSecondaryIndex.add(HBaseSecondaryIndex.java:128) at com.codefollower.lealone.hbase.dbobject.table.HBaseTable$1.call(HBaseTable.java:405) at com.codefollower.lealone.hbase.dbobject.table.HBaseTable$1.call(HBaseTable.java:403) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918) ... 1 more

at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:83)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:602)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:614)
at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:883)
at org.springframework.jdbc.core.namedparam.NamedParameterBatchUpdateUtils.executeBatchUpdateWithNamedParameters(NamedParameterBatchUpdateUtils.java:24)
at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.batchUpdate(NamedParameterJdbcTemplate.java:302)
at com.lealone.druid.demo.dao.impl.UserDaoImpl.batchSave(UserDaoImpl.java:64)
at com.lealone.druid.demo.service.impl.UserServiceImpl.batchSave(UserServiceImpl.java:36)
at sun.reflect.GeneratedMethodAccessor11.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy12.batchSave(Unknown Source)
at com.lealone.druid.demo.service.UserServiceImplTest.testBatchSave(UserServiceImplTest.java:61)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

Caused by: com.codefollower.lealone.jdbc.JdbcSQLException: General error: "java.nio.BufferOverflowException"; SQL statement: INSERT INTO HBASE_USER (ID,NAME,ORG,DISTRICT) VALUES (?,?,?,?) [50000-171] at com.codefollower.lealone.message.DbException.getJdbcSQLException(DbException.java:330) at com.codefollower.lealone.message.DbException.get(DbException.java:159) at com.codefollower.lealone.message.DbException.convert(DbException.java:282) at com.codefollower.lealone.hbase.command.CommandParallel.throwException(CommandParallel.java:193) at com.codefollower.lealone.hbase.command.CommandParallel.execute(CommandParallel.java:186) at com.codefollower.lealone.hbase.dbobject.table.HBaseTable.addRow(HBaseTable.java:412) at com.codefollower.lealone.command.dml.Insert.insertRows(Insert.java:116) at com.codefollower.lealone.command.dml.Insert.update(Insert.java:85) at com.codefollower.lealone.hbase.command.dml.HBaseInsert.internalUpdate(HBaseInsert.java:70) at com.codefollower.lealone.hbase.command.dml.InsertOrMergeSupport.update(InsertOrMergeSupport.java:123) at com.codefollower.lealone.hbase.command.dml.HBaseInsert.update(HBaseInsert.java:55) at com.codefollower.lealone.hbase.command.RetryableCommand.execute(RetryableCommand.java:44) at com.codefollower.lealone.hbase.command.RetryableCommand.updateInternal(RetryableCommand.java:30) at com.codefollower.lealone.command.CommandContainer.update(CommandContainer.java:76) at com.codefollower.lealone.command.Command.executeUpdate(Command.java:235) at com.codefollower.lealone.command.BackendBatchCommand.executeUpdate(BackendBatchCommand.java:91) at com.codefollower.lealone.server.TcpServerThread.executeBatch(TcpServerThread.java:264) at com.codefollower.lealone.server.TcpServerThread.process(TcpServerThread.java:473) at com.codefollower.lealone.server.TcpServerThread.run(TcpServerThread.java:136) at java.lang.Thread.run(Thread.java:662) Caused by: java.nio.BufferOverflowException at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:165) at java.nio.ByteBuffer.put(ByteBuffer.java:813) at com.codefollower.lealone.hbase.dbobject.index.HBaseSecondaryIndex.encode(HBaseSecondaryIndex.java:240) at com.codefollower.lealone.hbase.dbobject.index.HBaseSecondaryIndex.getKey(HBaseSecondaryIndex.java:180) at com.codefollower.lealone.hbase.dbobject.index.HBaseSecondaryIndex.getKey(HBaseSecondaryIndex.java:163) at com.codefollower.lealone.hbase.dbobject.index.HBaseSecondaryIndex.add(HBaseSecondaryIndex.java:128) at com.codefollower.lealone.hbase.dbobject.table.HBaseTable$1.call(HBaseTable.java:405) at com.codefollower.lealone.hbase.dbobject.table.HBaseTable$1.call(HBaseTable.java:403) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918) ... 1 more

at com.codefollower.lealone.engine.SessionRemote.parseError(SessionRemote.java:607)
at com.codefollower.lealone.engine.SessionRemote.done(SessionRemote.java:589)
at com.codefollower.lealone.command.FrontendBatchCommand.executeUpdate(FrontendBatchCommand.java:110)
at com.codefollower.lealone.jdbc.JdbcPreparedStatement.executeBatch(JdbcPreparedStatement.java:1102)
at org.apache.commons.dbcp.DelegatingStatement.executeBatch(DelegatingStatement.java:297)
at org.springframework.jdbc.core.JdbcTemplate$4.doInPreparedStatement(JdbcTemplate.java:898)
at org.springframework.jdbc.core.JdbcTemplate$4.doInPreparedStatement(JdbcTemplate.java:1)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:586)
... 48 more
codefollower commented 11 years ago

目前内部的ByteBuffer的容量是256字节,所以如果索引字段值和主表key的总长度大于256时会出错, 所以目前的设计太死板了,需要调整一下。

cxwxz commented 11 years ago

目前测试的结果好像不是这个原因,只要是多线程并发就会有出现这个 java.nio.BufferOverflowException 的 异常。 线程越多出现的时间越早。 应该,在线程的控制有上 BUG 引起的,我用单线程跑相同的代码,都不会出现这类异常。

cxwxzhtc

发件人: codefollower 发送时间: 2013-09-05 23:47 收件人: codefollower/Lealone 抄送: cxwxz 主题: Re: [Lealone] 使用二次索引时造成 java.nio.BufferOverflowException (#56) 目前内部的ByteBuffer的容量是256字节,所以如果索引字段值和主表key的总长度大于256时会出错, 所以目前的设计太死板了,需要调整一下。 — Reply to this email directly or view it on GitHub.

codefollower commented 11 years ago

是的,HBaseSecondaryIndex里的buffer字段在多线程时会存在并发修改问题, 再加上长度是256,所以就出现了BufferOverflowException。

这是java.nio.HeapByteBuffer类的源代码:

    public ByteBuffer put(byte[] src, int offset, int length) {

    checkBounds(offset, length, src.length);
    if (length > remaining())
        throw new BufferOverflowException();
    System.arraycopy(src, offset, hb, ix(position()), length);
    position(position() + length);
    return this;
    }

所以这个问题是只要索引字段值和主表key的总长度太长或多个线程并发写都存在问题。 我正在解决这个问题,谢谢你的反馈 :)

freemanhjr commented 11 years ago

这种索引字段值和主表key构成二级索引rowkey的方式能保证二级索引的排序吗? 如果是这样,为什么不考虑在多列做复合键时,用这种方式,而要用UUID呢?

codefollower commented 11 years ago

索引表的rowKey和主表的rowKey是不一样的,索引表的rowKey=索引字段值+主表rowKey,肯定能保证顺序。

uuid的主要动机是在表没有primary key时使用, 如果primary key是复合键的情况,将它们组合起来做为rowKey其实意义不大, 比如,如果rowKey是(a、b、c)三个字段组合而成,如果按b或c来查,这样的rowKey没任何意义, 当然,也可以用复合键,不过目前还没有计划很急迫实现这个功能。

freemanhjr commented 11 years ago

如果索引字段值是不定长的字符串,用索引字段值+主表rowkey还能保真顺序吗?