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 中使用WHERE
、ORDER BY
和LIMIT
子句来实现该逻辑。获取大表中的所有行通常是此类问题的错误解决方案。 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 有 WHERE
和 LIMIT
子句的原因。
【讨论】:
正如我所写,另一个 MySQL 驱动程序,特别是 PHP 和来自 bash 的本机 mysql 查询执行此查询非常快。而且我有列id
的索引,所以MySQL 必须比以往更快地给我第一条记录。我坚持认为这不是 MySQL 问题,并且我在问题中描述了为什么我这么认为。
第一行时间并不能有效地表明您对稀缺资源(MySQL 服务器)的负担。
@abr_*** 我绝对同意这个答案。如果您需要 100 行,请输入 Limit 100。如果您不知道需要多少行,请翻阅结果。以上是关于JDBC 连接器在 SELECT 上太慢的主要内容,如果未能解决你的问题,请参考以下文章
java连接sqlserver太慢!DriverManager.getConnection
Java通过jdbc连接DB2能使用with as( select ...吗