You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JMH测试Java SQL查询:连接重复创建致结果无效

解决JMH中数据库连接重复创建的问题

问题根源

你当前代码的核心问题在于对JMH的@Fork注解参数理解有误:@Fork(value = 1, warmups = 5)表示启动5个独立的预热JVM进程,再启动1个正式测试JVM进程。每个Fork进程的静态变量都会重新初始化,所以每个预热进程里的conn都是null,导致每次都重建连接。这显然不是你想要的“同一进程内复用连接做预热迭代”的效果。

解决方案

要实现连接复用,需要结合JMH的@State、@Setup、@TearDown注解,同时调整预热配置的方式:

  1. 用@State管理测试状态:将数据库连接定义为测试类的实例变量,通过@State(Scope.Benchmark)让JMH在整个Fork进程内维护一个测试类实例,确保连接复用。
  2. 用@Setup(Level.Trial)初始化连接:Level.Trial级别表示在每个Fork进程的所有迭代(包括预热和正式测试)开始前执行一次,保证连接只创建一次。
  3. 用@Warmup定义预热迭代:替换@Fork的warmups参数,改用@Warmup(iterations = 5)指定在同一进程内执行5次预热迭代。
  4. 用@TearDown(Level.Trial)清理资源:在Fork进程结束后关闭连接,避免资源泄漏。

修改后的完整代码

import org.openjdk.jmh.annotations.*;
import java.sql.*;
import java.util.concurrent.TimeUnit;

@State(Scope.Benchmark)
public class SqlBenchmark {
    private static final String url = "jdbc:postgresql://localhost:5432/postgres";
    private static final String user = "your_username"; // 替换为你的数据库用户名
    private static final String password = "your_password"; // 替换为你的数据库密码
    private Connection conn;

    @Setup(Level.Trial)
    public void initConnection() throws SQLException {
        conn = DriverManager.getConnection(url, user, password);
        // 仅用于验证连接创建,性能测试中建议移除IO输出
        System.out.println("Connection established successfully.");
    }

    @TearDown(Level.Trial)
    public void closeConnection() throws SQLException {
        if (conn != null && !conn.isClosed()) {
            conn.close();
            System.out.println("Connection closed.");
        }
    }

    @Benchmark
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @BenchmarkMode(Mode.SingleShotTime)
    @Fork(1)
    @Warmup(iterations = 5)
    public String executeRawSql() throws SQLException {
        String query = "select * from EmpDetails where EMP_ID=123";
        try (Statement stmt = conn.createStatement()) {
            ResultSet rs = stmt.executeQuery(query);
            if (rs.next()) {
                return rs.getString(1);
            }
        }
        return "";
    }

    // 新增PreparedStatement对比测试方法
    @Benchmark
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @BenchmarkMode(Mode.SingleShotTime)
    @Fork(1)
    @Warmup(iterations = 5)
    public String executePreparedSql() throws SQLException {
        String query = "select * from EmpDetails where EMP_ID=?";
        try (PreparedStatement pstmt = conn.prepareStatement(query)) {
            pstmt.setInt(1, 123);
            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) {
                return rs.getString(1);
            }
        }
        return "";
    }
}

关键细节说明

  • @State(Scope.Benchmark):确保整个Fork进程中只有一个SqlBenchmark实例,连接变量conn不会被重复初始化。
  • @Setup(Level.Trial):每个Fork进程仅执行一次连接初始化,预热和正式测试都复用这个连接,排除连接建立的开销对executeQuery性能的干扰。
  • @Warmup(iterations = 5):在同一进程内执行5次预热迭代,让JVM完成JIT编译等优化,确保正式测试的性能数据准确。
  • 避免IO操作干扰:@Benchmark方法内不要用System.out.println,IO开销会严重影响性能测试结果,必要时用日志框架替代。

内容的提问来源于stack exchange,提问作者MnkDude

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 01:31:35