Java JDBC Oracle SQL 查询每隔几个月就会挂起一次

Posted

技术标签:

【中文标题】Java JDBC Oracle SQL 查询每隔几个月就会挂起一次【英文标题】:Java OJDBC Oracle SQL query hangs once every few months 【发布时间】:2017-05-12 11:58:15 【问题描述】:

我们有一个带有 Web UI 和 REST API 的多线程 Java 应用程序,该应用程序使用 java 6 编译并在 tomcat 6 中运行。在操作期间,它使用 OJDBC 每天数百万次访问其 Oracle DB。每两三个月就会有一个数据库查询挂起并且永远不会返回,这会导致部分应用程序停止处理并创建积压。其他线程能够与数据库通信并完成它们的工作,只有一个线程被挂起,不幸的是停止了文件处理。

线程转储显示线程正在从永远不会超时也不会关闭的套接字读取:

"FileUpload" daemon prio=10 tid=0x00002b8e60617800 nid=0xf9e runnable [0x00002b8e5e10b000]
java.lang.Thread.State: RUNNABLE
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.read(SocketInputStream.java:129)
    at oracle.net.ns.Packet.receive(Packet.java:311)
    at oracle.net.ns.DataPacket.receive(DataPacket.java:103)
    at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:312)
    at oracle.net.ns.NetInputStream.read(NetInputStream.java:257)
    at oracle.net.ns.NetInputStream.read(NetInputStream.java:182)
    at oracle.net.ns.NetInputStream.read(NetInputStream.java:99)
    at oracle.jdbc.driver.T4CSocketInputStreamWrapper.readNextPacket(T4CSocketInputStreamWrapper.java:121)
    at oracle.jdbc.driver.T4CSocketInputStreamWrapper.read(T4CSocketInputStreamWrapper.java:77)
    at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1173)
    at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:309)
    at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:200)
    at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:543)
    at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:238)
    at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:1244)
    at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1492)
    at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1710)
    at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:4372)
    at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:4453)
- locked <0x00002b8e1c2d7010> (a oracle.jdbc.driver.T4CConnection)
    at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeQuery(OraclePreparedStatementWrapper.java:6270)
    at org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:96)
    at org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:96)
    at xxx.OracleFileInfoDAO.getFilesToUpload(OracleFileInfoDAO.java:874)

当这种情况发生时,DBA 已经查看了数据库服务器并且没有看到长时间运行的查询。解决方案是回收解决问题的tomcat,但我想知道是否有一种编程方式来处理这个问题。我已经看到了引用类似问题的附录,这些问题通过回收运行 DB 服务器的 LINUX 框来解决,但这对我们来说不是一个选项;我需要在应用程序级别进行修复。

数据库资源使用以下方式定义:

<Resource auth="Container" description="Oracle Datasource" name="xxx" scope="shareable" type="javax.sql.DataSource" url="jdbc:oracle:thin:@xxx:1521/xxx"  driverClassName="oracle.jdbc.driver.OracleDriver" username="xxx" password="xxx" maxWait="5000" maxActive="100" maxIdle="20" removeAbandoned="true" testOnReturn="true" testOnBorrow="true" validationQuery="select 1 from dual" />   

使用的OJDBC驱动是:ojdbc6_g-11.2.0.4.0.jar

数据库版本为:11.2.0.3.0

执行查询的java代码是:

                con = CSAConnectionManager.getConnection();                     
            StringBuilder strBuf = new StringBuilder(SQL_SELECT_FILE_INFO_TO_UPLOAD);
            ps = con.prepareStatement(strBuf.toString());
            ps.setString( 1, hostname );
            ps.setString( 2, containerId );
            ps.setMaxRows( maxRows );

            Date before = new Date();
            ResultSet rs = ps.executeQuery();

这是getConnection()的来源:

    public static Connection getConnection() throws Exception

    return instance.getInstanceConnection();


public Connection getInstanceConnection() throws Exception

    Connection con = null;
    if(ds != null)
    
        con = ds.getConnection();
    
    else
    
        String dburl = wrapper.getDBUrl();
        String username = wrapper.getDBUserName();
        String password = wrapper.getDBPassword();      
        String driverClass = wrapper.getDBDriverClass();
        Class.forName(driverClass).newInstance();

        con = DriverManager.getConnection(dburl,username,password);
           
    con.setAutoCommit(false);
    return con;

“ds”定义为:private static DataSource ds = null; 并使用以下方式初始化:

        Context initContext = new InitialContext();
        ds = (DataSource)initContext.lookup(wrapper.getCSADBJNDIName());

【问题讨论】:

【参考方案1】:

根据我的经验,这通常是网络错误。您的查询已完成,但您的客户端仍在阻止它永远不会收到的网络响应。这就是为什么弹回应用服务器会在重置应用服务器中的所有内容时起作用,但弹回数据库服务器没有任何意义,因为它不是数据库问题。看看这个网站上的这个问题/答案......

Question on network timeouts

【讨论】:

我同意@unleashed,可能是网络错误,但也可能是客户端处理速度慢或者数组获取大小太小,这会导致网络往返次数过多 ["SQL*Net 是一个健谈的协议"]。在这种情况下,弹跳应用服务器会终止有问题的空闲会话,这是瓶颈。 感谢您的回复和参考,非常有用。在这种情况下,网络错误似乎更合理。如上面的代码所示,正在使用 tomcat DB 资源。有没有办法在 tomcat 中定义超时而不是使用 DriverManager?【参考方案2】:

检查会话是否为:

    inactive - 如果不活跃,等待状态是什么,可能是在等待客户端或网络。 存在任何涉及空闲会话的阻塞锁(例如,它对未提交的事务持有一些锁)。

还要检查连接风暴(即太多会话访问实例会导致严重的 CPU 问题)

【讨论】:

DBA 看不到服务器上的会话/查询。我的应用程序中的线程转储显示:“FileUpload”守护进程 prio=10 tid=0x00002af6d42c8000 nid=0x369 runnable [0x00002af6d87fe000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) 我很确定这不是连接风暴问题。我将这两个添加到 tomcat 资源定义中,但没有看到任何日志条目:validationQueryTimeout="3600" guessTimeout="60" 希望有任何输入 完整资源定义:

以上是关于Java JDBC Oracle SQL 查询每隔几个月就会挂起一次的主要内容,如果未能解决你的问题,请参考以下文章

如何用java语言中如何执行sql查询语句

java(jdbc)向oracle中插入记录

Java + Oracle 中的 SQL 查询

java程序,每隔3秒查询30条数据出来,对每条数据进行处理,处理完删除数据,查询数据时有时候报异常

使用 java Spark DataFrame 通过 jdbc 访问 Oracle

如何使用java对oracle数据库进行增删查改