JMH测试Java SQL查询:连接重复创建致结果无效
解决JMH中数据库连接重复创建的问题
问题根源
你当前代码的核心问题在于对JMH的@Fork注解参数理解有误:@Fork(value = 1, warmups = 5)表示启动5个独立的预热JVM进程,再启动1个正式测试JVM进程。每个Fork进程的静态变量都会重新初始化,所以每个预热进程里的conn都是null,导致每次都重建连接。这显然不是你想要的“同一进程内复用连接做预热迭代”的效果。
解决方案
要实现连接复用,需要结合JMH的@State、@Setup、@TearDown注解,同时调整预热配置的方式:
- 用
@State管理测试状态:将数据库连接定义为测试类的实例变量,通过@State(Scope.Benchmark)让JMH在整个Fork进程内维护一个测试类实例,确保连接复用。 - 用
@Setup(Level.Trial)初始化连接:Level.Trial级别表示在每个Fork进程的所有迭代(包括预热和正式测试)开始前执行一次,保证连接只创建一次。 - 用
@Warmup定义预热迭代:替换@Fork的warmups参数,改用@Warmup(iterations = 5)指定在同一进程内执行5次预热迭代。 - 用
@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
相关产品推荐
相关产品推荐

