全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2025-08-24_事务报错,为何数据还是插入成功了

您的位置:首页 >> 新闻 >> 行业资讯

事务报错,为何数据还是插入成功了 (??金石瓜分计划强势上线,速戳上图了解详情??)前言?最近出现提交事务之后,程序反馈提交报错了,但是数据库中的数据确实是事务提交成功了的状态, 在生产环境造成了很多问题。 这个问题所有的后端程序员都值得一看,因为平时很少遇到这种情况! 「底层的报错如下:」 Caused by: java.net.SocketTimeoutException: Read timed out at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:171) at java.net.SocketInputStream.read(SocketInputStream.java:141) at com.kingbase8.core.VisibleBufferedInputStream.readMore(VisibleBufferedInputStream.java:210) at com.kingbase8.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:165) at com.kingbase8.core.VisibleBufferedInputStream.read(VisibleBufferedInputStream.java:117) at com.kingbase8.core.KBStream.receiveChar(KBStream.java:584) at com.kingbase8.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2658) at com.kingbase8.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:405) ...208common frames omittedsocket 基础知识了解这个问题前,需要回顾一下socket的几个关键时间参数: 「连接超时时间(Connect Timeout)」:指客户端在尝试连接到服务器时,等待连接建立的最长时间。如果在指定的时间内未能成功建立连接,将抛出SocketTimeoutException异常。可以通过Socket类的connect(SocketAddress endpoint, int timeout)方法来设置连接超时时间。「读取超时时间(Read Timeout)」:也称为SO_TIMEOUT,用于设置从输入流读取数据时的超时时间。它指定了在读取数据时等待的最长时间,如果在指定时间内没有数据可读取,将抛出SocketTimeoutException异常。可以通过setSoTimeout(int timeout)方法来设置读取超时时间。 (「今天讨论的就是这个参数」)「发送超时时间(Write Timeout)」:在一些情况下,还可以设置发送数据的超时时间,即从本地 socket 发送数据到对方 socket 时,等待对方确认接收的最长时间。通常不需要单独设置,一般情况下,如果网络连接正常,数据会被不断尝试发送,直到达到操作系统层面的相关限制。???本次的问题就是解决输入流读取数据超时的问题,了解了上面socket的基础知识之后,要解决这个问题就很简单了 ?解决问题排查问题过程目前数据库是人大金仓,驱动就是kingbase 8.6.0连接池是druid 1.2.19,数据库采用主从架构。通过代码发现主从架构和单节在设置socketTimeout是否逻辑是不一样的,带着以下两个问题我们去分析源码(逻辑基于主从架构)。 ???先说解决方案:spring.datasource.druid.socket-timeout = 60000 (当然这个值可以根据实际情况调整),这个配置实际上并不是直接修改socket的SO_TIMEOUT,分析了源码之后发现上面这个配置只是修改的重试次数 ?1. 既然出现SocketTimeoutException,首先就要搞清楚现在的超时时间是多少?2.参数配置到url后面为何失效了呢?从上面的报错可以定位到kingbase的最终报错的代码是:com.kingbase8.core.VisibleBufferedInputStream.readMore(VisibleBufferedInputStream.java:210) 定位VisibleBufferedInputStream类,看到socketTimeout属性如下: publicclassVisibleBufferedInputStreamextendsInputStream{ privatestaticfinalintMINIMUM_READ=1024; privatestaticfinalintSTRING_SCAN_SPAN=1024; privatefinalInputStream wrappedInputStream; privatebyte[] _buffer; privateint_index; privateintendIndex; privateString _host; privatebooleanuseDispatch; privateint_version; //--------------------------------- 这就是我们要确认的配置------------ privateintsocketTimeout;找到设置socketTimeout的地方: ?可以看到默认时间设置的是0(那么问题来了如果真的是无限等待为何会出现超时呢)?在 JDBC 驱动和网络编程中,socketTimeout=0是一个标准约定,表示「不设置超时」(无限等待)。此时: 读取操作会一直阻塞,直到有数据返回、连接被关闭或发生其他异常。若数据库端处理缓慢、网络中断或连接被意外保持,客户端可能会长时间无响应?看到这儿还有完,我们看看VisibleBufferedInputStream.readMore方法的实现: 异常之后的处理有个变量非常重要就是socketTimeout这个参数和实际的名称 含义 privateboolean readMore(int wanted) throws IOException { ................................. TraceLogger.logLineInfo(Level.ALL,"lineInfo"); int readT =true; int i =0; int j =0; int readT; while(true) { try{ //----------------------------------------读取响应数据----------------------------- readT =this.wrappedInputStream.read(this._buffer,this.endIndex, canFit); break; }catch(SocketTimeoutException var10) { TraceLogger.logLineInfo(Level.ALL,"lineInfo"); if(this.useDispatch) { if((this.pCMV2.master_online_ip.equals(this._host) ||this.pCMV2.slave_online_ip.contains(this._host +",")) && (Integer)this.pCMV2._connVersion.get(this._host) ==this._version) { label78: { // -------------------重试 socketTimeout 次数,------------ if(this.socketTimeout !=0) { ++i; if(i =this.socketTimeout) { breaklabel78; } } ++j; if(j %5==0) { KBLOGGER.log(Level.INFO,"Online _host {0} has been waiting for {1} times, socketTimeout is {2}", new Object[]{this._host, j,this.socketTimeout}); } continue; } } ............................ } throwvar10; } } if(readT 0) { returnfalse; }else{ this.endIndex += readT; returntrue; }}debug如下,发现socketTimeout 的值有两种情况: 当this.useDispatch = false 的时候socketTimeout = 0 (false的时候是由集群管理线程ClusterMonitorThread发起)当this.useDispatch = true 的时候 socketTimeout = 10?this.socketTimeout 只是发生异常的时候的重试次数那么我们在debug一下创建链接的时候,是不是也是这个默认的10呢:com.kingbase8.core.v3.ConnectionFactoryImpl.tryConnect()创建链接的关键源码如下: privateKBStreamtryConnect(String user, String database, Properties infoProps, SocketFactory socketFactory, HostSpec _hostSpec, SslMode sslMode,int_version, Object cCMV2)throwsSQLException, IOException { TraceLogger.logLineInfo(Level.ALL,"lineInfo"); // 创建socket 时候的链接超时时间 intconnectTimeoutT=KBProperty.CONNECT_TIMEOUT.getInt(infoProps) *1000; //----------------创建socket封装成KBStream 对象,创建的时候 设置建立链接的超时时间:默认 10*1000 毫秒 KBStreamnewStreamT=newKBStream(socketFactory, _hostSpec, connectTimeoutT, KBProperty.USEDISPATCH.getBoolean(infoProps) && infoProps.getProperty("isMonitor") ==null, _version, cCMV2); TraceLogger.logLineInfo(Level.ALL,"lineInfo"); // -------------------------默认0 intsocketTimeoutT=KBProperty.SOCKET_TIMEOUT.getInt(infoProps); if(KBProperty.USEDISPATCH.getBoolean(infoProps) && infoProps.getProperty("isMonitor") ==null) { TraceLogger.logLineInfo(Level.ALL,"lineInfo"); if(socketTimeoutT 0) { TraceLogger.logLineInfo(Level.ALL,"lineInfo"); socketTimeoutT =0; } //-------------------直接调用底层 `Socket` 对象的 `setSoTimeout` 方法,将 **Socket 的读取超时时间设置为 1000 毫秒(1 秒)** 。 newStreamT.getSocket().setSoTimeout(1000); //---------------------- `VisibleBufferedInputStream.readMore`------------使用会用到,实际上是读取失败的重试次数 newStreamT.setSocketTimeout(socketTimeoutT); KBLOGGER.log(Level.INFO,"Dispatch : socketTimeout is "+ socketTimeoutT *1000,newObject[0]); }else{?创建链接socket链接之后,会从连接池的配置读取,二次设置,下面方法的参数,是读取的连接池参数(没有配置的话就使用druid线程池默认的配置10_000) // 入参从连接池获取(就算没有设置,也会取连接池的默认)publicvoidsetNetworkTimeout(intmillisecs)throws IOException{ TraceLogger.logLineInfo(Level.ALL,"lineInfo"); //--------- 目前我们是主从结构,useDispatch = true------------ if(this.isUseDispatch()) { TraceLogger.logLineInfo(Level.ALL,"lineInfo"); //-------------写死socket 读时间为 1000 毫秒------------------------------------- this.connectionSocket.setSoTimeout(1000); //-------------设置连接池配置的 socketTimeout,用来重试读取数据次数 ------------------------------ this.setSocketTimeout(millisecs %1000==0? millisecs /1000: millisecs /1000+1); KBLOGGER.log(Level.INFO,"Dispatch : socketTimeout is "+ millisecs,newObject[0]); }else{ TraceLogger.logLineInfo(Level.ALL,"lineInfo"); this.connectionSocket.setSoTimeout(millisecs); KBLOGGER.log(Level.INFO,"Single or Monitor : socketTimeout is "+ millisecs,newObject[0]); }}?真正的scoket的读取时间在集群模式下面被写死成了1000ms? 原来我们配置的socket-timeout 最后会被除以1000 设置到 this.scoketTimeout 上面? this.socketTimeout 最后会被线程池的参数覆盖掉,所以设置到url中socketTimeout会失效??结论VisibleBufferedInputStream类中的socketTimeout为读取响应数据异常时候的重试次数,并非底层socket 读取响应超时时间 默认重试10次(主从) 真正的socket 的读取timeout 通过上面的代码发现是 固定的1000ms 就是1s,集群模式下面读取超时后会触发重试机制,实际上我们配置的是重试次数 「?注意的坑:」 int connectTimeoutT = KBProperty.CONNECT_TIMEOUT.getInt(infoProps) * 1000;建立链接的时间被*1000,并且这个参数只从url中读取?? 并且单位是秒坑人的地方第二点,集群模式下面 socket 真正的读时间 被写死成了1000ms,不能从url中配置。只能从连接池配置验证通过上面的debug 我们发现,kinbase在主从架构下面,没办法设置socketTimeout,虽然连接池和url都能配置这个参数,但是对于人大金仓来说 这个只能当成 重试次数来使用: 按照逻辑 超过重试次数次出有打印日志 抛出异常: privatebooleanreadMore(intwanted)throwsIOException { ..................... while(true) { try{ TraceLogger.logLineInfo(Level.ALL,"lineInfo"); readT =this.wrappedInputStream.read(this._buffer,this.endIndex, canFit); break; }catch(SocketTimeoutException var10) { 集群模式重试 .................... } // 按照逻辑 超过重试此处打印日志 抛出异常 KBLOGGER.log(Level.INFO,"socketTimeout Exception: useDispatch is "+this.useDispatch,newObject[0]); throwvar10; } } ...................}开启KBLOGGER.log将socketTimeout设置成 3000 重试三次(3000/1000),观察果然又出现报错,并且有日志打印: [2025-07-31 09:00:33] [43] [com.kingbase8.core.VisibleBufferedInputStream--readMore] socketTimeout Exception: useDispatchistrue,and_host = [node1], master_online_ip = [node1],slave_online_ip = [node2,], currentVersion = [1], lastVersion = [1], socketTimeout = [3] ?开始日志的方式需要引入特有的依赖,以及在数据库连接的url后面或者代码里面设置驱动的日志级别 ?总结代码没有问题,为何还会重复报名半年都还没解决的定时任务问题之前的这两个问题,从今天来说也算是给了一个相对合理的解决方案吧,就是增加了读取数据的重试次数,因为socket read time out 写死1s的(kingbase主从架构)。 而且这两个参数如何配置各有各的想法: socket-timeout: 3000 如果配置到数据库的url后(请用驼峰命名),在集群模式下面,这个参数后面会被 连接池的配置给覆盖(就算连接池的没有手动配置也会被默认值覆盖 10_000 )。所以不会生效如果配置到连接池,在集群模式下面,就被被当作 重试次数来使用connect-timeout: 3000 配置到数据库url后面,那么这个值的单位就是s,因为驱动在创建连接的时候还会*1000 ??配置到连接池的时候,根本不会生效?? (源码DruidAbstractDataSource.createPhysicalConnection,没有对kingbase驱动做处理)所以说程序也不能完全信任事务,事务在数据库是可靠的。只是程序和数据之间的信息传输不可靠了,导致程序做出错误的处理方案,从而引发bug AI编程资讯AI Coding专区指南:https://aicoding.juejin.cn/aicoding 点击"阅读原文"了解详情~ 阅读原文

上一篇:2024-02-21_卡萨帝洗碗机×亮碟再次开启流量密码 , 「做回家人」引爆全网共鸣 下一篇:2019-04-22_INT#14 寒武纪研究院院长杜子东:分形冯诺依曼机器学习计算机

TAG标签:

18
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价