使用远程 tcp 服务器的 H2 性能下降
H2 performance degrade using remote tcp server
我有一个使用单个连接的简单应用程序,流程是这样的:
SELECT QUERY
CONNECTION CLOSE
for(..) //thousands of iterations
{
SIMPLE SELECT QUERY
}
BUSINESS LOGIC
CONNECTION CLOSE
for(..) //thousands of iterations
{
SIMPLE SELECT QUERY
}
BUSINESS LOGIC
CONNECTION CLOSE
当我使用嵌入式连接模式时,应用程序在大约 20 秒内结束,但是当我切换到服务器模式时,性能下降:
- 本地主机:110 秒
- 远程服务器(局域网):30多分钟
每个查询检索少量数据。
如此糟糕的表现有解释吗?如何在不重写代码的情况下加速应用程序?
感谢任何帮助
把你的问题比作喝杯咖啡。
如果您在 table/workdesk 上访问您的咖啡,它类似于在内存中访问您的数据库(H2 嵌入式)
大约需要几秒(咖啡)和微秒(嵌入 h2)
如果您需要去厨房喝杯咖啡,这将花费您从椅子到厨房 + 返回的旅行时间(响应)。
厨房类似于 TCP 访问或文件访问本地数据库。
它需要你几分钟(咖啡)和一位数毫秒(h2 tcp 本地主机或本地文件)
如果你需要去外面的咖啡店喝杯咖啡,你至少需要 15 分钟才能在远程机器上的 h2 tcp 中得到一杯咖啡(和年龄(至少 2 位数毫秒) )
现在是问题,您想去咖啡店几次?
如果我给你一个迭代(for 循环)超过 1000 次到咖啡店,你会在第二次或第三次之后问我是否在开玩笑?你为什么要在网络上的长传输时间上打扰依赖 IO 的系统?
所以在您的情况下,如果您将第二个 for 循环减少为一个 SQL 查询,您将在本地获得出色的性能,当然尤其是在远程方式上。
我希望我能用咖啡来解释你的情况,因为它能更好地解释问题。
所以要回答最后一个问题,您必须重写 "thousands of iterations" for 循环。
更新 1
忘记了,如果您的 for 循环正在编写循环 (update/insert),您可以使用批量查询。如何使用它们取决于您使用的语言。在数据库上进行操作之前,批处理将向数据库提供一堆(例如数百个)insert/updates。
我用你提供的 H2 版本做了以下测试。
- 创建一个包含 500.000 条记录的测试数据库,数据库大小为 990 MB
java -cp h2-1.3.168.jar;. PerfH2 create
- select 索引列上的随机 8.000 条记录
在嵌入式模式下 -> 1.7 秒
java -cp h2-1.3.168.jar;. PerfH2 embedded
在服务器(本地主机)模式下 -> 2.6 秒
java -cp h2-1.3.168.jar;. PerfH2 server
如果您在使用服务器模式 jdbc:h2:tcp://localhost/your_database
时遇到问题,那么您的环境或访问服务器模式的方式似乎有问题。尝试使用剥离完成的应用程序并检查问题是否仍然存在。如果您也可以使用精简版重现该问题,请 post 代码。
在下面找到用于测试的代码。
public class PerfH2 {
public static void main(String[] args) throws SQLException {
if (null == args[0]) {
showUsage();
return;
}
long start = System.currentTimeMillis();
switch (args[0]) {
case "create":
createDatabase();
break;
case "embedded":
try (Connection conn = getEmbeddedConnection()) {
execSelects(conn);
}
break;
case "server":
try (Connection conn = getServerConnection()) {
execSelects(conn);
}
break;
default:
showUsage();
}
System.out.printf("duration: %d%n", System.currentTimeMillis() - start);
}
private static Connection getServerConnection() throws SQLException {
return DriverManager.getConnection("jdbc:h2:tcp://localhost/perf_test", "sa", "");
}
private static Connection getEmbeddedConnection() throws SQLException {
return DriverManager.getConnection("jdbc:h2:d:/temp/perf_test", "sa", "");
}
private static void execSelects(final Connection conn) throws SQLException {
Random rand = new Random(1);
String selectSql = "SELECT * FROM TEST_TABLE WHERE ID = ?";
PreparedStatement selectStatement = conn.prepareStatement(selectSql);
int count = 0;
for (int i = 0; i < 8000; i++) {
selectStatement.setInt(1, rand.nextInt(500_000));
ResultSet rs = selectStatement.executeQuery();
while (rs.next()) {
count++;
}
}
System.out.printf("retrieved rows: %d%n", count);
}
private static void createDatabase() throws SQLException {
try (Connection conn = DriverManager.getConnection("jdbc:h2:d:/temp/perf_test", "sa", "")) {
String createTable = "CREATE TABLE TEST_TABLE(ID INT, NAME VARCHAR(1024))";
conn.createStatement().executeUpdate(createTable);
String insertSql = "INSERT INTO TEST_TABLE VALUES(?, ?)";
PreparedStatement insertStmnt = conn.prepareStatement(insertSql);
StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1024 / 10; i++) {
sb.append("[cafebabe]");
}
String value = sb.toString();
int count = 0;
for (int i = 0; i < 50; i++) {
insertStmnt.setInt(1, i);
insertStmnt.setString(2, value);
count += insertStmnt.executeUpdate();
}
System.out.printf("inserted rows: %d%n", count);
conn.commit();
String createIndex = "CREATE INDEX TEST_INDEX ON TEST_TABLE(ID)";
conn.createStatement().executeUpdate(createIndex);
}
}
private static void showUsage() {
System.out.println("usage: PerfH2 [create|embedded|server]");
}
}
我有一个使用单个连接的简单应用程序,流程是这样的:
SELECT QUERY
CONNECTION CLOSE
for(..) //thousands of iterations
{
SIMPLE SELECT QUERY
}
BUSINESS LOGIC
CONNECTION CLOSE
for(..) //thousands of iterations
{
SIMPLE SELECT QUERY
}
BUSINESS LOGIC
CONNECTION CLOSE
当我使用嵌入式连接模式时,应用程序在大约 20 秒内结束,但是当我切换到服务器模式时,性能下降:
- 本地主机:110 秒
- 远程服务器(局域网):30多分钟
每个查询检索少量数据。
如此糟糕的表现有解释吗?如何在不重写代码的情况下加速应用程序?
感谢任何帮助
把你的问题比作喝杯咖啡。
如果您在 table/workdesk 上访问您的咖啡,它类似于在内存中访问您的数据库(H2 嵌入式) 大约需要几秒(咖啡)和微秒(嵌入 h2)
如果您需要去厨房喝杯咖啡,这将花费您从椅子到厨房 + 返回的旅行时间(响应)。 厨房类似于 TCP 访问或文件访问本地数据库。 它需要你几分钟(咖啡)和一位数毫秒(h2 tcp 本地主机或本地文件)
如果你需要去外面的咖啡店喝杯咖啡,你至少需要 15 分钟才能在远程机器上的 h2 tcp 中得到一杯咖啡(和年龄(至少 2 位数毫秒) )
现在是问题,您想去咖啡店几次? 如果我给你一个迭代(for 循环)超过 1000 次到咖啡店,你会在第二次或第三次之后问我是否在开玩笑?你为什么要在网络上的长传输时间上打扰依赖 IO 的系统?
所以在您的情况下,如果您将第二个 for 循环减少为一个 SQL 查询,您将在本地获得出色的性能,当然尤其是在远程方式上。
我希望我能用咖啡来解释你的情况,因为它能更好地解释问题。
所以要回答最后一个问题,您必须重写 "thousands of iterations" for 循环。
更新 1
忘记了,如果您的 for 循环正在编写循环 (update/insert),您可以使用批量查询。如何使用它们取决于您使用的语言。在数据库上进行操作之前,批处理将向数据库提供一堆(例如数百个)insert/updates。
我用你提供的 H2 版本做了以下测试。
- 创建一个包含 500.000 条记录的测试数据库,数据库大小为 990 MB
java -cp h2-1.3.168.jar;. PerfH2 create
- select 索引列上的随机 8.000 条记录
在嵌入式模式下 -> 1.7 秒
java -cp h2-1.3.168.jar;. PerfH2 embedded
在服务器(本地主机)模式下 -> 2.6 秒
java -cp h2-1.3.168.jar;. PerfH2 server
如果您在使用服务器模式 jdbc:h2:tcp://localhost/your_database
时遇到问题,那么您的环境或访问服务器模式的方式似乎有问题。尝试使用剥离完成的应用程序并检查问题是否仍然存在。如果您也可以使用精简版重现该问题,请 post 代码。
在下面找到用于测试的代码。
public class PerfH2 {
public static void main(String[] args) throws SQLException {
if (null == args[0]) {
showUsage();
return;
}
long start = System.currentTimeMillis();
switch (args[0]) {
case "create":
createDatabase();
break;
case "embedded":
try (Connection conn = getEmbeddedConnection()) {
execSelects(conn);
}
break;
case "server":
try (Connection conn = getServerConnection()) {
execSelects(conn);
}
break;
default:
showUsage();
}
System.out.printf("duration: %d%n", System.currentTimeMillis() - start);
}
private static Connection getServerConnection() throws SQLException {
return DriverManager.getConnection("jdbc:h2:tcp://localhost/perf_test", "sa", "");
}
private static Connection getEmbeddedConnection() throws SQLException {
return DriverManager.getConnection("jdbc:h2:d:/temp/perf_test", "sa", "");
}
private static void execSelects(final Connection conn) throws SQLException {
Random rand = new Random(1);
String selectSql = "SELECT * FROM TEST_TABLE WHERE ID = ?";
PreparedStatement selectStatement = conn.prepareStatement(selectSql);
int count = 0;
for (int i = 0; i < 8000; i++) {
selectStatement.setInt(1, rand.nextInt(500_000));
ResultSet rs = selectStatement.executeQuery();
while (rs.next()) {
count++;
}
}
System.out.printf("retrieved rows: %d%n", count);
}
private static void createDatabase() throws SQLException {
try (Connection conn = DriverManager.getConnection("jdbc:h2:d:/temp/perf_test", "sa", "")) {
String createTable = "CREATE TABLE TEST_TABLE(ID INT, NAME VARCHAR(1024))";
conn.createStatement().executeUpdate(createTable);
String insertSql = "INSERT INTO TEST_TABLE VALUES(?, ?)";
PreparedStatement insertStmnt = conn.prepareStatement(insertSql);
StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1024 / 10; i++) {
sb.append("[cafebabe]");
}
String value = sb.toString();
int count = 0;
for (int i = 0; i < 50; i++) {
insertStmnt.setInt(1, i);
insertStmnt.setString(2, value);
count += insertStmnt.executeUpdate();
}
System.out.printf("inserted rows: %d%n", count);
conn.commit();
String createIndex = "CREATE INDEX TEST_INDEX ON TEST_TABLE(ID)";
conn.createStatement().executeUpdate(createIndex);
}
}
private static void showUsage() {
System.out.println("usage: PerfH2 [create|embedded|server]");
}
}