多用户通过BeanShell采样器JDBC运行Oracle相同循环报错如何解决
问题根因
- 你代码中插入的主键ID硬编码为固定范围
11~12,所有并发线程都会插入相同ID值,触发主键唯一约束冲突,这是报错的核心原因。 - 你设置的全局删记录逻辑没有线程隔离,多线程并发执行时,删除和插入操作时序混乱,可能出现A线程刚插入数据就被B线程删除,或者多个线程同时插入同ID的情况。
调整方案
1. 替换固定主键为线程唯一取值,彻底避免ID重复
两种可选实现方式:
- 方式1:通过JMeter内置变量生成线程唯一ID(无需改库)
用JMeter内置对象ctx获取当前线程编号,给每个线程分配独立的ID区间,比如每个线程插2条数据,就把ID的计算逻辑改为:线程编号*100 + 偏移量,修改后的脚本如下:import java.sql.*; import oracle.jdbc.*; import org.apache.jmeter.protocol.jdbc.config.DataSourceElement; ResultSet rs = null; ResultSetMetaData rsmd = null; PreparedStatement pstmt = null; // 获取当前线程号,threadNum从0开始所以+1避免0值 int threadNum = ctx.getThreadNum() + 1; int startId = threadNum * 100 + 11; int endId = startId + 1; Connection conn = DataSourceElement.getConnection("IMPACTConnectionPrerequisitsII"); String Query = "DECLARE \n" + "j number(4) \n" + "BEGIN \n" + "FOR j in " + startId + " .. " + endId + " LOOP \n" // 替换原固定11..12 + "INSERT INTO FWA_STAGE_TU \n" + "(ID,EXT_ID,BATCH_NO,OPERATION,BLOCK_ROLL_UP_LAST_RECORD,LAST_UPDATED_DATE,ERROR_FLAG,TRIAL_NO,TRIAL_ALIAS_CODE, \n" + "COUNTRY_CODE,DISPLAY_UNIT_NO,GROUP_NO,TRIAL_UNIT_REFERENCE,PRIMARY_INVESTIGATOR,PRIMARY_CENTRE,OCATION_NO, \n" + "PURPOSE_CODE,MANAGING_MED_UNIT_CODE,UPDATING_MED_UNIT_CODE,FINANCE_MED_UNIT_CODE,PATIENTS_ALL_SET_UP_FLAG, \n" + "PATIENT_MONITORING_FLAG,COMMENTS,CONFIRMED_FLAG,CONFIRMED_BY,CONFIRMED_DATE,ROLLUP_ROLLDOWN_PLANNED_FIGS, \n" + "DOC_COLLECTION_INDICATOR,VALIDATED_PAT_CAP,MAXIMUM_PAT_CAP,PREFERRED_LANGUAGE_CODE,NEW_DISPLAY_UNIT_NO, \n" + "TOTAL_OPEN_DCF_ISSUES_NO,ORDER_INTERVAL,ORDER_INTERVAL_UNITS,DATA_SOURCE_CODE,RANK_SEQ) \n" + "VALUES (j, 1, 10, 'I', NULL, SYSDATE, 'N', 102922, 'TESTTRIAL4', 'BEL', \n" + "j, NULL, 'BELTU'||j, 130262, 124236, NULL, 'PATTR', 'FP', 'FP', 'FP', 'N', 'Y', 'TESTTU'||j, \n" + "'Y', 999999, SYSDATE, NULL, 'P', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL) \n" + "END LOOP \n" + "END"; try { pstmt = conn.prepareStatement(Query); pstmt.executeUpdate(); } catch(Throwable ex) { log.error("Error message: ", ex); throw ex; } finally { if (rs != null) { rs.close(); } if (pstmt != null) { pstmt.close(); } if (conn != null) { conn.close(); log.info("!!!! Connection closed to database !!!!"); } } - 方式2:Oracle端用序列生成主键(最稳妥)
在数据库中创建主键序列,插入时直接取序列值,无需代码侧管理ID,天生线程安全:
插入语句中ID的取值直接替换为-- 先在Oracle执行一次创建序列 CREATE SEQUENCE SEQ_FWA_STAGE_TU_ID START WITH 1 INCREMENT BY 1 NOCACHE;SEQ_FWA_STAGE_TU_ID.NEXTVAL即可。
2. 调整删除记录逻辑
不要全局删除全表数据,改为按线程删除自己生成的记录,删除语句加过滤条件:
DELETE FROM FWA_STAGE_TU WHERE ID BETWEEN ? AND ?
参数对应每个线程自己的startId和endId即可。如果需要全局清表,把清表操作放到setUp线程组(所有测试线程启动前仅执行一次)或者tearDown线程组(所有测试线程结束后仅执行一次),不要放在每个业务线程的迭代逻辑中。
3. 可选性能优化
把BeanShell采样器替换为JSR223采样器,选择Groovy语言,JMeter官方推荐高并发场景使用,性能比BeanShell高5~10倍,避免高并发下采样器本身成为性能瓶颈。
内容的提问来源于stack exchange,提问作者user16923013
相关产品推荐
相关产品推荐

