lealone / Lealone

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

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

Closed cbqqkcel closed 9 months ago

cbqqkcel commented 10 months ago

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 commented 10 months ago

查询数据库时报错了

codefollower commented 10 months ago

这个是你自己打的 jar 包?

cbqqkcel commented 10 months ago

是的

codefollower commented 10 months ago

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

codefollower commented 10 months ago

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

cbqqkcel commented 10 months ago

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

cbqqkcel commented 10 months ago

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 commented 10 months ago

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

cbqqkcel commented 10 months ago

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

cbqqkcel commented 10 months ago

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

codefollower commented 10 months ago

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

codefollower commented 10 months ago

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

codefollower commented 10 months ago

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

cbqqkcel commented 10 months ago

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

codefollower commented 10 months ago

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

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

cbqqkcel commented 10 months ago

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

codefollower commented 10 months ago

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

codefollower commented 10 months ago

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

codefollower commented 10 months ago

information schema 的 tables 表也可以查表 id

codefollower commented 10 months ago

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 commented 10 months ago

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

codefollower commented 10 months ago

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

cbqqkcel commented 10 months ago

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

只有10-25和26日的

codefollower commented 10 months ago

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

cbqqkcel commented 10 months ago

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

没有

codefollower commented 10 months ago

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

codefollower commented 10 months ago

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

cbqqkcel commented 10 months ago

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

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

cbqqkcel commented 10 months ago

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

cbqqkcel commented 10 months ago

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

codefollower commented 10 months ago

我一直以为你是在 lealone 的微服务框架里跑的,lealone-tomcat 插件我就是专门为了解决 tomcat 的线程池访问 lealone 产生的并发问题。直到看到堆栈错误里没有调度服务线程的信息,我才猜到调度服务线程根本就没有起作用,那肯定从第一天运行开始就可能导致 tomcat 的多个线程并发读写 lealone 从而会导致数据损坏。

在 lealone 的测试代码中,如果不启动 tcp server,调度服务线程也不会启动,但凡是嵌入式测试场景我都是单线程跑的,因为多线程确实有问题。

codefollower commented 10 months ago

简单总结就是: 如果在 leaone 中调度服务线程没有起作用,其他线程只能单线程访问 lealone 才是安全的,否则一定是会有各种奇奇怪怪的并发问题的。调度服务线程就是 lealone 的根基。

cbqqkcel commented 10 months ago

lealone 的微服务 不太习惯,习惯了Spring boot 那岂不是说spring boot 不能使用嵌入式

codefollower commented 10 months ago

在 lealone 6 中再想想怎么解决嵌入式环境外部多线程并发读写 lealone 的问题了,这不是个小问题。

cbqqkcel commented 10 months ago

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)

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

cbqqkcel commented 10 months ago

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)

codefollower commented 10 months ago

你开了多大的内存?JVM OOM了。

cbqqkcel commented 10 months ago

set JAVA_OPTS=-ea -Xms1G -Xmx16G -XX:MaxDirectMemorySize=1G

cbqqkcel commented 10 months ago

我本机是64G内存。

codefollower commented 10 months ago

16G 按理说足够了,我用256M内存就能持续导入 tpcc 的测试数据了,导入上百G的硬盘数据都没问题。你把你的表中所有的大对象类型直接改成 varchar 再导入试试,如果不出错那说明是大对象字段的问题,如果还出错,我需要表结构和导入数据的代码亲自试一下才知道问题到底出在哪。

cbqqkcel commented 10 months ago

早上好。我把9月份的数据放到生产环境,数据库版本号不记得了。全部数据是可以查询出来的。(嵌入式的) 搞到本地5.2 client-server 来就不行了。本地机器还比服务器的强几倍。

codefollower commented 10 months ago

嵌入式场景我可以100%确定是存在并发更新导致数据损坏的问题的,现在查询正确不代表以后就不出问题了。

还是先搞清楚 client server 下数据为什么导入失败的问题。至少在 client server 下并发读写我已经压测了很久的 tpcc,并没有发现问题。

codefollower commented 10 months ago

如果可以,你把表结构和导入数据的代码发一份到我的邮箱:codefollower AT gmail dot com

我试一下,不用发到 github,发 github 不安全

codefollower commented 10 months ago

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 commented 10 months ago

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 commented 10 months ago

-XX:MaxDirectMemorySize的作用:

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

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

codefollower commented 10 months ago

-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 commented 10 months ago

就这个情况-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 commented 10 months ago

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

codefollower commented 10 months ago

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

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