使用远程 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 秒内结束,但是当我切换到服务器模式时,性能下降:

每个查询检索少量数据

如此糟糕的表现有解释吗?如何在不重写代码的情况下加速应用程序?

感谢任何帮助

把你的问题比作喝杯咖啡。

如果您在 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 版本做了以下测试。

  1. 创建一个包含 500.000 条记录的测试数据库,数据库大小为 990 MB
    java -cp h2-1.3.168.jar;. PerfH2 create
  2. 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]");
    }
}