为啥即使没有争用,我的 Oracle PreparedStatement 有时也不会返回?

Posted

技术标签:

【中文标题】为啥即使没有争用,我的 Oracle PreparedStatement 有时也不会返回?【英文标题】:Why does my Oracle PreparedStatement sometimes never return despite no contention?为什么即使没有争用,我的 Oracle PreparedStatement 有时也不会返回? 【发布时间】:2014-02-25 22:26:48 【问题描述】:

2014 年 2 月 5 日更新:

问题已通过重新启动托管 Oracle 数据库的 Linux 服务器得到解决。尽管 Oracle 本身已定期重启,但服务器自去年 5 月以来一直未启动。

我有几个使用 Oracle 11.2 数据库和 11.2.0.3.0 ojdbc6.jar Oracle 驱动程序的 Java 1.6 程序。在看似随机的点,它显然会挂起,永远不会从 PreparedStatement.executeUpdate() 返回控制权。

我的程序经常将数据绑定到 BLOB 列,在这种情况下(同样是随机时间),它可能会在调用 OutputStream.flush() 时挂起,其中我的 OutputStream 是 OracleBlobOutputStream 的包装器。

在这两种情况下,线程在继续尝试读取套接字以获取 Oracle 响应之前一直卡住等待。

使用 sqlDeveloper 为我的 JDBC 瘦客户端监控 Oracle 数据库中的会话 我可以看到会话正在等待,如 Seconds In Wait 所示。在刷新 blob 的特定情况下,ActiveSQL 选项卡显示无可用文本。在挂在 PreparedStatement.executeUpdate() 的情况下,该选项卡将显示我的插入语句的全文。无论哪种情况,Waits 选项卡都会显示“来自客户端的 SQL*Net 更多数据”,这对我来说表明 Oracle 服务器正在等待更多数据来完成客户端请求。

所以我可以看到 Oracle 服务器似乎正在等待客户端完成他的请求。客户端似乎已经完成了请求,正在等待服务器返回响应。

网络错误可能是造成这种情况的原因吗?我认为客户端和服务器将受到 TCP/IP 流的重试逻辑的保护。我经常通过 Internet 上的 *** 连接(针对数据库的测试实例)使用此应用程序,我预计会出现更多错误,但在这种情况下我从未发现问题。

我已经在 Oracle 驱动程序中看到了针对 getNextPacket() 问题的修复,但如上所示,我们使用的是最新的驱动程序并且应该有这些。

正如我所料,“争用”选项卡从不指示任何内容。据我所知,竞争交易不是这里的问题。并且程序仍然会在晚上失败,因为除了我的程序之外几乎没有任何其他活动。

这段代码在我的测试环境中完美运行。它也适用于我客户站点的测试环境。但是在生产环境中它失败了。它可能会在失败之前插入 50-100K 行数据。

在某些情况下它不会挂起。它会引发不一致的异常,例如关于如何只能将 LONG 值绑定到 LONG 列的异常。这也是我在四个不同数据库的测试中从未见过的,并且问题从一个表转移到另一个表,没有明显的模式。

据我所知,动态 SQL 可以正常工作,并且问题是特定于预准备语句的。但我不能确定。

这个生产数据库比任何测试实例都大。它的大小可以处理大约 2 TB 的数据,并且可能是实现该目标的 1/3。所有的表空间都有足够的空间,并且回滚段最近扩大了 3 倍,并且没有得到充分利用。

我不知道自动提交模式会挂起,而且似乎只有在事务积累了大量数据后才会挂起。但由于问题如此随机,我无法得出结论。

这个程序运行了几个月没有问题,然后在几周前开始运行,没有对软件进行任何更改。客户的数据库一直在稳步变大,所以这是一个变化。我听说客户在那段时间安装了一些网络监控软件,但我没有任何具体细节。

有时 JDBC 批处理在发挥作用,有时没有,但仍然失败。

我正在把头发拉到这个上面,我几乎没有什么可做的!

*** 上的朋友有什么见解吗?

这是一个调用堆栈,我在其中等待在服务器上看到 Seconds in Wait,然后在 Eclipse 调试器中暂停了我的客户端程序。从 OracleOutputStream 开始的所有内容都是 ojdbc6.jar 代码。

Thread [GraphicsTranslator:1] (Suspended)        
owns: T4CConnection  (id=26)    
owns: Input  (id=27)       
SocketInputStream.socketRead0(FileDescriptor, byte[], int, int, int) line: not available [native method]             
SocketInputStream.read(byte[], int, int) line: 129             
DataPacket(Packet).receive() line: 293   
DataPacket.receive() line: 92      
NetInputStream.getNextPacket() line: 174          
NetInputStream.read(byte[], int, int) line: 119   
NetInputStream.read(byte[]) line: 94     
NetInputStream.read() line: 79 
T4CSocketInputStreamWrapper.readNextPacket() line: 122        
T4CSocketInputStreamWrapper.read() line: 78  
T4CMAREngine.unmarshalUB1() line: 1040           
T4CMAREngine.unmarshalSB1() line: 1016            
T4C8TTIBlob(T4C8TTILob).receiveReply() line: 847            
T4C8TTIBlob(T4C8TTILob).write(byte[], long, byte[], long, long) line: 243               
T4CConnection.putBytes(BLOB, long, byte[], int, int) line: 2078  
BLOB.setBytes(long, byte[], int, int) line: 698      
OracleBlobOutputStream.flushBuffer() line: 215               
OracleBlobOutputStream.flush() line: 167            
ISOToDBWriter.bindElementBuffer(ParameterBinding, SpatialObject, boolean) line: 519               
ISOToDBWriter.writePrimitive(SpatialObject, boolean) line: 1720              
ISOToDBWriter.writeDgnElement(SpatialObject, Properties, String, boolean) line: 1427 
ISOToDBWriter.write(SpatialObject) line: 1405   
ISOHandler.inputObject(InputEvent) line: 864    
InputEventMulticaster.inputObject(InputEvent) line: 87               
Input(Input).notifyInput(Object, Object) line: 198            
Input(Input).notifyInput(Object) line: 157            
Input.readElement(int) line: 468               
Input.readElement() line: 403    
Input.run() line: 741        
GraphicsTranslator.processAllDgnFiles() line: 1190            
GraphicsTranslator.run() line: 1364          
Thread.run() line: 662    

2014 年 2 月 3 日更新:

我已经能够在客户的网站上进行更多测试。显然问题是由网络错误引起的。我写了一个带有直接jdbc调用的小测试程序,它也失败了。它仅针对此特定数据库实例失败。测试程序将越来越长的字符串绑定到它继续执行的准备好的语句中,并最终回滚事务(如果它达到那么远)。测试程序有时会随机抛出异常,而不是挂起,如下所示:

java.sql.SQLException: ORA-01461: can bind a LONG value only for insert into a LONG column
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:447)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:951)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:513)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:227)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:208)
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:1046)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1336)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3613)
at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:3694)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:1354)
at com.byers.test.outage.TestPreparedInsert.insertThenRollback(TestPreparedInsert.java:81)
at com.byers.test.outage.TestPreparedInsert.runTest(TestPreparedInsert.java:54)
at com.byers.test.outage.TestPreparedInsert.main(TestPreparedInsert.java:28)

测试程序插入数千行并以相当不错的剪辑运行,直到插入字符串长于大约 1,300 字节。然后它变得越来越慢,当字符串大约 1,500 字节时,单个插入将需要 30 秒或更长时间。我怀疑当请求超过一个数据包的大小时问题就开始了。

我运行了 WireShark 并捕获了在我和 Oracle 服务器之间传输的所有 IP 数据包。然后我看到很多 TCP ACKed unseen segment、TCP Previous Segment not capture、TCP Dup ACK 3#1、TCP Dup ACK 3#2 等。我不是网络专家,但我很聪明地说“这不是好”。

与我的生产系统不同,到目前为止,我的测试程序实际上并没有导致 Oracle “挂起”。 Oracle 会话不显示等待中的秒数,如果我等待的时间足够长,程序将继续(尽管我对此的耐心有限)。除非我同时运行多个程序实例,否则我也没有看到引发上述异常,尽管这也可能是等待时间不够长的问题?

以下代码的调用,例如:

insertThenRollback(con, 50, 2000, 0);

非常擅长产生错误。有趣的是,从像 3000 字节这样的大插入字符串开始不会导致错误,直到程序在 4000 处回收并重新计数到 1300+ 范围内。

private static void insertThenRollback(Connection con, int delayMs, int rowCount, int startCharCount)
        throws SQLException, InterruptedException

    System.out.println("Batch " + (++batchCount) + ". Insert " + rowCount + " rows with "
            + delayMs + "ms. delay between, then rollback");
    String sql = "Insert Into config (name,value) values(?,?)";
    PreparedStatement stmt = con.prepareStatement(sql);
    String insString = "";
    for (int c = 0; c < startCharCount; ++c)
    
        int randomChar = (int) (Math.random() * DATA_PALLET.length());
        insString += DATA_PALLET.charAt(randomChar);
    
    try
    
        for (int i = 0; i < rowCount; ++i)
        
            if (insString.length() > MAX_INSERT_LEN - 1)
                insString = "";
            int randomChar = (int) (Math.random() * DATA_PALLET.length());
            insString += DATA_PALLET.charAt(randomChar);
            String randomName = "randomName--" + UUID.randomUUID();
            System.out.println("Row " + (i + 1) + "->" + randomName + '/' + insString.length()
                    + " chars");
            stmt.setString(1, randomName);
            stmt.setString(2, insString);
            stmt.executeUpdate();
            Thread.sleep(delayMs);
        
    
    finally
    
        System.out.println("Rollback");
        con.rollback();
        stmt.close();
    

这似乎让我能够告诉客户问题出在他们的网络上。大家同意吗?客户端应该能够以某种方式监视他们的网络以发现这些类型的错误,这难道不是真的吗?在我看来,我们会投入数百小时的集体努力来解决这样的问题,只是为了找出它是硬件还是某种侵入性软件,这似乎几乎是愚蠢的。有没有办法通过某种监控来检测这些高度的网络错误?

【问题讨论】:

您要关闭preparedStatementConnection 对象吗? 是的,所有语句、连接和 blob 对象在程序完成后都会关闭。 顺便说一句,当您一次执行 50-100k 行时,您是否正在使用任何类型的批量更新?并发布您的代码 sn-p,它确实有助于其他人查看。 我的一个程序失败,没有任何类型的批处理。其他人在没有批处理的情况下失败。似乎无关。 网络监控给我敲响了警钟。我已经看到边界入侵检测在它认为它看到可疑的东西时会默默地丢弃数据包,即使在 LAN 中,也有任何一端的指示。这将阻止失败重试,因为重试也会被丢弃,如果不是该连接上的所有进一步流量的话。软件和数据库在同一个盒子上吗?当查询相同的数据时,您是否看到问题?我会询问网络人员在您看到此内容时是否记录了任何问题,和/或请求 SQL*Net 流量的规则例外以查看它是否停止。 【参考方案1】:

最近我们在生产应用程序中遇到了相同的行为,重新启动应用程序后,数据库持久性恢复工作。

为了寻找可能发生的事情的线索,我最终找到了这篇文章 (Understanding JDBC Internals & Timeout Configuration),其中详细解释了 JDBC 的工作原理及其不同类型的超时。

我们将尝试在 JDBC 上配置超时(使用 oracle.net.CONNECT_TIMEOUT / oracle.jdbc.ReadTimeout) 以尽量避免以后出现此问题。

【讨论】:

以上是关于为啥即使没有争用,我的 Oracle PreparedStatement 有时也不会返回?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 SVG 元素没有出现? (即使在检查我的网页时)

为啥我的 AngularJS ajax 自动触发,即使我没有调用它?

为啥我的 python 脚本没有显示为进程,即使它正在运行?

为啥我的状态值即使在改变状态后也没有改变? [复制]

为啥我的 WASAPI 监听器即使在没有播放的时候也会触发?

为啥我的 onBindViewHolder () 根本没有运行,即使我的所有其他回收器视图方法都运行了?