[英]Investigating slow simple queries in JDBC and MySQL
PreparedStatement.executeQuery()
is taking ~20x longer to execute than if it were run directly via the shell. PreparedStatement.executeQuery()
的执行时间比直接通过 shell 运行的时间长约 20 倍。 I've logged with timers to determine that this method is the culprit.我已经用计时器记录以确定这种方法是罪魁祸首。
The query and some DB info ( ignoring the Java issue for the moment ):查询和一些数据库信息(暂时忽略 Java 问题):
mysql> SELECT username from users where user_id = 1;
// lightning fast // 闪电般的快
Running that same query 1,000 times via mysqlslap is also lightning fast.通过 mysqlslap 运行相同的查询 1000 次也非常快。
mysqlslap --create-schema=mydb --user=root -p --query="select username from phpbb_users where user_id = 1" --number-of-queries=1000 --concurrency=1
Benchmark
Average number of seconds to run all queries: 0.051 seconds
Minimum number of seconds to run all queries: 0.051 seconds
Maximum number of seconds to run all queries: 0.051 seconds
Number of clients running queries: 1
Average number of queries per client: 1000
The Problem: Performing the same query in JDBC slows things significantly.问题:在 JDBC 中执行相同的查询会显着减慢速度。 In a for loop calling the below
queryUsername()
1,000 times (this is called in the Main method, which isn't shown here) takes around 872ms.在 for 循环中调用以下
queryUsername()
1,000 次(在 Main 方法中调用,此处未显示)大约需要 872 毫秒。 That's ~17x slower.这慢了约 17 倍。 I've tracked down the heavy usage by placing timers in various spots (omitted some for brevity).
我通过在不同的位置放置计时器来追踪大量使用(为了简洁省略了一些)。 The primary suspect is
stmt.executeQuery()
which took 776ms of the 872ms runtime.主要嫌疑人是
stmt.executeQuery()
,它占用了 872 毫秒运行时间中的 776 毫秒。
public static String queryUsername() {
String username = "";
// DBCore.getConnection() returns HikariDataSource.getConnection() implementation exactly as per https://www.baeldung.com/hikaricp
try (Connection connection = DBCore.getConnection();
PreparedStatement stmt = connection.prepareStatement("SELECT username from phpbb_users where user_id = ?");) {
stmt.setInt(1, 1); // just looking for user_id 1 for now
// Google timer used to measure how long executeQuery() is taking
// Another Timer is used outside of this method call to see how long
// total execution takes.
// Approximately 1 second in for loop calling this method 1000 times
Stopwatch s = Stopwatch.createStarted();
try (ResultSet rs = stmt.executeQuery();) {
s.stop(); // stopping the timer after executeQuery() has been called
timeElapsed += s.elapsed(TimeUnit.MICROSECONDS);
while (rs.next())
{
username = rs.getString("username"); // the query returns 1 record
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return username;
}
Additional context and things tried :其他上下文和尝试的事情:
SHOW OPEN TABLES
has several tables open, but all have In_use=0 and Name_locked=0. SHOW OPEN TABLES
有几个打开的表,但都具有 In_use=0 和 Name_locked=0。SHOW FULL PROCESSLIST
looks healthy. SHOW FULL PROCESSLIST
看起来很健康。Don't reconnect each time.不要每次都重新连接。 Open the connection at the start;
一开始就打开连接; reuse it until the web page (or program) is finished.
重复使用它,直到 web 页面(或程序)完成。
Chances are that you are comparing different realities.您可能正在比较不同的现实。
When running mysqlslap you are most likely using Unix Domain Sockets in the communication between the tool and MySQL server.运行 mysqlslap 时,您很可能在工具和 MySQL 服务器之间的通信中使用 Unix 域 Sockets。 Try changing that to TCP and you should observe an immediate performance drop.
尝试将其更改为 TCP,您应该会立即观察到性能下降。 Connector/J, on the other hand, creates TCP based connections by default (Unix Domain Sockets can be used but only by using a third party library).
另一方面,Connector/J 默认创建基于 TCP 的连接(可以使用 Unix 域 Sockets,但只能使用第三方库)。
Also, in mysqlslap you are running a simple query directly, which are handled by a COM_QUERY protocol command.此外,在 mysqlslap 中,您直接运行一个简单的查询,由 COM_QUERY 协议命令处理。 In the Java sample you are preparing the query first and then executing it.
在 Java 示例中,您首先准备查询,然后执行它。 Depending on how Connector/J is configured this may result in a single COM_QUERY protocol command or a pair of commands, namely, COM_STMT_PREPARE and COM_STMT_EXECUTE.
根据连接器/J 的配置方式,这可能会导致单个 COM_QUERY 协议命令或一对命令,即 COM_STMT_PREPARE 和 COM_STMT_EXECUTE。 Connector/J is also affected by how its statement caches are configured (and/or the CP ones).
Connector/J 还受其语句缓存的配置方式(和/或 CP 缓存)的影响。 However, you are only measuring the
executeQuery
part so, theoretically, Connector/J could be being favored here.但是,您只测量
executeQuery
部分,因此理论上,Connector/J 可能在这里受到青睐。
Finally, unless you actually come up with a use case where you guarantee that both executions are effectively doing the same work under the same circumstances, you can compare results and point out differences, but you can't take any conclusions from it.最后,除非你真的想出一个用例,保证两个执行在相同情况下有效地完成相同的工作,否则你可以比较结果并指出差异,但你不能从中得出任何结论。 For example, it's not that hard to introduce caches and make those simple iterations even completely skip communicating to the server... that would make things extremely fast.
例如,引入缓存并让那些简单的迭代甚至完全跳过与服务器的通信并不难……这将使事情变得非常快。
move borrowing connection and Stopwatch related code out of method.将借用连接和秒表相关代码移出方法。 then measure as:
然后测量为:
Stopwatch s = Stopwatch.createStarted();
try (Connection con = ....) {
for (int i=0; i < 1000; i++) {
queryUsername( con );
}
}
s.stop();
print s.elapsed(TimeUnit.MICROSECONDS);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.