JMeter高效构造MySQL测试数据:性能测试数据准备实战指南
1. 为什么要费劲用 JMeter 给 MySQL 构造测试数据1.1 测试数据不足这件事到底有多拖后腿做性能测试的人应该都有体会真正开始压接口之前最浪费时间的事情往往不是写脚本而是搞定测试数据。接口压测需要一批符合业务规则的存量数据比如十万个用户、几万张订单、一批带不同状态的商品记录。可测试环境里的数据库要么数据量少得可怜要么数据特征完全不符合业务要求要么开发同学临时塞了一些乱七八糟的脏数据进去根本没法直接用。我见过不少团队的做法是让开发写个一次性 SQL 脚本在测试库里跑一遍生成个几十万条数据就完事。这种做法最大的问题是数据不可控脚本跑完就没了下次想换一批数据重新来又得找开发改脚本。而且开发写的脚本往往就是简单的循环 insert完全模拟不了真实业务数据该有的特征——用户手机号全是 13800000000 这种重复值订单金额全是整数时间字段全是固定日期。拿这种数据去做性能测试测出来的结果根本没有参考价值。用 JMeter 给 MySQL 构造测试数据本质上是把造数这件事变成一个可配置、可重复执行、可并发的工程化过程。脚本写完一次整套数据生成逻辑就沉淀下来了以后每次要数据直接跑一下就行而且生成的数据可以在每次运行前通过参数调整灵活度非常高。1.2 为什么选 JMeter 而不是写 Python 脚本或存储过程有些朋友可能会说造数这种事情我用 Python 写个脚本不就完了吗何必绕一圈用 JMeter。的确Python 脚本能造数但用 JMeter 有几个很实际的优势。第一JMeter 天然支持多线程并发造数。性能测试中很多场景需要同一时刻有大量用户同时注册、同时下单用 Python 脚本实现并发要自己写 threading 或者 multiprocessing 的逻辑还得控制并发粒度。而 JMeter 里只需要在线程组里设置线程数它就会按你设定的方式去并发执行而且可以精确控制 ramp-up 时间模拟逐渐加压和瞬时压满两种不同的数据生成模式。第二JMeter 的参数化能力是现成的。造数过程里最常见的就是生成随机手机号、随机身份证、随机姓名、随机日期、随机金额这些需求在 JMeter 里用函数就能搞定不需要写代码。如果你需要从一批预置数据里取比如从一百个城市里随机选一个用 CSV Data Set Config 或者 __RandomFromMultipleVars 函数都能实现比在 Python 里自己维护数据源要省事得多。第三也是很重要的一点JMeter 造数脚本和后续的性能测试脚本在技术栈上是统一的。你在造数阶段已经验证了 JDBC 连接、参数化、断言这些能力到了真正写压测脚本的时候这些经验是完全复用的。团队内部做知识传递也更容易——大家都用 JMeter不用去理解一套新的脚本语言。1.3 造数方案的整体选型思路我给团队设计这套方案时整体思路是这样通过 JMeter 的 JDBC 能力连接 MySQL利用线程组控制并发压力利用循环次数控制数据总量利用 JMeter 内置函数和 CSV 文件控制数据内容的随机性和真实性最后通过定时器控制整体造数节奏避免单次压垮数据库。选择 JDBC Request 作为核心采样器是因为它是 JMeter 原生支持的 SQL 操作方式不像 BeanShell 那样需要写大量代码配置简单、可读性好团队成员一眼就能看懂每个采样器在做什么。如果想要更复杂的操作比如一条线程里先查再判断再插入可以通过添加多个 JDBC Request 配合 if 控制器来实现。这套方案的核心优势在于数据总量可以精确计算数据内容可以随意定制造数过程可以随时暂停和恢复而且整个过程有日志和结果树造了多少条、报了多少错一目了然。注意如果你的需求只是往表里插几万条全字段固定的数据用存储过程的 while 循环确实更快。但如果你需要的是模拟真实业务特征的数据JMeter 的灵活性和可观测性是存储过程替代不了的。2. 环境准备把 JMeter、JDBC 驱动和 MySQL 之间的关系理顺2.1 JDK 与 JMeter 版本的选择开始配置之前先把环境基础打好。JMeter 是 Java 应用它的运行依赖 JDK版本不匹配会在启动时报错或者运行中出各种诡异问题。目前常用的对应关系大致是JMeter 5.4 及以上版本一般要求 JDK 8 以上JMeter 5.6 之后官方推荐 JDK 11 以上JMeter 6.x 需要 JDK 11 或者更高。如果你本机装的是 JDK 17那么建议直接用最新版本的 JMeter老版本在 JDK 17 上运行可能会出现反射相关的 Warning虽然不影响主要功能但查问题的时候容易被干扰。检查 JDK 版本用一行命令java -versionJMeter 启动后在界面左上角也能看到版本信息。建议在开始配置数据库连接之前先确认 JMeter 能够正常启动、能够正常录制或手动创建一个最简单的 HTTP 请求确保基础环境没有问题再往下走数据库相关配置。否则如果 JMeter 本身启动都有问题排查数据库连接报错时容易找错方向。2.2 MySQL JDBC 驱动的下载与放置JMeter 本身不带 MySQL 的 JDBC 驱动这是很多人都踩过的坑。你配置好了连接参数一运行就报No suitable driver found其实就是驱动 jar 包没放进去。驱动的选择取决于你的 MySQL 版本。如果是 MySQL 5.7 及以下用mysql-connector-java5.1.x 系列就行如果是 MySQL 8.0 及以上建议用mysql-connector-j8.x 系列。这里有个细节MySQL 8.x 的驱动类名变了5.x 用的是com.mysql.jdbc.Driver8.x 用的是com.mysql.cj.jdbc.Driver配置错了也会报找不到类。下载的时候去 Maven 中央仓库搜索mysql-connector-j或者mysql-connector-java下载对应的 jar 包。拿到 jar 包之后放到 JMeter 安装目录下的lib/ext目录里然后重启 JMeter。重启这一步不能省JMeter 启动时才会扫描加载这些 jar 包。注意如果你已经在 JMeter 打开的状态下放了 jar 包不重启是不会生效的。这是新手最容易犯的错——放完 jar 直接运行报错说还是找不到驱动其实重启一下就好了。2.3 准备一个可用的测试库和测试表构造测试数据之前你的 MySQL 里得先有一个目标库和目标表。这个表的结构建议尽量贴合真实业务表字段类型、长度、索引都要按实际情况来否则造出来的数据在后续压测时可能因为字段长度超限或者类型不匹配而插入失败。下面给出一个简单但比较典型的用户表示例CREATE DATABASE IF NOT EXISTS test_db DEFAULT CHARACTER SET utf8mb4; USE test_db; CREATE TABLE IF NOT EXISTS t_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, user_no VARCHAR(32) NOT NULL COMMENT 用户编号, user_name VARCHAR(64) NOT NULL COMMENT 用户姓名, phone VARCHAR(20) NOT NULL COMMENT 手机号, email VARCHAR(128) DEFAULT NULL COMMENT 邮箱, age INT DEFAULT NULL COMMENT 年龄, city VARCHAR(64) DEFAULT NULL COMMENT 城市, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_user_no (user_no), KEY idx_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户测试表;建表的时候有几个点需要注意utf8mb4字符集是对中文最好的支持方式唯一索引的字段要仔细考虑如果你的造数脚本里生成的 user_no 可能会重复插入时就会报重复键错误create_time和update_time用默认值可以减少造数脚本中需要管理的字段数量让脚本更简洁。2.4 先验证 MySQL 本身的连通性在进行 JMeter 配置之前先用命令行客户端验证一下 MySQL 是否正常工作这能帮你把问题范围缩小。比如 MySQL 所在机器、端口、账号权限这些用命令行先验证一遍后面 JMeter 报错时就只需要关注 JMeter 配置层面的问题了。mysql -h127.0.0.1 -P3306 -uroot -p连接成功后执行show databases;看看能不能看到目标库。然后创建一个专门用于测试的账号并授予对应的权限不建议直接用 root 去连 JMeter——一是权限太大有风险二是有些 MySQL 环境 root 默认只能本地 Socket 连接JMeter 走 TCP 连接时可能因为权限或者 host 限制而失败。CREATE USER test_user% IDENTIFIED BY Test123456; GRANT SELECT, INSERT, UPDATE, DELETE ON test_db.* TO test_user%; FLUSH PRIVILEGES;3. JMeter 里怎么连上 MySQL那几个关键配置项别搞错3.1 JDBC Connection Configuration 逐项拆解JMeter 连接 MySQL 的核心组件是 JDBC Connection Configuration它负责维护数据库连接池。你可以在测试计划下添加配置元件 - JDBC Connection Configuration。里面需要重点关心的配置项如下配置项推荐值说明Variable Name自定义如mysql_pool这个变量名必须和 JDBC Request 里的引用名称保持一致Max Number of Connections按线程数设置一般等于或略大于线程组线程数连接池大小小了会排队大了会压垮数据库Max Wait10000 或更大获取连接的超时时间单位毫秒Database URLjdbc:mysql://127.0.0.1:3306/test_db?useSSLfalseserverTimezoneAsia/Shanghai连接地址注意参数配置JDBC Driver Classcom.mysql.cj.jdbc.DriverMySQL 8.x 用这个5.x 用com.mysql.jdbc.DriverUsername数据库账号使用最小权限账号Password数据库密码对应账号密码Variable Name是连接池的唯一标识。你配置了一个名为mysql_pool的连接池后面 JDBC Request 里的Variable Name必须填mysql_pool否则 JMeter 不知道用哪个连接池去执行 SQL。这个字段一旦填错运行时会直接报Connection pool is empty或者找不到连接池的错误而且报错信息不会明显提示是名称不匹配排查起来有点绕。3.2 数据库 URL 里那串参数到底是什么意思JDBC URL 是出错率最高的地方很多连接问题都出在这串字符上。拿到一个 URL先拆开看jdbc:mysql://主机地址:端口号/数据库名?参数1值1参数2值2。常用参数逐个说明useSSLfalse本地测试环境基本都没有配 SSL 证书如果你的 MySQL 没启用 SSL这个参数不设或者设成 true连接时会报 SSL 相关错误。本地测试环境建议直接关闭 SSL省去一堆麻烦。serverTimezoneAsia/ShanghaiMySQL 8.x 对时区要求更严格如果驱动和 MySQL 服务端时区不一致操作时间字段时会报Server returns invalid timezone错误。明确指定时区后这个报错就消失了。characterEncodingutf8保证中文不会乱码。虽然我们在建表时指定了 utf8mb4但连接层面的字符集也要匹配双保险。allowMultiQueriestrue如果你需要在一条 JDBC Request 里执行多条 SQL用分号分隔就需要开启这个参数。注意它存在 SQL 注入风险本地测试库用没问题生产环境不建议开。rewriteBatchedStatementstrue批量插入时极大的性能优化参数后面讲批量造数时会重点说。完整的 URL 示例jdbc:mysql://127.0.0.1:3306/test_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowMultiQueriestruerewriteBatchedStatementstrue3.3 线程组的设计数据总量是可以算出来的造数脚本里的线程组核心逻辑很简单线程数控制并发度循环次数控制每个线程执行的次数数据总量等于两者相乘。比如你要生成 10 万条数据可以设置 50 个线程、每个线程循环 2000 次。这两个数的配比决定了造数的速率和对数据库的压力。具体怎么配取决于你的目的。如果你是在为后续压测准备存量数据一般建议造数速度不要太猛给数据库留足喘息空间用低并发、多循环的方式比较稳妥。如果你是要模拟高并发写入场景顺便验证数据库写入性能那就可以把线程数调高比如 100 甚至 200 个线程同时插入观察数据库有没有锁等待、连接数打满等问题。线程组配置里有一个容易忽略的点Loop Count如果填了无限循环脚本就会一直跑直到手动停止。造数脚本我一般不推荐无限循环因为造完数之后你可能忘了停白白占用数据库连接。建议算好需要的总量设置具体的循环次数。另外Scheduler也可以用来控制造数时间窗口比如设置持续运行 10 分钟到时间自动停止。这个在做灌入一段时间的数据时很好用。3.4 JDBC Request 的基本使用方法JDBC Connection Configuration 配好之后在线程组下添加Sampler - JDBC Request把两者关联起来。JDBC Request 里的核心配置Variable Name填连接池的名称这个必须和 JDBC Connection Configuration 里的一致。Query Type选择 SQL 类型插入数据选Update Statement查询数据选Select Statement调用存储过程选Callable Statement。Query输入 SQL 语句可以使用 JMeter 的参数和函数也可以写带?的预编译语句配合Parameter Values使用。最简单的插入语句可以直接写INSERT INTO t_user (user_no, user_name, phone, email, age, city) VALUES (U00000001, 张三, 13800000001, zhangsantest.com, 28, 北京);先跑通这个最简单的确认数据能插进去再去搞参数化。一次把复杂配置堆上去出了问题反而不知道是连接配置的问题还是 SQL 语法的问题。4. 造数脚本实战从笨办法到批量生成智能化数据4.1 第一步先把固定数据循环插入跑通万事开头难造数脚本的第一步不是直接上随机函数而是先跑一个最简单的固定值插入把链路验证通。在线程组里设置线程数 5循环次数 10。JDBC Request 的 Query 写成上面那条固定 SQL。运行之后去数据库查一下SELECT COUNT(*) FROM t_user;如果结果是 50说明链路已经通了。这个阶段的目标是验证连接池配置正确、JDBC Request 配置正确、SQL 语法正确。确认无误之后再把这个固定 SQL 升级成参数化 SQL。这一步虽然看着笨但能省掉后面排查问题时的一大半痛苦——我见过太多人上来就写随机函数 CSV 参数化 批量提交运行报错之后根本不知道问题是出在连接配置还是 SQL 还是参数生成上。4.2 第二步让数据有变化别全是同一个值固定值插入跑通之后最关键的一步就是把固定值替换成动态生成的值。JMeter 内置函数里有几个在造数场景中非常常用的${__Random(10000000,99999999,)}生成 8 位随机数字可以用在手机号后 8 位配合前缀13、15、18等拼接。${__time(yyyy-MM-dd HH:mm:ss,)}生成当前时间字符串用于时间字段。${__time(yyyy-MM-dd,)}生成当前日期。${__UUID()}生成 UUID适合填充唯一编号字段。${__RandomString(8,abcdefghijklmnopqrstuvwxyz,)}生成长度为 8 的随机小写字母字符串。把 SQL 改造成这样INSERT INTO t_user (user_no, user_name, phone, email, age, city) VALUES ( CONCAT(U, ${__time(/)}), CONCAT(user_, ${__Random(1000,9999,)}), CONCAT(13, ${__Random(100000000,999999999,)}), CONCAT(${__RandomString(8,abcdefghijklmnopqrstuvwxyz,)} test.com), ${__Random(18,60,)}, 北京 );这里用CONCAT函数把固定前缀和随机值拼起来。生成手机号的时候注意13 9位随机数字总共 11 位。如果你直接写CONCAT(1, ${__Random(100000000,999999999,)})可以生成 10 位数字再注意一下长度。注意${__time(/)}生成的是当前时间戳毫秒级同一秒内并发插入多条数据时可能撞车如果 user_no 有唯一索引建议在后面再拼一段随机数字来降低重复概率。4.3 第三步用 CSV 文件提供真实感更强的业务数据随机字符串虽然能用但看起来还是一眼假。用户名全是user_1234这种城市全是北京和真实业务场景差距很大。如果需要更真实的数据可以把业务数据放在 CSV 文件里用 CSV Data Set Config 组件来读取。常见的做法是准备两个 CSV一个放常见姓氏和名字比如一百个姓、几百个名字一个放城市列表。然后在 JDBC Request 里通过CSV Data Set Config定义的变量名来引用。CSV 文件第一行通常是列名surname,given_name 张,伟 李,娜 王,磊在测试计划中添加配置元件 - CSV Data Set Config配置好文件路径、变量名比如surname、given_name、分隔符逗号。然后在 SQL 里这样用INSERT INTO t_user (user_no, user_name, phone, email, age, city) VALUES ( CONCAT(U, ${__time(/)}), CONCAT(${surname}, ${given_name}), CONCAT(13, ${__Random(100000000,999999999,)}), CONCAT(${__RandomString(8,abcdefghijklmnopqrstuvwxyz,)} test.com), ${__Random(18,60,)}, ${city} );这里有个关键细节CSV Data Set Config 的模式。如果选择每次迭代取一行而你的循环次数很大CSV 文件很快会耗尽如果选择共享模式 循环CSV 读完后会从头继续配合随机函数使用效果更好。另外在 CSV Data Set Config 里有一个Sharing Mode默认是所有线程共享同一个文件指针如果需要每个线程都从第一行开始独立读取可以选择每个线程独立的文件副本。4.4 第四步用 JSR223 处理器生成复杂业务字段有些时候字段的生成规则比较复杂内置函数搞不定。比如订单号有业务编码规则——前缀 日期 随机序列号比如DD20250617xxxxxx。这种用纯函数拼接也能做但可读性和维护性不好而且在字段多了之后SQL 里全是函数嵌套看起来非常痛苦。这种情况下推荐用 JSR223 采样器或者 JSR223 前置处理器来做字段预生成。JSR223 支持 Groovy 脚本JMeter 内置支持不需要额外装东西。比如你在 JDBC Request 之前添加一个 JSR223 预处理程序预先定义好 SQL 中需要用到的变量def orderNo DD new Date().format(yyyyMMddHHmmss) String.format(%04d, new Random().nextInt(10000)) def userName u_ new Random().nextInt(99999999) def city [北京, 上海, 广州, 深圳, 杭州][new Random().nextInt(5)] vars.put(orderNo, orderNo) vars.put(userName, userName) vars.put(city, city)然后在 SQL 中通过${orderNo}、${userName}、${city}来引用。Gorovy 脚本的能力比 JMeter 内置函数强很多可以写循环、分支、随机选择、日期计算甚至可以调用第三方 Java 库。不过要提醒一句能用内置函数解决的就别上脚本JSR223 增加脚本开发成本和调试成本只在确实复杂的场景下才值得用。4.5 第五步大批量造数时的批量提交优化当你需要一次性生成几十万甚至上百万条数据的时候一条一条插入的性能是扛不住的。这就需要用批量插入。批量插入有两种实现方式。第一种是在 SQL 里直接写多条 VALUES 语句INSERT INTO t_user (user_no, user_name, phone, email, age, city) VALUES (U0001, 张三, 13800000001, atest.com, 20, 北京), (U0002, 李四, 13800000002, btest.com, 21, 上海), (U0003, 王五, 13800000003, ctest.com, 22, 广州);这种方式的好处是简单直接但需要你在 SQL 中把参数全部展开。在 JMeter 里配合循环控制器可以变量在每次循环里改变。更常见的做法是用循环控制器每次执行一条带参数的 insert然后通过rewriteBatchedStatementstrue优化。但要注意JMeter 的 JDBC Request 本身并不会自动把多次插入合成一个批次它是逐条执行的rewriteBatchedStatements优化的是 JDBC 驱动层的批量执行能力这个和我们平时用 JDBC 的addBatch()不太一样。如果你真的需要极高吞吐量的批量造数我建议换个思路在 MySQL 端用LOAD DATA LOCAL INFILE或者先把数据生成到 CSV 文件再用mysql命令导入。但在 JMeter 造数场景下一般用 50~100 个线程并发插入配合 MySQL 的 InnoDB 引擎每秒插入几千条数据是完全没有问题的。对于大多数性能测试的造数需求这个速度已经够用了。实操心得JDBC Connection Configuration 里可以开启一个Transaction Isolation的设置默认是DEFAULT。如果是大批量插入可以设置成READ_COMMITTED减少锁的开销。另外MySQL 端把autocommit保持开启就好不要关掉。关掉之后 JDBC Request 执行完 Insert 如果没手动 commit数据可能一直不落库。4.6 造数总量和节奏的控制造完数之后你要能确认数据总量符合预期这个用 MySQL 的 count 查询就能验证。但更好的方式是在脚本里加一个简单的判断逻辑避免造得太多或者太少。计算规则就是之前说的数据总量 线程数 × 循环次数。比如你要 10 万条数据设 50 线程、每个线程循环 2000 次。如果运行过程中有插入失败的最终条数会少于 10 万那就需要检查错误日志看是哪些 SQL 执行失败了修正后重新补齐。控制造数节奏的组件是定时器。在 JDBC Request 下添加定时器 - 固定定时器设置 1000 毫秒即 1 秒这样每条线程每执行完一次插入就等 1 秒。这个对并发量管控很有效——50 个线程配合 1000ms 固定定时器平均每秒就 50 次插入不会把数据库压得太厉害。如果你想让数据库满负荷跑那就不加定时器让每个线程以最快的速度连续插入这时候要注意观察 MySQL 的 CPU 和连接数。5. 常见问题与排查技巧实录5.1 报错No suitable driver found for jdbc:mysql...怎么办这个报错基本就是 JDBC 驱动没有加载成功原因无非两个jar 包没放对位置或者驱动类名配置错误。排查步骤先打开 JMeter 安装目录下的lib/ext目录确认 mysql-connector 的 jar 文件确实存在。然后确认你放的是 MySQL 8.x 对应的mysql-connector-j-8.x.x.jar还是 5.x 对应的mysql-connector-java-5.1.x.jar。最后再检查 JDBC Connection Configuration 里的JDBC Driver ClassMySQL 8.x 对应com.mysql.cj.jdbc.DriverMySQL 5.x 对应com.mysql.jdbc.Driver。如果 jar 包存在且类名也正确但还是报错大概率是 JMeter 没有重启。把 JMeter 完全关掉再启动一次这是最快验证方式。5.2 连接超时报Cannot create PoolableConnectionFactory这个报错说明 JDBC URL 里的地址、端口、数据库名、账号密码有问题或者 MySQL 服务端拒绝了连接。建议直接用命令行客户端试一下同样的地址端口账号密码能不能连上mysql -h127.0.0.1 -P3306 -utest_user -pTest123456 test_db如果命令行能连上但 JMeter 连不上检查一下 JDBC URL 里的 host 是否写成了localhost。很多场景下localhost会被解析成 IPv6 地址::1而 MySQL 可能只监听了 IPv4 的 127.0.0.1。这种坑特别隐蔽建议 JDBC URL 里写127.0.0.1而不是localhost。另外检查 MySQL 的max_connections设置。如果你在 JMeter 线程组里设置了大量线程每个线程都可能从连接池获取连接加上 JMeter 本身还有其他连接池很快会把 MySQL 连接数打满。命令行执行SHOW VARIABLES LIKE max_connections;看一下当前上限再根据这个值调整 JMeter 连接池的大小。5.3 时区报错Server returns invalid timezone这个报错在 MySQL 8.x 上非常常见。字面意思就是 MySQL 服务端返回的时区信息被 JDBC 驱动判定为无效。大多数情况是因为安装 MySQL 时没有配置默认时区默认使用的是系统时区但这个时区在 JDBC 驱动看来是未知的。解决方法有两个一是在 JDBC URL 后面加上serverTimezoneAsia/Shanghai二是修改 MySQL 服务端的时区配置SET GLOBAL time_zone 08:00; SET time_zone 08:00;我一般推荐两种都做。URL 里加上参数保证 JMeter 侧没问题MySQL 侧也修改时区保证其他工具连接时不报错。注意如果 MySQL 的time_zone改了之后还需要在配置文件my.cnf里加default-time-zone 08:00否则 MySQL 服务重启后时区又变回去了。5.4 SSL 连接报错和乱码问题如果你的 JDBC URL 没有显式配置useSSLfalseMySQL 8.x 的 JDBC 驱动默认使用 SSL 连接。本地测试环境没有配置 SSL 证书时会报类似Establishing SSL connection without servers identity verification is not recommended的警告。这个警告本身不阻断连接但很烦人而且后续如果证书有问题可能直接报错。解决办法就是 URL 里加上useSSLfalse。如果你确实需要 SSL那得把证书配置好那就超出造数的范畴了不在本文讨论。中文乱码的问题通常是连接字符集和表字符集不一致导致的。表用了 utf8mb4但连接没有指定characterEncodingutf8查询和插入时中文就可能出现问号。连接 URL 里加上characterEncodingutf8基本能解决。5.5 插入数据时报字段长度超限或数据重复这类问题属于 SQL 本身或参数化规则导致的。最常见的字段长度超限就是你生成的数据超过了表格字段定义的长度。比如phone VARCHAR(20)如果你生成的手机号因为拼接问题变成 12 位或更多字符就会报Data too long for column phone。定位方法就是把当前这条 SQL 的参数值打印出来看一眼多在 JMeter 的查看结果树里打开Sampler result标签页看请求体里的实际参数值。数据重复的问题通常是唯一索引或主键冲突导致的。造数时如果用了__time(/)这种毫秒级时间戳加随机数拼接作为 user_no在高并发下依然有可能重复概率不高但存在。解决方法是直接使用 UUID 或者更长位数的随机串或者用用户编号前缀 UUID拼接。5.6 造数速度太慢或数据库被压垮的调优方法造数速度慢和数据库被压垮看似是相反的问题根源其实都是线程数和连接池配置不合理。造数速度慢先看 JMeter 的聚合报告里 JDBC Request 的响应时间。如果平均响应时间已经很高说明数据库端已经忙不过来了加线程数不会变快反而会加剧等待。这时候应该反过来降低并发让每个请求处理得更快整体吞吐量反而可能上升。如果响应时间很低但整体造数速度就是上不去可能是 CSV 读取成了瓶颈或者固定定时器加太多了。数据库被压垮的表现是插入超时、锁等待超时、连接数打满。这时候最有效的调整是把线程数降低或者加一个定时器给数据库喘气的机会。同时检查 MySQL 的innodb_buffer_pool_size和max_connections配置别让造数脚本把数据库搞挂。6. 脚本打磨心得与后续还能怎么玩6.1 让造数脚本更可控善用用户自定义变量和属性写造数脚本的时候别把线程数、循环次数、数据总量这些关键参数写死在组件配置里。我习惯把这些参数都提到用户自定义变量组件里统一管理THREAD_COUNT线程数LOOP_COUNT每个线程循环次数DB_HOST数据库地址DB_PORT数据库端口DB_NAME数据库名DB_USER数据库用户名DB_PASS数据库密码这样你每次造数只需要改几个变量值不需要进去翻各个组件的配置。尤其是数据库地址变更这种情况——测试环境数据库从一台机器迁移到另一台如果脚本里到处都硬编码了旧地址改起来会非常痛苦。如果你的测试环境有多套dev、test、staging还可以利用 JMeter 的-J参数在启动时动态传入配置jmeter -n -t create_test_data.jmx -JTHREAD_COUNT50 -JLOOP_COUNT2000 -JDB_HOST192.168.1.100脚本里的用户自定义变量写成${__P(THREAD_COUNT,50)}这样默认值就是 50命令行传参时用传入的值。这样做的好处是同一份脚本可以随时切换目标环境不需要改脚本本身。6.2 加点校验确保造出来的数据是真正可用的造数不是把数据灌进去就完了还得验证造出来的数据是否符合业务预期。我一般会在造数脚本的最后加一个验证环节用一个额外的 JDBC Request 去查询总数和样例数据。SELECT COUNT(*) AS total FROM t_user; SELECT * FROM t_user ORDER BY id DESC LIMIT 10;在 JDBC Request 的下面可以加断言对查询结果做判断。比如用响应断言判断返回的内容里是否包含期望的数字或者用 JSR223 断言做更灵活的校验比如判断 count 结果是否大于某个阈值。这样脚本跑完后看一眼结果树就知道造数是否成功不用手动去命令行里查了。另一种更实用的验证方式是抽查几条数据确认关键字段的格式符合预期。比如手机号必须以1开头且长度为 11 位日期字段必须在合理的范围内。这些校验可以用 JSR223 断言配合 Groovy 正则表达式去实现虽然写起来麻烦一些但对于后续要用来做压测的数据质量保证非常值得。6.3 造数脚本的后续扩展思路这个系列讲的是 JMeter 造数但造的思路完全可以扩展到更多场景。我目前在工作踩过这些扩展方向给大家参考一是把造数脚本和测试数据清理脚本配合使用。造完数之后如果需要重复执行压力测试往往需要先清理上次造的数据。造数脚本的 SQL 逻辑里加一个 DELETE 条件在测试开始前执行清理保证每次压测的数据起点是相同的。二是构造关联表的数据。很多业务场景不是单表插入而是主表和子表都要有数据比如用户和订单。这时候可以先用 JMeter 批量造用户把生成的用户 ID 和 user_no 导出到 CSV 文件然后第二个线程组读取这个 CSV为每个用户生成若干条订单记录。注意两个线程组之间可以通过线程组 - 添加配置元件 - CSV 数据集配置来共享数据。三是配合常数吞吐量定时器控制造数速率。如果你需要在压测过程中同时造数比如模拟系统运行过程中持续有新用户注册的场景就可以用常数吞吐量定时器来精确控制每分钟插入多少条数据。这个和固定定时器的区别是它能根据目标吞吐量动态调整每次请求之间的间隔更接近于真实业务压力。四是把造数脚本纳入 CI/CD 流程。JMeter 支持命令行非 GUI 模式运行加上-J参数动态传入配置完全可以做成一个 Jenkins 任务。测试环境重置之后一键触发造数任务半小时后数据自动就位。这个在实际项目中节省的时间非常可观尤其是你经常需要重置环境重跑全量测试的时候。我个人在多次项目里用这套方案造数最大的体会是造数脚本写得好不好直接决定了后续压测的效率和数据的可信度。很多性能测试结论最后被质疑根因就是测试数据太假——用户分布不合理、数据量不足、字段都是重复值。花一点精力把造数脚本打磨好看起来是在准备工作上多花了时间但它换来的是整个测试过程的可重复性和结果的可信度。这套方法从 JMeter 5.1 时代一直用到现在不管版本怎么升级核心思路基本不变希望对你有帮助。