偶尔 NullPointerException in ResultSetImpl.checkColumnBounds 或 ResultSetImpl.getStringInternal

2022-09-03 15:41:42

首先,请不要将其作为“是什么以及如何解决”的副本关闭。我知道是什么,我知道如何在我自己的代码中解决它,但是当它被mysql-connector-java-5.1.36-bin.jar抛出时,我无法控制。NullPointerExceptionNullPointerException

我们在mySQL数据库上运行通用数据库查询时遇到了异常,这种查询大部分时间都有效。在部署新版本后,我们开始看到此异常,但发生此异常的查询在很长一段时间内都没有更改。

下面是查询的外观(带有一些必要的简化)。我用一些之前和之后执行的逻辑包围了它。实际的代码并不都在一个方法中,但我把它放在一个块中,以使其更容易理解。

Connection conn = ... // the connection is open
...
for (String someID : someIDs) {
    SomeClass sc = null;
    PreparedStatement
        stmt = conn.prepareStatement ("SELECT A, B, C, D, E, F, G, H FROM T WHERE A = ?");
    stmt.setString (1, "someID");
    ResultSet res = stmt.executeQuery ();
    if (res.next ()) {
        sc = new SomeClass ();
        sc.setA (res.getString (1));
        sc.setB (res.getString (2));
        sc.setC (res.getString (3));
        sc.setD (res.getString (4));
        sc.setE (res.getString (5));
        sc.setF (res.getInt (6));
        sc.setG (res.getString (7));
        sc.setH (res.getByte (8)); // the exception is thrown here
    }
    stmt.close ();
    conn.commit ();
    if (sc != null) {
        // do some processing that involves loading other records from the
        // DB using the same connection
    }
}
conn.close();

res.getByte(8)导致 具有以下调用堆栈的 a:NullPointerException

com.mysql.jdbc.ResultSetImpl.checkColumnBounds(ResultSetImpl.java:763) com.mysql.jdbc.ResultSetImpl.getStringInternal(ResultSetImpl.java:5251) com.mysql.jdbc.ResultSetImpl.getString(ResultSetImpl.java:5173) com.mysql.jdbc.ResultSetImpl.getByte(ResultSetImpl.java:1650) org.apache.tomcat.dbcp.dbcp2.DelegatingResultSet.getByte(DelegatingResultSet.java:206) org.apache.tomcat.dbcp.dbcp2.DelegatingResultSet.getByte(DelegatingResultSet.java:206)

我搜索了相关mysql连接器版本的源代码,并找到了这个(取自这里):

756 protected final void checkColumnBounds(int columnIndex) throws SQLException {
757     synchronized (checkClosed().getConnectionMutex()) {    
758         if ((columnIndex < 1)) {    
759             throw SQLError.createSQLException(    
760                   Messages.getString("ResultSet.Column_Index_out_of_range_low",    
761                   new Object[] { Integer.valueOf(columnIndex), Integer.valueOf(this.fields.length) }), SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
762                   getExceptionInterceptor());   
763         } else if ((columnIndex > this.fields.length)) {    
764             throw SQLError.createSQLException(    
765                   Messages.getString("ResultSet.Column_Index_out_of_range_high",    
766                   new Object[] { Integer.valueOf(columnIndex), Integer.valueOf(this.fields.length) }), SQLError.SQL_STATE_ILLEGAL_ARGUMENT,   
767                   getExceptionInterceptor());
768         }
769
770         if (this.profileSql || this.useUsageAdvisor) {
771             this.columnUsed[columnIndex - 1] = true;
772         }
773     }
774 }

如您所见,异常发生在以下行:

} else if ((columnIndex > this.fields.length)) {   

这意味着以某种方式成为.this.fieldsnull

我能找到的最接近的东西是这个问题,它没有答案。

我怀疑问题不在我发布的查询中。也许由于我们在同一连接上运行的其他一些语句,实例出了问题。我只能说,我们在执行每个语句后立即关闭它,并从其中读取数据。ConnectionResultSet

编辑 (1/19/2017):

我无法在开发环境中重现错误。我认为这可能是在长时间使用相同的连接时触发的一些mysql连接器错误。我将上述循环限制为一次最多加载6个元素。此外,我们还将 mysql 连接器版本升级到了 5.1.40。

我们仍然在 中看到 s,但这次是在不同的位置。NullPointerExceptionResultSetImpl

堆栈跟踪为:

com.mysql.jdbc.ResultSetImpl.getStringInternal(ResultSetImpl.java:5294) com.mysql.jdbc.ResultSetImpl.getString(ResultSetImpl.java:5151) org.apache.tomcat.dbcp.dbcp2.DelegatingResultSet.getString(DelegatingResultSet.java:198) org.apache.tomcat.dbcp.dbcp2.DelegatingResultSet.getString(DelegatingResultSet.java:198)

这意味着这次异常是由于我们的一个调用而引发的(我不知道是哪一个)。res.getString()

我找到了mysql-connector-java-5.1.40-bin的源代码.jar在这里,相关代码是:

5292            // Handles timezone conversion and zero-date behavior
5293
5294            if (checkDateTypes && !this.connection.getNoDatetimeStringSync()) {
5295                switch (metadata.getSQLType()) {

这意味着为空。 是类型的实例变量,它在 的构造函数中初始化,并且仅在调用时才设置为 null(它由 调用,根据源代码中的文档,在调用时调用)。在从中读取所有数据之前,我们绝对不会关闭。this.connectionthis.connectionMySQLConnectionResultSetImplpublic void realClose(boolean calledExplicitly)public void close()ResultSet.close()ResultSet

任何想法如何进行?


答案 1

自从我发布这个问题以来已经很长一段时间了,我想发布一个答案,描述导致这个问题的确切情况 。NullPointerException

我认为这可能有助于将来遇到这种令人困惑的异常的读者跳出框框思考,因为我几乎有充分的理由怀疑这是一个mysql连接器错误,即使它毕竟不是。

在调查此异常时,我确定我的应用程序在尝试从中读取数据时不可能关闭数据库连接,因为我的数据库连接不会在线程之间共享,并且如果同一线程关闭连接然后尝试访问它,则应引发不同的异常(某些)。这是我怀疑mysql连接器错误的主要原因。SQLException

事实证明,毕竟有两个线程访问同一连接。这很难弄清楚的原因是其中一个线程是垃圾回收器线程

回到我发布的代码:

Connection conn = ... // the connection is open
...
for (String someID : someIDs) {
    SomeClass sc = null;
    PreparedStatement
        stmt = conn.prepareStatement ("SELECT A, B, C, D, E, F, G, H FROM T WHERE A = ?");
    stmt.setString (1, "someID");
    ResultSet res = stmt.executeQuery ();
    if (res.next ()) {
        sc = new SomeClass ();
        sc.setA (res.getString (1));
        sc.setB (res.getString (2));
        sc.setC (res.getString (3));
        sc.setD (res.getString (4));
        sc.setE (res.getString (5));
        sc.setF (res.getInt (6));
        sc.setG (res.getString (7));
        sc.setH (res.getByte (8)); // the exception is thrown here
    }
    stmt.close ();
    conn.commit ();
    if (sc != null) {
        // do some processing that involves loading other records from the
        // DB using the same connection
    }
}
conn.close();

问题在于“执行一些涉及使用相同连接从数据库加载其他记录的处理”部分,不幸的是,我没有在原始问题中包括该处理,因为我认为问题不存在。

放大到该部分,我们有:

if (sc != null) {
    ...
    someMethod (conn);
    ...
}

看起来像这样:someMethod

public void someMethod (Connection conn) 
{
    ...
    SomeOtherClass instance = new SomeOtherClass (conn);
    ...
}

SomeOtherClass看起来像这样(当然我在这里简化):

public class SomeOtherClass
{
    Connection conn;

    public SomeOtherClass (Connection conn) 
    {
        this.conn = conn;
    }

    protected void finalize() throws Throwable
    { 
        if (this.conn != null)
            conn.close();
    }

}

SomeOtherClass在某些情况下,可能会创建自己的数据库连接,但在其他情况下可以接受现有连接,例如我们在此处的连接。

如您所见,该部分包含对接受开放连接作为参数的调用。 将连接传递给 的本地实例。 有一个关闭连接的方法。someMethodsomeMethodSomeOtherClassSomeOtherClassfinalize

现在,在返回后,将有资格进行垃圾回收。当它被垃圾回收时,其方法由垃圾回收器线程调用,该线程关闭连接。someMethodinstancefinalize

现在我们回到 for 循环,它继续使用相同的连接执行 SELECT 语句,该连接可能随时被垃圾回收器线程关闭。

如果垃圾回收器线程碰巧关闭了连接,而应用程序线程处于依赖于打开连接的某个 mysql 连接器方法的中间,则可能会发生。NullPointerException

删除该方法解决了问题。finalize

我们通常不会在类中重写该方法,这使得很难找到 bug。finalize


答案 2

据我所知,发生这种情况的唯一方式是有人/某事在 ResultSet.getString() 被调用后立即调用 ResultSet.close(),但在它返回之前。

这可能是您的应用程序(它是多线程的,还是具有棘手的处理路径),或者您可能在这段时间内的某个时候丢失了连接,这会导致驱动程序隐式关闭连接,从而关闭所有打开的语句,从而关闭所有打开的结果集。

不过,你不应该得到一个NPE,我们应该提出一个更好的SQLException。

您可以检查mysqld错误日志,看看那里是否发生了任何暗示连接正在丢失的事情?

-- http://bugs.mysql.com/bug.php?id=41484


推荐