Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

嵌入式场景多线程并发更新在生产环境导致数据损坏 #207

Closed
cbqqkcel opened this issue Oct 26, 2023 · 67 comments
Closed

Comments

@cbqqkcel
Copy link

2023-10-26T18:34:10.351+08:00 ERROR 16245 --- [nio-5220-exec-7] c.c.i.c.advice.GlobalResponseHandler : Throwable

java.lang.IllegalStateException: Position 0 [5.2.0/6]
at org.lealone.common.util.DataUtils.newIllegalStateException(DataUtils.java:616) ~[lealone-common-5.2.0.jar!/:na]
at org.lealone.storage.aose.btree.BTreeStorage.readPage(BTreeStorage.java:165) ~[lealone-aose-5.2.0.jar!/:na]
at org.lealone.storage.aose.btree.page.PageReference.readPage(PageReference.java:199) ~[lealone-aose-5.2.0.jar!/:na]
at org.lealone.storage.aose.btree.page.PageReference.getOrReadPage(PageReference.java:175) ~[lealone-aose-5.2.0.jar!/:na]
at org.lealone.storage.aose.btree.page.NodePage.getChildPage(NodePage.java:51) ~[lealone-aose-5.2.0.jar!/:na]
at org.lealone.storage.aose.btree.page.Page.gotoLeafPage(Page.java:267) ~[lealone-aose-5.2.0.jar!/:na]
at org.lealone.storage.aose.btree.BTreeMap.getObjects(BTreeMap.java:178) ~[lealone-aose-5.2.0.jar!/:na]
at org.lealone.transaction.aote.AOTransactionMap.getObjects(AOTransactionMap.java:563) ~[lealone-aote-5.2.0.jar!/:na]
at org.lealone.db.index.standard.StandardPrimaryIndex.getRow(StandardPrimaryIndex.java:325) ~[lealone-db-5.2.0.jar!/:na]
at org.lealone.db.table.StandardTable.getRow(StandardTable.java:230) ~[lealone-db-5.2.0.jar!/:na]
at org.lealone.db.index.standard.StandardSecondaryIndex$StandardSecondaryIndexCursor.get(StandardSecondaryIndex.java:305) ~[lealone-db-5.2.0.jar!/:na]
at org.lealone.db.index.standard.StandardSecondaryIndex$StandardSecondaryIndexCursor.get(StandardSecondaryIndex.java:299) ~[lealone-db-5.2.0.jar!/:na]

@cbqqkcel
Copy link
Author

查询数据库时报错了

@codefollower
Copy link
Member

这个是你自己打的 jar 包?

@cbqqkcel
Copy link
Author

是的

@codefollower
Copy link
Member

是基于什么时候的代码?你没有改动过吧?

@codefollower
Copy link
Member

我看这个异常,是不是通过嵌入式的方式使用数据库的?堆栈没有看到调度服务线程的信息。

@cbqqkcel
Copy link
Author

没有改过代码,是嵌入式的。

@cbqqkcel
Copy link
Author

java.lang.IllegalStateException: Position 0 [5.2.0/6]
at org.lealone.common.util.DataUtils.newIllegalStateException(DataUtils.java:616) ~[lealone-common-5.2.0.jar!/:na]
at org.lealone.storage.aose.btree.BTreeStorage.readPage(BTreeStorage.java:165) ~[lealone-aose-5.2.0.jar!/:na]
at org.lealone.storage.aose.btree.page.PageReference.readPage(PageReference.java:199) ~[lealone-aose-5.2.0.jar!/:na]
at org.lealone.storage.aose.btree.page.PageReference.getOrReadPage(PageReference.java:175) ~[lealone-aose-5.2.0.jar!/:na]
at org.lealone.storage.aose.btree.page.NodePage.getChildPage(NodePage.java:51) ~[lealone-aose-5.2.0.jar!/:na]
at org.lealone.storage.aose.btree.BTreeCursor.hasNext(BTreeCursor.java:78) ~[lealone-aose-5.2.0.jar!/:na]
at org.lealone.storage.aose.btree.BTreeCursor.next(BTreeCursor.java:56) ~[lealone-aose-5.2.0.jar!/:na]
at org.lealone.transaction.aote.AOTransactionMap$2.next(AOTransactionMap.java:290) ~[lealone-aote-5.2.0.jar!/:na]
at org.lealone.db.index.standard.StandardPrimaryIndex$StandardPrimaryIndexCursor.next(StandardPrimaryIndex.java:496) ~[lealone-db-5.2.0.jar!/:na]
at org.lealone.sql.optimizer.IndexCursor.next(IndexCursor.java:263) ~[lealone-sql-5.2.0.jar!/:na]
at org.lealone.sql.optimizer.TableFilter.next(TableFilter.java:357) ~[lealone-sql-5.2.0.jar!/:na]
at org.lealone.sql.optimizer.TableIterator.next(TableIterator.java:46) ~[lealone-sql-5.2.0.jar!/:na]
at org.lealone.sql.query.QOperator.next(QOperator.java:76) ~[lealone-sql-5.2.0.jar!/:na]
at org.lealone.sql.query.QFlat.run(QFlat.java:19) ~[lealone-sql-5.2.0.jar!/:na]
at org.lealone.sql.query.YieldableSelect.executeInternal(YieldableSelect.java:95) ~[lealone-sql-5.2.0.jar!/:na]
at org.lealone.sql.executor.YieldableBase.run(YieldableBase.java:115) ~[lealone-sql-5.2.0.jar!/:na]
at org.lealone.sql.StatementBase.syncExecute(StatementBase.java:507) ~[lealone-sql-5.2.0.jar!/:na]
at org.lealone.sql.StatementBase.executeQuery(StatementBase.java:527) ~[lealone-sql-5.2.0.jar!/:na]
at org.lealone.client.jdbc.JdbcStatement.executeQuerySQLCommand(JdbcStatement.java:376) ~[lealone-client-5.2.0.jar!/:na]
at org.lealone.client.jdbc.JdbcPreparedStatement.execute(JdbcPreparedStatement.java:308) ~[lealone-client-5.2.0.jar!/:na]
at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:65) ~[mybatis-3.5.13.jar!/:3.5.13]
at cn.com.idmy.orm.core.mybatis.FlexStatementHandler.lambda$query$2(FlexStatementHandler.java:100) ~[core-1.3.5.jar!/:1.3.5]
at cn.com.idmy.orm.core.audit.AuditManager.startAudit(AuditManager.java:104) ~[core-1.3.5.jar!/:1.3.5]
at cn.com.idmy.orm.core.mybatis.FlexStatementHandler.query(FlexStatementHandler.java:100) ~[core-1.3.5.jar!/:1.3.5]
at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:65) ~[mybatis-3.5.13.jar!/:3.5.13]
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:333) ~[mybatis-3.5.13.jar!/:3.5.13]
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:158) ~[mybatis-3.5.13.jar!/:3.5.13]
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:110) ~[mybatis-3.5.13.jar!/:3.5.13]
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:90) ~[mybatis-3.5.13.jar!/:3.5.13]
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:154) ~[mybatis-3.5.13.jar!/:3.5.13]
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147) ~[mybatis-3.5.13.jar!/:3.5.13]
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:142) ~[mybatis-3.5.13.jar!/:3.5.13]
at jdk.internal.reflect.GeneratedMethodAccessor61.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:425) ~[mybatis-spring-3.0.1.jar!/:3.0.1]
at jdk.proxy2/jdk.proxy2.$Proxy77.selectList(Unknown Source) ~[na:na]
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:224) ~[mybatis-spring-3.0.1.jar!/:3.0.1]
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:147) ~[mybatis-3.5.13.jar!/:3.5.13]
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80) ~[mybatis-3.5.13.jar!/:3.5.13]
at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:142) ~[mybatis-3.5.13.jar!/:3.5.13]
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86) ~[mybatis-3.5.13.jar!/:3.5.13]
at jdk.proxy2/jdk.proxy2.$Proxy86.selectListByQuery(Unknown Source) ~[na:na]
at cn.com.idmy.orm.core.util.MapperUtil.doPaginate(MapperUtil.java:167) ~[core-1.3.5.jar!/:1.3.5]
at cn.com.idmy.orm.core.BaseDao.paginateAs(BaseDao.java:1015) ~[core-1.3.5.jar!/:1.3.5]
at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:732) ~[na:na]
at org.apache.ibatis.binding.MapperProxy$DefaultMethodInvoker.invoke(MapperProxy.java:155) ~[mybatis-3.5.13.jar!/:3.5.13]
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86) ~[mybatis-3.5.13.jar!/:3.5.13]
at jdk.proxy2/jdk.proxy2.$Proxy86.paginateAs(Unknown Source) ~[na:na]
at cn.com.idmy.orm.core.BaseDao.paginate(BaseDao.java:939) ~[core-1.3.5.jar!/:1.3.5]
at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:732) ~[na:na]
at org.apache.ibatis.binding.MapperProxy$DefaultMethodInvoker.invoke(MapperProxy.java:155) ~[mybatis-3.5.13.jar!/:3.5.13]
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86) ~[mybatis-3.5.13.jar!/:3.5.13]
at jdk.proxy2/jdk.proxy2.$Proxy86.paginate(Unknown Source) ~[na:na]
at jdk.internal.reflect.GeneratedMethodAccessor172.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at cn.com.idmy.orm.core.mybatis.MapperInvocationHandler.invoke(MapperInvocationHandler.java:82) ~[core-1.3.5.jar!/:1.3.5]
at jdk.proxy2/jdk.proxy2.$Proxy86.paginate(Unknown Source) ~[na:na]
at cn.com.idmy.cloud.service.impl.DefaultServiceImpl.page(DefaultServiceImpl.java:224) ~[service-1.0.0.jar!/:1.0.0]
at cn.com.idmy.zhdc.pa.admin.service.impl.RoomServiceImpl.page(RoomServiceImpl.java:37) ~[classes!/:1.0.0]
at cn.com.idmy.cloud.service.ReadService.page(ReadService.java:124) ~[service-1.0.0.jar!/:1.0.0]
at jdk.internal.reflect.GeneratedMethodAccessor171.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-6.0.11.jar!/:6.0.11]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.0.11.jar!/:6.0.11]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.0.11.jar!/:6.0.11]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:756) ~[spring-aop-6.0.11.jar!/:6.0.11]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-6.0.11.jar!/:6.0.11]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:391) ~[spring-tx-6.0.11.jar!/:6.0.11]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.0.11.jar!/:6.0.11]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar!/:6.0.11]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:756) ~[spring-aop-6.0.11.jar!/:6.0.11]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-6.0.11.jar!/:6.0.11]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar!/:6.0.11]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:756) ~[spring-aop-6.0.11.jar!/:6.0.11]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-6.0.11.jar!/:6.0.11]
at cn.com.idmy.zhdc.pa.admin.service.impl.RoomServiceImpl$$SpringCGLIB$$0.page() ~[classes!/:1.0.0]
at cn.com.idmy.zhdc.pa.admin.controller.RoomController.page(RoomController.java:23) ~[classes!/:1.0.0]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-6.0.11.jar!/:6.0.11]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-6.0.11.jar!/:6.0.11]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.0.11.jar!/:6.0.11]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[spring-webmvc-6.0.11.jar!/:6.0.11]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-6.0.11.jar!/:6.0.11]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.0.11.jar!/:6.0.11]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) ~[spring-webmvc-6.0.11.jar!/:6.0.11]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) ~[spring-webmvc-6.0.11.jar!/:6.0.11]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[spring-webmvc-6.0.11.jar!/:6.0.11]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) ~[spring-webmvc-6.0.11.jar!/:6.0.11]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.0.11.jar!/:6.0.11]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.12.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.12.jar!/:na]
at cn.com.idmy.cloud.filter.GlobalFilter.doFilterInternal(GlobalFilter.java:40) ~[web-1.0.0.jar!/:1.0.0]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.11.jar!/:6.0.11]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.11.jar!/:6.0.11]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.11.jar!/:6.0.11]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.0.11.jar!/:6.0.11]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.11.jar!/:6.0.11]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.0.11.jar!/:6.0.11]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.11.jar!/:6.0.11]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.12.jar!/:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.12.jar!/:na]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

@cbqqkcel
Copy link
Author

这个表新的数据有问题,不查询新的数据不会出错。

@cbqqkcel
Copy link
Author

用6.0.0-SNAPSHOT读这个数据还是一样的。估计是数据坏了

@cbqqkcel
Copy link
Author

这个表的主键特别长,还有中文

@codefollower
Copy link
Member

如果直接内嵌在 tomcat 的容器里跑,并发读写 lealone 用的是 tomcat 的线程,这时就绕过 lealone 内部的调度服务线程了。嵌入式环境使用 lealone 然后用容器的并发线程读写会有并发访问的问题,这个我之前没有考虑到,我以为嵌入式场景是单线程的。

@codefollower
Copy link
Member

lealone 整个并发控制系统是完全依赖 lealone 调度服务线程的,如果调度服务线程不参与并发读写了,是一定会发生并发访问问题的。

@codefollower
Copy link
Member

lealone-tomcat 那个插件也是内嵌在 tomcat 中跑的,但是我用 lealone 的调度服务线程完全替换掉 tomcat 的线程池了,所以并发读写 lealone 是安全的。

@cbqqkcel
Copy link
Author

那怎么办,想着删除今天的数据。也不能删的

@codefollower
Copy link
Member

主表的少量 page 因为并发读写出问题有可能损坏了,你需要把数据库备份一下,然后执行 repair table 语句修复一下看看还有问题没有。

修复完之后还是建议先改成 client-server 的方式访问数据库,目前这种直接内嵌在 tomcat 中用它的线程并发读写 lealone 我已经发现是会有并发问题然后容易导致数据损坏的。

@cbqqkcel
Copy link
Author

repair table 没用。
有办法把今天新增的数据全部删除吗

@codefollower
Copy link
Member

codefollower commented Oct 26, 2023

直接去对应的表目录下,找到尾部数字最大的 chunk 文件,删掉它之后最新的数据就从上一个 chunk 文件开始。

@codefollower
Copy link
Member

表的 id 可以通过 information schema 的 db_objects 表查询

@codefollower
Copy link
Member

information schema 的 tables 表也可以查表 id

@codefollower
Copy link
Member

select id,database_name from information_schema.databases;
select id,table_name from information_schema.tables;
使用上面两条 sql 找到数据库和表的 id,然后到 lealone 的 data 目录下找 db_xx/t_xx_yy,就是表的数据目录,如果是 i 开头的就是索引的数据。

@codefollower
Copy link
Member

如果 redo_log 目录中还有数据,删除表的最新 chunk 之后是可以在重新启动时自动恢复的。

@codefollower
Copy link
Member

data\redo_log\archives 目录中的 redoLog_xxx 文件还保留了几天?虽然已经归档了,依然可以从这些归档的 redoLog 文件恢复到今天的数据的。不过我还没有开发工具从 archives 目录中的 redoLog_xxx 文件恢复,重启数据库时只是从未归档的 redoLog 文件恢复。

@cbqqkcel
Copy link
Author

data\redo_log\archives 目录中的 redoLog_xxx 文件还保留了几天?虽然已经归档了,依然可以从这些归档的 redoLog 文件恢复到今天的数据的。不过我还没有开发工具从 archives 目录中的 redoLog_xxx 文件恢复,重启数据库时只是从未归档的 redoLog 文件恢复。

只有10-25和26日的

@codefollower
Copy link
Member

那个 lealone-5.2.0.jar 里面是不是没有 snakeyaml?没有的话就不读 lealone.yaml

@cbqqkcel
Copy link
Author

那个 lealone-5.2.0.jar 里面是不是没有 snakeyaml?没有的话就不读 lealone.yaml

没有

@codefollower
Copy link
Member

你下个 zip 或 gz 的包,然后去 bin 目录下运行,这个才是完整的分发包。

@codefollower
Copy link
Member

data\redo_log 目录及其子目录下的所有文件都很重要,如果数据损坏后又不允许丢失一条数据,就可以通过 redo_log 目录中的文件恢复。从 redo_log 归档文件恢复数据的工具我还没做,如果你的数据不能丢失,我可以花点时间做个工具,你可以等一段时间,等我做完了我就把工具给你恢复数据,如果数据丢点没关系,那我就不着急做这个工具了,会把这个功能规划到 lealone 6.0 中。

@cbqqkcel
Copy link
Author

data\redo_log 目录及其子目录下的所有文件都很重要,如果数据损坏后又不允许丢失一条数据,就可以通过 redo_log 目录中的文件恢复。从 redo_log 归档文件恢复数据的工具我还没做,如果你的数据不能丢失,我可以花点时间做个工具,你可以等一段时间,等我做完了我就把工具给你恢复数据,如果数据丢点没关系,那我就不着急做这个工具了,会把这个功能规划到 lealone 6.0 中。

可以丢失只要能回到今天之前。不过今天才发现之前的数据也有问题。

@cbqqkcel
Copy link
Author

我这个项目是每月使用一次

@cbqqkcel
Copy link
Author

之前的数据有几张表报 [2023-10-26 21:58:35] [HY000][50000] General error: "java.lang.StackOverflowError" [50000-0]

@codefollower codefollower changed the title 生产环境报错 嵌入式场景多线程并发更新在生产环境导致数据损坏 Oct 26, 2023
@codefollower
Copy link
Member

codefollower commented Oct 27, 2023

Exception in thread "ScheduleService-2" java.lang.NullPointerException: Cannot invoke "org.lealone.storage.StorageMap.isClosed()" because "map" is null at org.lealone.transaction.aote.AOTransactionEngine.fullGc(AOTransactionEngine.java:270) at org.lealone.server.Scheduler.gc(Scheduler.java:219) at org.lealone.server.Scheduler.executeNextStatement(Scheduler.java:228) at org.lealone.server.Scheduler.run(Scheduler.java:110)

导数据一段时间就报这个错了。不知道是代码问题还是数据问题。

这个我试了一下是个代码的 bug,一个线程准备做 GC 时,刚拿到当前使用的表对应的 StorageMap 的名字,另外一个线程就把表删除了,这时再用 StorageMap 的名字 get 出 StorageMap 对象就为 null 了,然后没有判断是否为 null 就去拿它调用 isClosed() 方法,最后就抛出 NullPointerException。

这个 bug 容易修复,加上 if(map != null) 即可。

@codefollower
Copy link
Member

Exception in thread "ScheduleService-7" java.lang.OutOfMemoryError: Capacity: 6144 at org.lealone.db.DataBuffer.grow(DataBuffer.java:553) at org.lealone.db.DataBuffer.ensureCapacity(DataBuffer.java:532) at org.lealone.db.DataBuffer.put(DataBuffer.java:362) at org.lealone.net.nio.NioBuffer.appendBytes(NioBuffer.java:68) at org.lealone.net.nio.NioBuffer.appendBytes(NioBuffer.java:13) at org.lealone.net.NetBufferOutputStream.write(NetBufferOutputStream.java:35) at java.base/java.io.DataOutputStream.writeChar(DataOutputStream.java:190) at org.lealone.net.TransferOutputStream.writeString(TransferOutputStream.java:213) at org.lealone.net.TransferConnection.sendError(TransferConnection.java:105) at org.lealone.server.SessionInfo.sendError(SessionInfo.java:130) at org.lealone.server.Scheduler.executeNextStatement(Scheduler.java:262) at org.lealone.server.Scheduler.run(Scheduler.java:110)

这个问题我也重现出来了,原因是看到你配的参数是 set JAVA_OPTS=-ea -Xms1G -Xmx16G -XX:MaxDirectMemorySize=1G

我才突然明白问题是出在 -XX:MaxDirectMemorySize=1G 这个参数,把它去掉就好了,运行 java 开发的数据库不需要它。

以下是原因:

使用 java 开发的数据库,为了避免经常被 JVM GC 暂停影响会选择使用 java.nio.DirectByteBuffer 也就是 DirectMemory 来管理内存中的数据,lealone 也不例外,也大量使用了 DirectByteBuffer,并且 lealone 只在乎 -Xmx 这个参数,直接忽视 -XX:MaxDirectMemorySize,也就是说如果 -Xmx 设置为 16G,lealone 就会尽可能的把数据装载到 DirectByteBuffer 中,并不会考虑 -XX:MaxDirectMemorySize 这个参数,哪怕你设置了 1G,lealone 在装了 1G 的 DirectByteBuffer 后,看到最大 -Xmx 是 16G,还小得很呢,就继续加载数据到 DirectByteBuffer,但是 JVM 本身是要按 -XX:MaxDirectMemorySize 这个参数办事的,当 lealone 加载的 DirectByteBuffer 超过 1G了,JVM 就抛出 OutOfMemoryError 了。

我也是第一次碰到这个问题,-XX:MaxDirectMemorySize 这个参数在我的印象里我从来没用过,限制它的大小带来的好处我也不知道有哪些,缺点对于数据库来说就很致命了。

@areyouok
Copy link

-XX:MaxDirectMemorySize的作用:

大的DirectByteBuffer在堆内占用很少的内存空间,但是在堆外占用大量的内存,如果不加以限制,超过物理内存的大小就麻烦了。特别是,一些使用时间比较长的DirectByteBuffer对象,晋升到老年代,然后又变成垃圾了,如果老年代gc迟迟不触发,这部分堆外内存就不会释放。加了-XX:MaxDirectMemorySize以后,分配堆外内存如果超限制了,会触发一次堆内的gc,释放掉占坑不拉屎的DirectByteBuffer对象。

当然更好的办法是手工释放DirectByteBuffer,在java里面没有直接的办法,需要通过非标准api。

@codefollower
Copy link
Member

-XX:MaxDirectMemorySize的作用:

大的DirectByteBuffer在堆内占用很少的内存空间,但是在堆外占用大量的内存,如果不加以限制,超过物理内存的大小就麻烦了。特别是,一些使用时间比较长的DirectByteBuffer对象,晋升到老年代,然后又变成垃圾了,如果老年代gc迟迟不触发,这部分堆外内存就不会释放。加了-XX:MaxDirectMemorySize以后,分配堆外内存如果超限制了,会触发一次堆内的gc,释放掉占坑不拉屎的DirectByteBuffer对象。

当然更好的办法是手工释放DirectByteBuffer,在java里面没有直接的办法,需要通过非标准api。

lealone 内部自己实现了一套 GC 算法,分配的 DirectByteBuffer 的大小是受 -Xmx 限制的,所以不会超过物理内存的大小,同时 GC 算法也会动态把 DirectByteBuffer 对象置 null,这时 JVM 的 GC 线程可以及时回收。
所以设置 -XX:MaxDirectMemorySize 参数对 lealone 是没有任何意义的,反而误让 JVM 干坏事了。

@areyouok
Copy link

就这个情况-Xmx16G -XX:MaxDirectMemorySize=1G这样设置是不合理的(其实在很多数情况下这个设置也不太合理,Xmx比MaxDirectMemorySize大太多)。

有的时候,光把DirectByteBuffer 对象置 null是不够的,现在java主流gc算法都是分代的比如G1和CMS,如果一个DirectByteBuffer能够在多个minor gc后保持存活,就晋升到老年代去了,有的程序老年代gc要很久才触发一次(比如大部分对象都是短生命周期的,都被minor gc干掉了,老年代就迟迟不会满),这样在老年代gc发生前,这个设置为null的DirectByteBuffer 都不会回收。

不分代的gc算法比如zgc下会如何我不太清楚,但现在使用最广泛的gc还是分代的。

@cbqqkcel
Copy link
Author

cbqqkcel commented Oct 27, 2023

只设置-Xmx是最好的,因为我看到报了一个错,网上说是MaxDirectMemorySize太小了。所以就调大了
JVM 参数不懂,就随便调大了

@codefollower
Copy link
Member

只设置-Xmx是最好的,因为我看到报了一个错,网上说是MaxDirectMemorySize太小了。所以就调大了

没事的,这里只是讨论技术问题,如果下次遇到运行 lealone 出现任何问题,直接发来 github 就可以了,不用先去网上查的。

@cbqqkcel
Copy link
Author

昨天异常太多了。我没有全部发上来。后来代码改了。没有测试代码了。
遇到过 Async callback is null, may be a bug! packetId 这个异常信息。
操作步骤是我一条一条的从 room 表查询把好的数据导出到新的 room2 表中遇到的。刚开始很快到后面就特别慢。

@cbqqkcel
Copy link
Author

cbqqkcel commented Oct 27, 2023

java.lang.StackOverflowError

昨天遇到最多的错是这个。查询一个表全部数据的时候。我觉得是数据坏了的问题导致的。很多表中的字段都是异常信息。

@codefollower
Copy link
Member

同时出现 OutOfMemoryError 和数据损坏这两种情况,就说明没有比这个更糟糕的了,数据库出现任何错误提示都是不奇怪的。

这个大问题的原因总结起来就两个: 1. 不能在嵌入式场景下多线程使用 lealone 了,这个很明确是有问题,并且一定会导致数据损坏的;2. 把 -XX:MaxDirectMemorySize 这个参数去掉,没用的,还可能导致 OOM。

我看了你给我的数据,数据量很小的,比我压测 tpcc 少了几百倍,表结构也没什么特殊的。
换成 client-server 模式后试试吧。

@cbqqkcel
Copy link
Author

clinet-server 依旧不行
因为客户着急用我昨天晚上把数据库临时迁移到 MySQL 了。
刚才从mysql导出的sql insert 语句数据都插入进去了。查询很多数据的时候还是会报错
insert 是用datagrip 命令行插入的。

image

@cbqqkcel
Copy link
Author

cbqqkcel commented Oct 27, 2023

为确保干净,5.2 的 zip我刚才又重新下载了一遍,data 目录下的数据全部删除重新搞得
image

是不是这个表有问题

@cbqqkcel
Copy link
Author

image

@cbqqkcel
Copy link
Author

要不我把sql文件和操作步骤发给你看看

@codefollower
Copy link
Member

room 表有多少行数据?你在 client-server 模式下重新 insert 数据是在一张空的 room 表下进行,还是在原有的数据上进行?

@cbqqkcel
Copy link
Author

26549 条数据。空表,全部都是重新来过的。

@codefollower
Copy link
Member

26549 条数据根本就无关痛痒,你的内存那么大,写这点数据都是直接放内存中的,还轮不到写硬盘后数据损坏。
你直接把你导入数据的 sql 发到我邮箱吧,还是我直接操作调试能更快找到原因。

我只用256M内存压测 tpcc 上百万的记录都没问题,所以我就特别奇怪你的场景到底是哪出了问题。

@cbqqkcel
Copy link
Author

cbqqkcel commented Oct 27, 2023

26549 条数据根本就无关痛痒,你的内存那么大,写这点数据都是直接放内存中的,还轮不到写硬盘后数据损坏。 你直接把你导入数据的 sql 发到我邮箱吧,还是我直接操作调试能更快找到原因。

我只用256M内存压测 tpcc 上百万的记录都没问题,所以我就特别奇怪你的场景到底是哪出了问题。

步骤和文件发你了,如果是硬件问题那就尴尬了

@codefollower
Copy link
Member

我定位到大概的原因了,当使用 from room limit 0,30000 查询时,记录数只要超过1万,lealone 默认就把结果集写到一个临时文件,写临时文件可能存在 bug,所以导致读取的时候出现 java.lang.IllegalStateException: Position 0 那个错误。
临时的解决方案就是执行 set max_memory_rows 1000000; 语句,把在内存中查到的结果集记录数调大一些,不写到临时文件。

insert 到 room 表的数据是没问题的,只是读查询结果集的临时文件出问题了。

@forifido
Copy link

-XX:MaxDirectMemorySize的作用:
大的DirectByteBuffer在堆内占用很少的内存空间,但是在堆外占用大量的内存,如果不加以限制,超过物理内存的大小就麻烦了。特别是,一些使用时间比较长的DirectByteBuffer对象,晋升到老年代,然后又变成垃圾了,如果老年代gc迟迟不触发,这部分堆外内存就不会释放。加了-XX:MaxDirectMemorySize以后,分配堆外内存如果超限制了,会触发一次堆内的gc,释放掉占坑不拉屎的DirectByteBuffer对象。
当然更好的办法是手工释放DirectByteBuffer,在java里面没有直接的办法,需要通过非标准api。

lealone 内部自己实现了一套 GC 算法,分配的 DirectByteBuffer 的大小是受 -Xmx 限制的,所以不会超过物理内存的大小,同时 GC 算法也会动态把 DirectByteBuffer 对象置 null,这时 JVM 的 GC 线程可以及时回收。 所以设置 -XX:MaxDirectMemorySize 参数对 lealone 是没有任何意义的,反而误让 JVM 干坏事了。

这里有点疑惑,你的意思是你能保证堆和直接内存的占用之和不超过 Xmx 的配置?

@cbqqkcel
Copy link
Author

我定位到大概的原因了,当使用 from room limit 0,30000 查询时,记录数只要超过1万,lealone 默认就把结果集写到一个临时文件,写临时文件可能存在 bug,所以导致读取的时候出现 java.lang.IllegalStateException: Position 0 那个错误。 临时的解决方案就是执行 set max_memory_rows 1000000; 语句,把在内存中查到的结果集记录数调大一些,不写到临时文件。

insert 到 room 表的数据是没问题的,只是读查询结果集的临时文件出问题了。

非常有可能,我之前导出数据的时候 5000一次的查询就不会有问题。过1万就有可能出问题。

@codefollower
Copy link
Member

-XX:MaxDirectMemorySize的作用:
大的DirectByteBuffer在堆内占用很少的内存空间,但是在堆外占用大量的内存,如果不加以限制,超过物理内存的大小就麻烦了。特别是,一些使用时间比较长的DirectByteBuffer对象,晋升到老年代,然后又变成垃圾了,如果老年代gc迟迟不触发,这部分堆外内存就不会释放。加了-XX:MaxDirectMemorySize以后,分配堆外内存如果超限制了,会触发一次堆内的gc,释放掉占坑不拉屎的DirectByteBuffer对象。
当然更好的办法是手工释放DirectByteBuffer,在java里面没有直接的办法,需要通过非标准api。

lealone 内部自己实现了一套 GC 算法,分配的 DirectByteBuffer 的大小是受 -Xmx 限制的,所以不会超过物理内存的大小,同时 GC 算法也会动态把 DirectByteBuffer 对象置 null,这时 JVM 的 GC 线程可以及时回收。 所以设置 -XX:MaxDirectMemorySize 参数对 lealone 是没有任何意义的,反而误让 JVM 干坏事了。

这里有点疑惑,你的意思是你能保证堆和直接内存的占用之和不超过 Xmx 的配置?

在 java 程序中肯定不能像 c++ 那样精确控制占用了多么内存,但是可以用个预估值,比如写入一条记录可以根据字段类型以及 jvm 分配一个对象占用多少字节做一个预估,然后动态累加,删除记录时再减去。启动时拿到-Xmx的最大值,取1/3,只要累加的内存使用量超过这个阈值了 lealone 就会启动 GC 任务,回收掉数据库中那些可以踢出去的对象。

@codefollower
Copy link
Member

clinet-server 依旧不行 因为客户着急用我昨天晚上把数据库临时迁移到 MySQL 了。 刚才从mysql导出的sql insert 语句数据都插入进去了。查询很多数据的时候还是会报错 insert 是用datagrip 命令行插入的。

image

调试了代码,确实是查询记录数超过1万条后生成临时文件有 bug 导致的。

@codefollower
Copy link
Member

java.lang.StackOverflowError

昨天遇到最多的错是这个。查询一个表全部数据的时候。我觉得是数据坏了的问题导致的。很多表中的字段都是异常信息。

这个问题我找到原因了,查表超过1万行时,写临时文件是在一个大事务中写所有记录,所以产生了很多次 page split,在对 page 标记脏页时使用了递归,递归太深,所以就堆栈溢出了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants