资讯详情 Jmeter+SQLite:接口测试与性能测试结果落库与查询分析实战
📅 2026/10/22 19:37:34
做接口测试和性能测试这些年Jmeter用得多了我发现一个特别实际的问题测试做完数据怎么存、怎么查、怎么分析。以前大家都是导出CSV或者JTL文件攒多了根本打不开就算打开了也没法快速筛数据。后来我在一个项目里把Jmeter的结果直接写进SQLite本地单文件存储用SQL查起来是真的方便。这个方案不需要额外部署数据库服务装上就能用尤其适合个人和中小团队做测试留痕和结果复盘。这篇文章我就把整套思路和完整实操写出来包括表结构设计、JSR223脚本怎么写、高并发下怎么批量入库以及我踩过的那些坑。1. 为什么要把Jmeter结果存进SQLite很多测试工程师用Jmeter跑完脚本后默认就是生成一个CSV文件或者JTL文件放在那里。数据量小的时候没什么感觉但用例一旦跑起来几千几万条请求落到文件里问题就来了。这节我把传统方式的痛点和SQLite方案的优势掰开讲清楚。1.1 传统结果保存方式的几个痛点先说CSV。CSV文件结构简单Excel直接能打开但它天生不适合做大量测试数据的存储和分析。第一文件超过几十兆之后Excel打开就卡Even用Python读也会占用不少内存。第二CSV是纯文本字段多了之后非常乱Jmeter的断言结果、响应体、请求参数混在一起很难定位某一条具体的请求。第三筛选和统计能力几乎为零你想查“昨天上午10点到11点之间某个接口响应时间超过3秒的请求”用CSV去做是一件很痛苦的事情。JTL文件也好不到哪里去。Jmeter默认的JTL是XML或者二进制格式XML解析慢二进制又没工具直接看。拿到一个JTL文件通常还得靠Jmeter重新打开倾听器去查看重放一遍花费时间不说图表渲染还慢。而且JTL文件只能记录采样器级别的结果想关联业务级别的上下文信息比如订单号、用户ID非常麻烦。还有一个很容易被忽略的问题CSV和JTL都没有追加写入能力续跑的场景很尴尬。你今天跑了一轮明天想接着跑新数据是继续写同一个文件还是新建一个新建了怎么合并分析我见过不少同事在月底整理回归数据的时候面对十几个CSV文件一脸茫然最后只能写Python脚本先拼接再统计绕了一大圈。1.2 SQLite做测试数据仓库的天然优势SQLite是最适合这种场景的本地数据库它不需要安装服务端就是一个单文件数据库JDBC驱动也只是一个jar包。相比MySQL或者PostgreSQL它省去了账号密码、网络连接、服务启动这些步骤尤其是你只是想保存测试结果的时候弄一套数据库服务纯属杀鸡用牛刀。从使用体验来说SQLite具备所有关系数据库的能力。你可以建多个表把采样结果、请求参数、断言信息、业务编号分门别类地存起来可以用SQL做聚合统计AVG、MAX、COUNT这些都能直接跑还可以按时间范围、接口路径、错误类型做精确筛选。数据量级别在几十万条以内时SQLite的查询性能完全够用配合索引还能做到毫秒级返回。SQLite还有个特别实际的优势单文件拷贝。测试数据存完以后整个库就是一个.db文件发给同事或者备份到网盘都极其方便不需要导出再导入。我在做模拟项目的性能测试回归时每次跑完就把db文件按日期存档三个月下来也就几百兆查历史数据时直接挂载文件就行比翻CSV压缩包靠谱多了。2. 两种核心使用场景功能测试数据存档与性能结果落库标题里“功能/性能使用”其实是两种差别很大的玩法。很多教程只教你“把结果写入SQLite”但不告诉你功能测试和性能测试的侧重点完全不同。我把这两种场景拆开讲你就知道脚本和表结构该按什么思路设计了。2.1 功能测试把每次请求留痕可查功能测试的场景很好理解你调一个接口希望把请求参数、响应状态码、响应体、断言结果都记录下来。比如测试一个下单接口你关心的是“这个订单号返回了什么结构断言到底过了没有”。这些数据放到一个固定表里查询时按照订单号或者请求时间过滤比翻日志方便很多。功能测试对写入性能要求不高手工点一次请求或者自动化跑一百条用例写入频率都很低。但有一个隐形需求经常被忽略匿名化前后端信息。存库后你可以随时回溯某个测试账号的行为路径方便排查是不是环境数据导致的问题。同一套脚本既能用来做接口自动化回归也能在出问题的时候快速查“这条数据当时到底返回了什么”。在这个场景下表结构需要偏向“业务留痕”尽量把请求相关的业务字段存清楚。比如保存响应体中提炼出来的订单号保存断言是否成功保存失败时的错误信息。这样后续排查就不需要再去翻Jmeter的界面日志直接在数据库里SQL查一遍就知道了。2.2 性能测试采样结果批量入库才能分析性能测试完全是另一套思路。压测时一个线程组可能每秒产生几十上百条采样结果一条结果里包含响应时间、发送字节数、接收字节数、状态码等。如果每条结果都立刻插库数据库的写入会变成瓶颈甚至反过来影响压测机的资源占用。性能测试的核心目标是事后分析所以我们要做的是批量写入。典型做法是把采样结果放到一个内存缓冲列表里攒够一定条数或者每隔一段时间统一提交一次。这样单次插入语句变成批量插入数据库I/O次数大幅减少写入速度能提升一个量级。性能场景的表结构也要做调整。很多字段其实不需要逐条细节分析比如“连接时间”和“等待时间”压力测试重点是整体分布。但为了后面画响应时间散点图、计算TP99你又不能只存平均值否则数据失真。所以我的建议是明细全部落库分析时再通过SQL聚合让数据值保留最大灵活性。2.3 两种场景对写入策略的要求完全不同这里有必要做个对比方便你对号入座选择实现路径。维度功能测试存档性能测试落库写入频率低几十到几百条/轮次高每秒几十到数百条写入方式逐条插入即可批量插入 定时提交主要查询方式按业务字段精确查找按时间段/接口维度聚合统计表结构侧重业务字段丰富订单号、断言信息采样指标完整响应时间、字节数、状态码数据库影响几乎无压力需要开WAL 连接复用 索引优化认清场景区别后后面做连接配置、写脚本、调参数时心里就有数了。功能测试可以写得随意一些性能测试则每一步都要考虑对压测本身的影响。3. 环境准备与数据库结构设计这一节是照着就能跑的操作部分。我把JDBC驱动怎么装、JDBC连接配置怎么写、表结构怎么建一步步列出来。如果你之前没用过Jmeter连接数据库这里不会跳步。3.1 驱动安装与JDBC连接配置第一步是下载SQLite的JDBC驱动。注意不是Jmeter内置的需要你手动去Maven仓库搜索“sqlite-jdbc”把对应版本的jar下载下来放到Jmeter安装目录下的lib文件夹中。我习惯用最新稳定版因为老版本可能存在兼容性问题尤其是和JSR223里的Groovy配合时。放好jar包后重启Jmeter添加一个“JDBC Connection Configuration”配置元件右键线程组 - 添加 - 配置元件 - JDBC Connection Configuration。关键字段如下Variable Name填写一个名字比如sqliteDB后面JDBC请求里用这个名字引用连接池。Database URLjdbc:sqlite:/绝对路径/test_results.db注意SQLite的JDBC URL和MySQL不太一样文件路径要用绝对路径否则容易写到Jmeter的bin目录下你根本不知道的位置。JDBC Driver classorg.sqlite.JDBCUsername / Password留空即可SQLite默认不鉴权有一个细节要注意JDBC Connection Configuration里的“Pool Max”指连接池最大连接数。SQLite本身对并发写有限制我通常设置为1因为单连接配合批量提交已经够用还能避免多连接同时写导致的锁竞争。读操作如果比较频繁可以单独开一个只读连接但压测时没必要搞那么复杂。注意数据库URL里的目录必须提前存在。SQLite JDBC不会自动创建不存在的目录如果路径里的文件夹没建连接时会直接报错。3.2 表结构设计要点建表建议在测试前手动用数据库工具比如DBeaver建好或者用Jmeter的JDBC请求执行建表语句。我更推荐手动建好因为建表语句里的字段名要和脚本中写入的数据严格对应用工具建表可以直观检查拼写。功能测试场景推荐一张请求明细表CREATE TABLE IF NOT EXISTS api_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, case_name TEXT, request_url TEXT, request_method TEXT, request_params TEXT, response_code INTEGER, response_body TEXT, success TEXT, cost_time INTEGER, create_time INTEGER );这张表存的是业务留痕数据字段覆盖“请求是什么、返回了什么、花了多久”。request_params和response_body用TEXT存JSON字符串即可查询时用LIKE或者后续在程序里解析。性能测试场景推荐一张采样数据表CREATE TABLE IF NOT EXISTS sample_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, test_batch TEXT, thread_group TEXT, sampler_name TEXT, elapsed INTEGER, response_code INTEGER, success INTEGER, bytes INTEGER, sent_bytes INTEGER, latency INTEGER, connect_time INTEGER, thread_name TEXT, sample_time INTEGER );sample_time存的是毫秒级时间戳elapsed是单次响应时间latency和connect_time用于分析网络耗时success用0/1表示通过与否。多跑几轮压测时可以用test_batch字段区隔批次避免每次压测前都要清表。3.3 SQLite连接参数的几个重要开关连接串里可以附带参数有两个对写入性能和稳定性非常关键PRAGMA journal_modeWAL把SQLite的日志模式切换为Write-Ahead Logging能显著减少读写锁冲突。查询时读操作不会阻塞写操作在压测场景下这个开关至关重要。可以在建表后执行这条PRAGMA语句。PRAGMA synchronousNORMAL让SQLite在WAL模式下不必每次提交都做fsync提高写入速度。正常压测数据不是金融账本丢一两条历史数据可接受这个取舍是划算的。这两个配置在JDBC连接串里写法是jdbc:sqlite:/绝对路径/test_results.db?journal_modeWALsynchronousNORMAL也可以连接后通过JDBC请求执行PRAGMA journal_modeWAL;和PRAGMA synchronousNORMAL;这两条语句。我实测下来批量入库场景下打开WAL和NORMAL同步后写入吞吐能提升一倍有余。还有一个容易踩的坑SQLite连接串末尾不要随意加?后面不认识的参数JDBC驱动不认识就直接忽略有些参数不一定生效。最靠谱的方式是先连接再执行PRAGMA语句确认。4. 脚本实现与核心代码解析说完了环境接下来是重头戏。Jmeter里写数据库写入逻辑建议用JSR223采样器或者JSR223后置处理器语言选Groovy。Groovy对Java的库调用非常友好JDBC的PreparedStatement这些都能直接用而且Jmeter内置了Groovy引擎BeanShell那套解释执行性能太差早就该淘汰了。4.1 JSR223 Groovy 的基本写法在测试计划里添加一个JSR223采样器放到HTTP请求之后或者用JSR223后置处理器语言选择groovy。脚本里可以通过内置变量拿到上下文数据最常用的几个vars线程上下文变量可以读写Jmeter变量prev上一个采样结果对象包含响应时间、状态码等sampleResult当前采样器结果对象功能测试场景下的一个典型写入脚本如下import java.sql.Connection import java.sql.DriverManager import java.sql.PreparedStatement def caseName vars.get(case_name) def requestUrl vars.get(request_url) def conn DriverManager.getConnection(jdbc:sqlite:/绝对路径/test_results.db) String sql INSERT INTO api_log(case_name, request_url, request_method, request_params, response_code, response_body, success, cost_time, create_time) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?) try { PreparedStatement ps conn.prepareStatement(sql) ps.setString(1, caseName) ps.setString(2, requestUrl) ps.setString(3, prev.getRequestHeaders().split(\n)[0]) ps.setString(4, prev.getSamplerData()) ps.setInt(5, prev.getResponseCode()) ps.setString(6, prev.getResponseDataAsString()) ps.setString(7, vars.get(assertion_result) ?: PASS) ps.setLong(8, prev.getTime()) ps.setLong(9, System.currentTimeMillis()) ps.executeUpdate() } catch (Exception e) { log.error(写入SQLite失败, e) } finally { conn.close() }这段脚本有几个注意点。prev.getResponseDataAsString()在响应体很大的时候会占内存建议对响应截断后再入库比如prev.getResponseDataAsString().take(2048)。prev.getTime()返回的是毫秒和系统时间戳保持同一单位方便后续计算耗时。assertion_result这个变量需要你在断言中用vars.put(assertion_result, PASS)设置这样断言结果才能被记录。4.2 批量提交降低写入开销上面的脚本每条请求都建立一次连接、执行一次插入、关闭连接跑十几条没问题但压测场景就不行了。性能测试的正确封装是把连接对象放在线程级别复用攒一定条数再批量提交。我用的思路是这样的在JSR223采样器里判断当前线程组的vars中是否存在一个数据库连接对象不存在就创建存在就复用。同时准备一个计数器变量记录当前线程已暂存多少条数据达到阈值就执行提交。import java.sql.Connection import java.sql.DriverManager import java.sql.PreparedStatement Connection conn vars.getObject(db_conn) if (conn null || conn.isClosed()) { conn DriverManager.getConnection(jdbc:sqlite:/绝对路径/perf_test.db?journal_modeWALsynchronousNORMAL) vars.putObject(db_conn, conn) } String sql INSERT INTO sample_data(test_batch, thread_group, sampler_name, elapsed, response_code, success, bytes, sent_bytes, latency, connect_time, thread_name, sample_time) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) PreparedStatement ps vars.getObject(ps) if (ps null) { ps conn.prepareStatement(sql) vars.putObject(ps, ps) } Integer batchCounter vars.getObject(batch_counter) if (batchCounter null) { batchCounter 0 } String testBatch vars.get(test_batch) ?: default_batch ps.setString(1, testBatch) ps.setString(2, vars.get(threadGroupName)) ps.setString(3, prev.getSampleLabel()) ps.setLong(4, prev.getTime()) ps.setLong(5, prev.getResponseCode()) ps.setInt(6, prev.isSuccessful() ? 1 : 0) ps.setLong(7, prev.getBytesAsLong()) ps.setLong(8, prev.getSentBytes()) ps.setLong(9, prev.getLatency()) ps.setLong(10, prev.getConnectTime()) ps.setString(11, prev.getThreadName()) ps.setLong(12, System.currentTimeMillis()) ps.addBatch() batchCounter 1 if (batchCounter 100) { ps.executeBatch() conn.commit() batchCounter 0 } vars.putObject(batch_counter, batchCounter)这段脚本默认每100条提交一次。实际压测时如果并发高、每秒钟有几百个请求100条可能一两秒就攒满了提交频次依然偏高。可以把阈值提高到500甚至1000或者改成“每5秒提交一次”后者需要额外记录上一次提交时间写起来稍微复杂一些。我一般先用500条这个值跑出来的结果很不错。这里有个容易忽略的关键点SQLite JDBC默认是自动提交的conn.commit()不会真正等待写入落盘只是让WAL里的数据进入数据库主文件。所以手动提交更多是控制WAL文件不要无限增长而不是控制写入频率本身。如果你想让批量动作真正生效需要在连接时设置conn.setAutoCommit(false)然后在批量结束时才commit。4.3 性能测试场景的高并发写入方案高并发下直接在JSR223里频繁开关连接是致命的。Jmeter每个线程虚拟用户都会执行这个脚本如果每个线程都设一个连接那数据库连接数等于并发用户数。SQLite对多写者不友好多个连接同时写入会频繁出现SQLITE_BUSY。推荐做法是每个线程只创建一个连接全程复用压测结束后用“JSR223 tearDown”或“setUp线程组”关闭连接。实际测试中我见过100个并发线程各持有一个SQLite连接配合WAL模式写入依然稳定因为线程间写是轮询的并不频繁。关键不在于连接数量而在于不要在每条采样都去建连、断连。连接结束后关闭的逻辑可以放在测试计划里的“setUp/tearDown线程组”的JSR223采样器里。脚本写法如下Connection conn vars.getObject(db_conn) PreparedStatement ps vars.getObject(ps) if (ps ! null) { ps.executeBatch() ps.close() } if (conn ! null !conn.isClosed()) { conn.commit() conn.close() }这步一定要做否则Jmeter进程退出时可能丢失缓冲区里的数据。如果你用的是vars.putObject(db_conn, conn)tearDown线程组也能通过vars拿到连接对象只要同一测试计划内共享变量即可。还有一个细节batch里的数据有失败情况怎么办SQLite JDBC的executeBatch不会像MySQL那样自动跳过错误遇到外键约束等异常会直接抛错。为了不丢失全部采样推荐在batch执行时捕获异常如果失败则逐条重放batch定位哪一条导致问题。不过对于压测采样这种非严格业务数据我的策略是直接丢弃失败批次保证测试流程不中断。代码可以改成try { ps.executeBatch() conn.commit() } catch (Exception e) { log.error(批量提交失败尝试逐条写入, e) conn.rollback() }5. 查询分析与报表生成数据落库之后真正的价值在于查询和分析。SQLite是标准SQL语法配合几个窗口函数可以完成大部分性能分析需求。5.1 用SQL直接统计吞吐量和响应时间一个最常见的需求是压测完成后按接口维度统计平均响应时间、最大响应时间、成功率、吞吐量。SQL写起来很直观SELECT sampler_name, COUNT(*) AS total_requests, SUM(CASE WHEN success 1 THEN 1 ELSE 0 END) * 1.0 / COUNT(*) AS success_rate, CAST(AVG(elapsed) AS INTEGER) AS avg_elapsed, MAX(elapsed) AS max_elapsed, MIN(elapsed) AS min_elapsed, COUNT(*) * 1.0 / ( (MAX(sample_time) - MIN(sample_time)) / 1000.0 ) AS tps FROM sample_data WHERE test_batch batch_001 GROUP BY sampler_name;这条SQL直接给出每个采样器的总请求数、成功率、平均响应时间和TPS。sample_time的毫秒差换算成秒作为时间窗计算TPS逻辑和Jmeter聚合报告基本一致。想算TP90/TP95/TP99SQLite 3.25以上支持窗口函数可以通过percentile的方式计算WITH ranked AS ( SELECT sampler_name, elapsed, ROW_NUMBER() OVER (PARTITION BY sampler_name ORDER BY elapsed) AS rn, COUNT(*) OVER (PARTITION BY sampler_name) AS total FROM sample_data WHERE success 1 AND test_batch batch_001 ) SELECT sampler_name, MIN(CASE WHEN rn total * 0.90 THEN elapsed END) AS tp90, MIN(CASE WHEN rn total * 0.99 THEN elapsed END) AS tp99 FROM ranked GROUP BY sampler_name;这个查询思路先给每个接口的耗时数据按从小到大排序再按90%、99%分位划出界线。压测报告里的响应时间分布靠这条SQL几分钟就算出来了完全不用再打开Jmeter插件视图去手动看。5.2 把数据交给Python做可视化SQLite文件可以直接用Python读取配合pandas和matplotlib可以做更灵活的可视化。压测跑完后终端敲几行代码响应时间散点图、吞吐趋势图、错误码分布就出来了。import sqlite3 import pandas as pd import matplotlib.pyplot as plt conn sqlite3.connect(/绝对路径/perf_test.db) df pd.read_sql_query( SELECT elapsed, sample_time, sampler_name FROM sample_data WHERE test_batch batch_001 , conn) df[sample_time] pd.to_datetime(df[sample_time], unitms) df.plot(xsample_time, yelapsed, kindscatter, alpha0.3) plt.title(响应时间散点图) plt.show()这段代码把每次采样的响应时间按时间顺序画成散点图能一眼看出压测过程中有没有明显的响应变慢拐点。再配合成功率、TPS曲线性能瓶颈往往就藏在波动趋势里。功能测试的查询更简单按业务字段直接搜就行SELECT * FROM api_log WHERE request_url LIKE %order/create% ORDER BY create_time DESC LIMIT 50;排查问题时这个SQL可比打开Jmeter界面翻树快多了。如果存了请求参数和响应体连“当时的body是什么”都能一键查出来。6. 实操中的常见问题与避坑和SQLite Jmeter组合打交道过程中我遇到过不少坑。大部分问题不是复杂的技术难题而是一些细节没注意到导致的。下面把这些经验和解决方案整理出来能帮你省不少调试时间。6.1 驱动加载失败与连接串写错最常见的报错是ClassNotFoundException: org.sqlite.JDBC。原因是jar没有放到lib目录或者放了但Jmeter没重启。Jmeter的lib目录下驱动加载必须重启进程才能生效不像有些插件可以热加载。另外如果你用的是Maven下载的jar注意jar包不能损坏可以用压缩工具打开看一眼内部是否包含org/sqlite/JDBC.class。连接串写错也经常发生。SQLite的JDBC URL写成jdbc:sqlite://绝对路径/db.sqlite会报错正确写法是jdbc:sqlite:/绝对路径/db.sqlite。这里没有主机名和端口斜杠数量也很容易搞错。建议先在DBeaver里测试连接成功再把同样的URL粘到Jmeter里能少踩一半坑。6.2 数据库文件被锁SQLite的并发写能力有限虽然WAL模式大幅改善了读写并发但如果两个连接同时执行写操作依然可能遇到SQLITE_BUSY。遇到这类报错先检查有没有其他程序比如DBeaver、Python脚本打开了同一个db文件很多时候不是Jmeter的问题而是你在查询时把连接占了。另外一个建议是给JDBC的URL加一个超时参数避免锁等待无限挂起jdbc:sqlite:/绝对路径/perf_test.db?journal_modeWALsynchronousNORMALbusy_timeout3000busy_timeout3000表示锁等待最多3秒超过就抛错。设置后即使偶发锁冲突也不会卡死整个压测线程。6.3 脚本里踩过的典型坑JSR223脚本里的变量作用域是个大坑。vars.getObject拿到的连接对象是放在当前线程变量里的如果脚本跑在多个线程组里线程组之间不会共享。你可能会看到某些线程突然报空指针就是因为那个线程第一次执行脚本时还没有初始化连接对象。解决的方法是每次使用前都做空判断或者干脆在setUp线程组里预先初始化所有线程的连接。还有一个坑Groovy脚本里使用prev时要留意JSR223采样器在测试计划中的位置。如果JSR223采样器放在HTTP请求之后prev就是前一个HTTP请求的结果如果放在监听器里prev可能指向最后一个采样器。为了保证数据正确我的做法是把写入逻辑放在“JSR223后置处理器”中它在HTTP请求执行完后立刻运行prev对象语义非常明确不会串。大批量insert时内存也可能出问题。循环每执行一次ps.addBatch()JDBC驱动内部就会缓存一条待插入数据攒到几十万条后占用的堆内存非常可观。解决办法有两个一是设置合理的batch阈值不要攒太多二是在批量提交后调用ps.clearBatch()确保statement对象不会越积越多。6.4 几个提升稳定性的小习惯我最后提几个逐渐养成的习惯都是实际教训换来的。第一条压测前先跑一条测试请求确认数据确实写进db文件了再全量压测不然跑完几千条才发现SQL写错字段名整个批次重来浪费时间。第二条给每个压测批次设置一个唯一的test_batch标识即使是同一天跑的两轮压力测试也要区分不然事后分析很难比对两轮数据这是我的一个反例后的收获。第三条写完SQL后先查SELECT COUNT(*) FROM sample_data WHERE test_batchxxx确认本批次记录数和Jmeter的采样数对得上这是校验数据完整性的最快方法。还有一点是关于数据清理的。SQLite单文件体积增长后删除大量数据并不会自动释放文件占用的磁盘空间。我每个月会用VACUUM命令压缩一次数据库文件避免db文件越攒越大最后几GB的网络磁盘很尴尬。命令很简单VACUUM;这个操作在做完数据归档后执行一次就行对日常使用没有影响。还有一个提醒如果你在压测机上开启Jmeter的图形界面同时跑SQLite写入界面渲染本身会消耗CPU高并发场景下可能导致采样结果延迟变大。建议压测时用非GUI模式jmeter -n -t 脚本.jmx -l 结果文件把数据库写入逻辑放到后台线程去做或者干脆单独用一台机器做施压和数据采集。我在某项目中亲测过同一个压测脚本GUI模式比非GUI模式平均响应时间高出了将近10%这个差距完全来自Jmeter自身的渲染开销。恢复生产前还有一些注意事项比如在长时间压测时建议写一个简单的心跳查询定时看数据库连接是否正常。不查的话可能压测跑了几十分钟后才发现SQLite连接早已断开所有采样数据全丢了。心跳查询只需要一条SELECT 1;把它放在独立的后置处理器中每1000次采样执行一次就能及时发现连接异常。实际操作的时候我见过不少人纠结于SQLite的极限写入性能总觉得它不够强。其实SQLite单机写入能力做测试数据存储绰绰有余关键瓶颈基本都在脚本写法。JSR223 批量commit WAL模式这套组合已经帮我处理过一天几十万条采样的压测数据完全没出过性能问题。至于几百万条那种极端情况我已经建议直接换ClickHouse或者InfluxDB了那完全是另一个量级的方案。最后分享一个我自己的使用习惯每次压测结束我只保留三样东西——db文件、压测Jmx脚本、一份SQL查询后的关键指标摘要。db文件用于深挖问题Jmx负责复现摘要用于快速汇报。这三样放在同一个目录下按日期整理半年来想查哪次压测的结果都清清楚楚。用SQLite做Jmeter的存储层之后我已彻底摆脱了翻CSV和JTL的噩梦希望这篇文章也能帮你少走点弯路。