JDBC 连接器在 SELECT 上太慢

Posted

技术标签:

【中文标题】JDBC 连接器在 SELECT 上太慢【英文标题】:JDBC connector is too slow on SELECT 【发布时间】:2017-10-19 13:47:22 【问题描述】:

这个问题在网上并不鲜见,但是我对mysql服务器做了一些优化工作来解决这个问题,但没有得到结果。所以一开始我使用maven的包mysql:mysql-connector-java:6.0.6。 我试着运行这段代码:

try 
            mysqlConnection = DriverManager.getConnection(DatabaseUtils.mysqlUrl, DatabaseUtils.mysqlUser, DatabaseUtils.mysqlPassword);
            PreparedStatement valuesStatement = "SELECT * FROM `test` ORDER BY `id`"
            ResultSet cursor = valuesStatement.executeQuery();
            double value = 0;
            if (cursor.next())
                value = cursor.getDouble("value");
         catch (SQLException sqlEx) 
            sqlEx.printStackTrace();
         finally 
            cursor.close();
            pricesStatement.close();
        

我的表中有很多记录。它大约是百万,但每天都会增加大约一千条记录。所以当这个简单的例子执行了 30 秒时,我感到非常惊讶。我用谷歌搜索了我的问题,我发现只有“使用池”、“调整 mysql 服务器”、“尝试解释选择”。但我注意到执行时间与行数有关。于是我查看了驱动的代码,发现:

TextResultsetReader::read():

        while(true) 
            if(row == null) 
                rows = new ResultsetRowsStatic(rowList, cdef);
                break;
            

            if(maxRows == -1 || rowList.size() < maxRows) 
                rowList.add(row);
            

            row = (ResultsetRow)this.protocol.read(ResultsetRow.class, trf);
        

这意味着即使我只想获取一个行,驱动程序也会获取所有查询的行并让我排在第一位。手册建议使用“setFetchSize”仅获取 n 条记录。但它不起作用。无论如何,驱动程序代码都会获取所有数据。于是我发现有两个记录集:ResultRowsStatic 和 ResultSetStreaming。第二个似乎只在我需要查询数据时才获取数据。如何使用 ResultRowsStreaming?我发现它只在代码中。参数“fetchSize”必须等于 -2147483648。我确实尝试过,它奏效了!现在“executeQuery()”的执行时间大约为 0.0007 秒。这对我来说非常快。但是等等..我的脚本无论如何都需要 30 秒。为什么?我在执行查询后调试了代码。之后只有两种“关闭”方法。有什么问题?这是真的,“cursor.close()”占用了剩下的时间。我再次查看了库代码并到达了ResultsetRowsStreaming::close():

boolean hadMore = false;
int howMuchMore = 0;
synchronized(mutex) 
    while(this.next() != null) 
        hadMore = true;
        ++howMuchMore;
        if(howMuchMore % 100 == 0) 
            Thread.yield();
        
    

    if(conn != null) 
        if(!((Boolean)this.protocol.getPropertySet().getBooleanReadableProperty("clobberStreamingResults").getValue()).booleanValue() && ((Integer)this.protocol.getPropertySet().getIntegerReadableProperty("netTimeoutForStreamingResults").getValue()).intValue() > 0) 
            int oldValue = this.protocol.getServerSession().getServerVariable("net_write_timeout", 60);
            this.protocol.clearInputStream();

            try 
                this.protocol.sqlQueryDirect((StatementImpl)null, "SET net_write_timeout=" + oldValue, (String)this.protocol.getPropertySet().getStringReadableProperty("characterEncoding").getValue(), (PacketPayload)null, -1, false, (String)null, (ColumnDefinition)null, (GetProfilerEventHandlerInstanceFunction)null, this.resultSetFactory);
             catch (Exception var9) 
                throw ExceptionFactory.createException(var9.getMessage(), var9, this.exceptionInterceptor);
            
        

        if(((Boolean)this.protocol.getPropertySet().getBooleanReadableProperty("useUsageAdvisor").getValue()).booleanValue() && hadMore) 
            ProfilerEventHandler eventSink = ProfilerEventHandlerFactory.getInstance(conn.getSession());
            eventSink.consumeEvent(new ProfilerEventImpl(0, "", this.owner.getCurrentCatalog(), this.owner.getConnectionId(), this.owner.getOwningStatementId(), -1, System.currentTimeMillis(), 0L, Constants.MILLIS_I18N, (String)null, (String)null, Messages.getString("RowDataDynamic.2") + howMuchMore + Messages.getString("RowDataDynamic.3") + Messages.getString("RowDataDynamic.4") + Messages.getString("RowDataDynamic.5") + Messages.getString("RowDataDynamic.6") + this.owner.getPointOfOrigin()));
        
    

此代码无条件地获取所有其余数据,仅用于记录我未获取的记录数。真奇怪。如果附加了记录器,那将是合理的。但在我的情况下,这段代码在 30 秒内计算未获取的行数,然后......什么也不做。而这个问题我无法解决,因为没有参数可以告诉代码不要计算行数。

现在我不知道下一步该做什么。查询时间对我来说很慢。例如 php 的 mysql 驱动程序在 0.0004-0.001 秒内执行此查询。

所以那些使用mysql-connector for Java的人,请告诉我你有这些问题吗?如果没有,您能否发布任何示例来绕过上述问题?也许您使用其他连接器。那么请告诉我,该怎么办?

【问题讨论】:

如果我的理解是正确的 - 你得到了你想要的结果,但语句在关闭之前继续执行选择?你试过valuesStatement.cancel()吗? See here 我尝试在 close() 之前添加 cancel() 但它也无济于事。所有其余数据均由驱动程序获取。我正在使用jar,所以我无法编辑驱动程序的代码。 如果你只想要一行,告诉服务器只发送一行,使用LIMIT 1 就我而言,我不知道应该设置什么限制。我的目的比这个例子更复杂。我选择所有数据,通过非线性算法制作几个数组,当我得到需要的数组数量时,我就停止获取数据。所以我不知道我需要多少行:一万行。 尊重您关于不知道需要多少行的评论:请edit 您的问题告诉我们您决定需要哪些行的逻辑。解决此问题后,您将在 SQL 中使用WHEREORDER BYLIMIT 子句来实现该逻辑。获取大表中的所有行通常是此类问题的错误解决方案。 thedailywtf.com 中提到你的工作就是这种错误。 【参考方案1】:

你的 SQL 查询说

SELECT * FROM test ORDER BY id

通过该查询,您正在指示您的 MySQL 服务器序列化您的test 表的每一行每一列,并将其发送到您的Java 程序。所以,MySQL 服从。你有一张大桌子。所以你对 MySQL 的指导需要时间。是的,表格中的行越多,花费的时间就越长。这不是 JDBC 或驱动程序的问题;这是您使用的 SQL 的问题。

从您的示例代码看来,您需要一列——名为value——来自一行——第一行——在你的表中。您可以使用以下 SQL 语句来完成:

 SELECT value FROM test ORDER BY id LIMIT 1

如果您的id 列是您的表的主键,这将很快。

SQL 的重点是允许您的表包含如此多的行,以至于在短时间内将它们全部提取到您的 Java(或其他)程序中是不合理的。这就是 SQL 有 WHERELIMIT 子句的原因。

【讨论】:

正如我所写,另一个 MySQL 驱动程序,特别是 PHP 和来自 bash 的本机 mysql 查询执行此查询非常快。而且我有列id 的索引,所以MySQL 必须比以往更快地给我第一条记录。我坚持认为这不是 MySQL 问题,并且我在问题中描述了为什么我这么认为。 第一行时间并不能有效地表明您对稀缺资源(MySQL 服务器)的负担。 @abr_*** 我绝对同意这个答案。如果您需要 100 行,请输入 Limit 100。如果您不知道需要多少行,请翻阅结果。

以上是关于JDBC 连接器在 SELECT 上太慢的主要内容,如果未能解决你的问题,请参考以下文章

java连接sqlserver太慢!DriverManager.getConnection

Java通过jdbc连接DB2能使用with as( select ...吗

到链接服务器的 JDBC 连接

Hive JDBC 连接超时

诡异,java连接sql中 distinct 不生效,jdbc方式连接,用的是2000的数据库

在 JDBC 中关闭后 Postgresql 连接保持空闲