接手过几套生产 MySQL 之后,我越发觉得:sysbench 压测这件事,不是跑个命令把 TPS 截图发群里就算干完了。它最大的价值,是在新库上线前、或者一次大的参数调整之后,用一套标准化的负载提前暴露那些“平时看着没事、一上线就出问题”的隐患——磁盘 IO 扛不住、复制延迟突然升高、连接数撑不住峰值、某些 SQL 在压力下的执行计划完全跑偏。这篇文章是《关于生产环境下的 MySQL》系列的中篇,专门聊 sysbench 压测。我见过不少团队做压测,就是拿默认参数跑了个 oltp_read_only,看到 TPS 两万就以为数据库稳了。结果上线第一个大促,业务读写一上来,延迟从 5ms 直接飙到 200ms。问题在哪?不是 sysbench 不准,而是压测的方法没对准生产环境的真实场景。这篇我就把压测从准备、执行到结果解读的完整链路拆开讲,包括我们自己踩过的坑和总结出来的判断方法。内容偏实操,适合正在准备新库上线、或者想评估现有实例性能冗余的 DBA 和后端同学参考。1. 压测到底在验证什么:Sysbench 和真实业务之间隔着一层纱1.1 你压的是基准模型,不是业务流量很多人把 sysbench 当成一个“数据库跑分工具”,这是一个常见的误用。sysbench 本质上是一个可编程的基准测试框架,它通过内置的 lua 脚本来模拟某种固定的负载模型。比如 oltp_read_write 模拟的是索引点查、范围查询、插入、更新、删除的混合流量,而 oltp_point_select 则纯粹模拟主键点查。这些模型的特点是:SQL 模式固定、访问分布均匀、没有业务上的热点和倾斜。这和真实业务差了多远?举几个例子:真实场景里的 SQL 通常长短不一,有的查询要关联五六张表,有的更新会触发大范围二级索引变更;真实数据的访问频率往往是幂律分布的,热门商品、热门用户会被反复查询,而 sysbench 默认生成的随机主键访问是均匀分布的;真实事务的并发模式也和脚本不同,业务系统里会有长事务、行锁等待、死锁重试,sysbench 的简单事务模型很难完整复现这些行为。所以我对压测的定位从来不是“模拟线上流量”,而是“验证组件能力”。它最擅长回答的问题是:这个实例在某个并发度下,每秒能处理多少事务、延迟处于什么水平、瓶颈在哪一层。上线前用 sysbench 压一轮,我能判断当前的磁盘能否支撑预估的写入吞吐、连接池的并发设置是否合理、buffer pool 是否够用。这些问题一旦到线上才暴露,代价就非常高了。1.2 不同 lua 脚本的选择判定sysbench 从 1.0 开始内置了几个经典脚本,选择哪个要看你想验证什么:oltp_read_only:纯只读负载,适合评估查询能力、buffer pool 命中率和读路径的瓶颈。oltp_write_only:纯写入负载,适合评估磁盘刷盘能力、redo log 写入性能和 IO 子系统上限。oltp_read_write:读写混合负载,默认比例大致是 3 个点查、1 个范围查、1 个更新、1 个插入、1 个删除,更接近一般 OLTP 业务的整体形态。oltp_point_select:单条主键点查,主要用于测试 CPU 和索引查找的极限,通常结果最高。实际操作中,如果你只打算跑一轮,就跑 oltp_read_write;如果是上线前常规体检,我会再加一轮 oltp_write_only 和 oltp_read_only,分别确认读和写的天花板。只跑混合负载容易掩盖某个单一场景的短板——比如你的磁盘写入其实已经快撑爆了,但混合模型里写入占比不高,可能总量上看不出来,单独跑一轮 write_only,redo log 的瓶颈就非常明显。1.3 生产环境压测必须建立安全护栏还有一个很多人不太放在心上的问题:压测打出来的负载是真实打到 MySQL 上的。如果直接在生产实例、在业务高峰时段跑,哪怕你只是跑五分钟,也可能把 RT 打上去,拖垮正在运行的业务。我们这边的原则是:不直接在核心业务实例上做压力测试。要么在从库、容灾实例上先验证大致的容量水位,要么在低峰期、用限流后的并发度去跑,而且全程盯着慢查询和监控大盘。新库上线前的压测则相对自由——它本来就是一台还没接流量的实例,压测可以放开了跑,因为现在出问题比上线后再出问题要好处理得多。注意:压测之前一定先在测试环境确认好并发度上限,不要一上来就 128 线程直接怼到生产库上。我见过压测把数据库连接数打满、把磁盘 IO util 跑到 100%,最后连监控都拉不起来的情况。压测也是需要“先小后大、逐步加压”的。2. 动手之前的硬准备:从 sysbench 安装到压测机的条件2.1 sysbench 版本怎么选,源码安装还是二进制sysbench 目前主流版本是 1.0.x,1.1 还在开发阶段。Linux 发行版的软件源里通常都带,但版本可能比较老(比如 0.4.12 这种上古版本)。0.4 和 1.0 的命令行参数差别非常大,网上很多老教程用的是 0.4 的--num-threads这类参数,拿到 1.0 上跑是会直接报错的。所以我的建议是:统一用 1.0.20,也是目前社区使用最普遍的版本。安装方式我推荐优先用包管理器:# CentOS / Rocky Linux yum install sysbench # Ubuntu / Debian apt install sysbench如果包管理器里的版本太旧,再考虑源码编译。源码编译并不复杂,依赖主要是 libtool、make、gcc、mysql-devel:wget https://github.com/akopytov/sysbench/archive/refs/tags/1.0.20.tar.gz tar zxvf 1.0.20.tar.gz && cd sysbench-1.0.20 ./autogen.sh ./configure --prefix=/usr/local/sysbench --with-mysql make -j$(nproc) make install注意编译时一定要保证 MySQL 客户端库能找得到。如果是用云厂商的 RDS,本机没装 mysql-devel,编译会卡在 MySQL 连接驱动上。此时可以退一步直接用 yum 装,反正压测走的是 MySQL 协议,不需要本地有 server。2.2 压测机的配置要求与网络压测最容易犯的一个错误是:数据库服务器很强大,但压测机自己先扛不住了。sysbench 本身是单机多线程工具,压测机在高并发下会消耗大量 CPU 来生成负载和解析结果。如果你的压测机只有 2 核,去压一台 32 核的数据库,线程一拉到 64,压测机 CPU 直接 100%,sysbench 报出来的 TPS 和延迟就全是噪声了。我给一个经验参考:压测机的 CPU 核数不要低于数据库实例核数的一半,内存不要低于 4GB,网络建议走内网,不要跨公网压测——公网的延迟抖动和带宽瓶颈会让结果完全失真。还有一点容易被忽略:压测机和数据库之间的网络延迟要提前确认。ping一下,如果是同机房同网段,延迟应该小于 0.5ms;如果是跨可用区,可能有 2~5ms。本地延迟过高时,sysbench 报出的延迟会包含大量网络往返时间,这时候你衡量的是网络质量而不是数据库性能。所以,压测尽量在数据库所在的内网环境里发起。2.3 测试表结构与 MySQL 基线记录建表方式 sysbench 会自己处理,但你要决定测试数据规模。我的默认口径是:表数量不低于 16 张,每张表的行数不低于 100 万行。为什么要这个量级?因为生产环境的数据量和 buffer pool 的关系决定了 IO 行为,如果数据总量远小于 buffer pool,所有数据都能常驻内存,测出来的结果就是“纯内存性能”,掩盖了磁盘 IO 的真实瓶颈。这个坑后面专门讲,这里先记住结论:压测数据要让 buffer pool 装不下。MySQL 侧的环境变量也要记录好,作为压测基线。建议至少记录以下几项:参数记录值示例用途版本MySQL 8.0.33不同版本的性能差异很大buffer pool 大小12G决定数据是否能全部缓存redo log 容量innodb_redo_log_capacity = 16G影响写入吞吐上限实例规格16C32G和压测结果做对照磁盘类型SSD(本地盘)IO 能力影响延迟并发连接数上限max_connections = 1000防止压测把连接打满这些参数在压测开始前记录好,后面调优复测时才有对比基准。3. 从 prepare 到 run:完整的 sysbench 压测流程与关键参数3.1 prepare 阶段的数据量与建表参数sysbench 的压测分三个阶段:prepare(准备数据)→run(执行压测)→cleanup(清理数据)。prepare 阶段做的事情是创建测试库、建表、插入指定行数的随机数据。以 oltp_read_write 为例,准备数据的命令长这样:sysbench /usr/local/share/sysbench/oltp_read_write.lua \ --mysql-host=192.168.1.100 \ --mysql-port=3306 \ --mysql-user=bench_user \ --mysql-password=your_password \ --mysql-db=bench_db \ --tables=16 \ --table-size=1000000 \ --threads=8 \ prepare几个参数说明:--tables=16:生成 16 张表。多表比单表更接近真实库表结构,也能分散 InnoDB 内部的竞争。--table-size=1000000:每张表 100 万行。16 张表就是 1600 万行,按每行约 200 字节算,总数据量在 3GB 左右。--threads=8:prepare 阶段不用太高并发,避免导入数据时把磁盘 IO 打满,影响后续测试环境。--mysql-db=bench_db:测试库需要提前创建。prepare 阶段会比较久。数据量 3GB 左右、普通 SSD 磁盘,大概 5~10 分钟能完成。如果用了较大的数据量(比如 10GB 以上),建议在凌晨或者空闲时段准备,避免长时间占用 IO。注意:prepare 生成的表都是 InnoDB 表,主键是自增整数,索引和数据都是随机字符串。它和线上表的存储特征不完全一样,但作为基准测试已经足够。对于压测来说,数据是否“像线上”远不如“数据量级是否足够大到触发磁盘 IO”重要。3.2 run 阶段的核心参数和完整命令数据准备好之后,进入正式的压测环节。完整的 run 命令:sysbench /usr/local/share/sysbench/oltp_read_write.lua \ --mysql-host=192.168.1.100 \ --mysql-port=3306 \ --mysql-user=bench_user \ --mysql-password=your_password \ --mysql-db=bench_db \ --tables=16 \ --table-size=1000000 \ --threads=16 \ --time=300 \ --report-interval=10 \ --rand-type=uniform \ --max-requests=0 \ run逐个说下参数含义:--threads=16:压测并发线程数,这是最重要的变量。一般从 8 开始,逐级往上加。--time=300:压测持续 300 秒。不建议低于 120 秒,时间太短数据波动大,MySQL 的 buffer pool 预热、刷盘行为都没有进入稳态。--report-interval=10:每 10 秒打印一次中间结果。这个对观察变化趋势很重要,后面看瓶颈拐点全靠它。--rand-type=uniform:主键访问是均匀随机分布。生产环境热点访问更明显,但做基准测试时 uniform 是标准做法。如果模拟热点,可以用--rand-type=pareto,能产生类似二八分布的效果。--max-requests=0:0 表示不限制总请求数,以--time为准。跑完一轮之后,记得把结果完整保留下来。sysbench 会在结束时输出一个汇总报告,包含事务统计、延迟统计、每秒执行次数等。这个报告是后续分析的第一手资料。3.3 输出日志里每个数字到底是什么意思一轮压测结束后,sysbench 会在终端输出类似这样的汇总(数据为示例值):SQL statistics: queries performed: read: 381744 write: 109070 other: 47216 total: 538030 transactions: 109070 (363.53 per sec.) queries: 538030 (1793.43 per sec.) ignored errors: 0 (0.00 per sec.) reconnects: 0 (0.00 per sec.) General statistics: total time: 300.0502s total number of events: 109070 Latency (ms): min: 3.12 avg: 43.27 max: 312.94 95th percentile: 78.59 sum: 4720500.32 Threads fairness: events (avg/stddev): 6816.8750/88.62 execution time (avg/stddev): 295.0313/0.78这里面的核心数字:transactions和per sec.:这就是事务数 TPS。上面示例是 363.53 TPS,说明这个实例在 16 线程下每秒能完成约 363 个混合读写事务。queries per sec.:这是 QPS,通常比 TPS 高,因为一个事务里包含多条 SQL。Latency里的avg和95th percentile:平均延迟和 95 分位延迟。95th percentile 比 avg 更能反映大多数请求的真实体验——平均值会被极端值拉高,但 95% 的请求延迟处于这个值以下,往往才是线上用户能感知到的性能边界。Threads fairness:各线程间任务分布是否均匀。如果 stddev 很大,说明负载分配不均,压测机可能已经达到瓶颈。3.4 cleanup 以及与定时压测的配合压测完成后,用 cleanup 清理数据:sysbench /usr/local/share/sysbench/oltp_read_write.lua \ --mysql-host=192.168.1.100 \ --mysql-port=3306 \ --mysql-user=bench_user \ --mysql-password=your_password \ --mysql-db=bench_db \ --tables=16 \ --table-size=1000000 \ cleanupcleanup 会删掉全部测试表。我会在压测结束后把测试库整个 drop 掉,避免测试数据残留在生产实例上占空间。如果要在同一台实例上做多轮压测,建议每轮之间重新 prepare,因为上一轮的测试可能已经把表结构搞乱了,缓冲池里的也全是测试数据,重新 prepare 能保证每轮起始条件一致。4. 解读结果:不要只盯着 TPS,要看延迟曲线和瓶颈拐点4.1 一组典型的压测结果示例我拿之前做过的一轮压测举例。环境是 16C32G 的云主机,测试库 16 张表、每张 100 万行,buffer pool 设成 12G,磁盘为 SSD。sysbench oltp_read_write,线程数从 8 增加到 64,每轮 300 秒,结果汇总如下:线程数TPSQPSavg latency (ms)95th latency (ms)8520260015.324.616940470016.928.1321550775020.538.9641830915034.968.4这张表信息量很大。从 8 线程增加到 32 线程,TPS 几乎线性增长,延迟只从 15ms 涨到 20ms,说明系统资源还有冗余,瓶颈不在这。但当线程数从 32 翻到 64 时,TPS 只从 1550 涨到 1830,增幅不到 20%,但 95th 延迟从 38.9ms 涨到 68.4ms,接近翻倍。这就是典型的拐点信号:继续加并发已经换不来吞吐,只会让延迟急剧恶化。4.2 TPS、QPS、延迟分位数的判断逻辑所以解读结果的基本逻辑是:TPS 看趋势,延迟看分位,拐点定瓶颈。如果 TPS 随着并发线性增长,延迟保持在低位,说明实例还很空闲,可以继续加压。如果 TPS 增长放缓,同时延迟快速增长,说明某个资源已经接近极限——CPU、IO、锁竞争,三者必居其一。如果 TPS 不升反降,说明过高的并发已经引发了严重竞争,比如 InnoDB 的行锁争用、buffer pool mutex 争用、或者磁盘 IO 队列过长。具体的瓶颈定位要看监控。理想情况下,压测时应该开着监控面板观察数据库实例的 CPU、磁盘 IO util、IO 等待、InnoDB 行锁等待等指标。CPU 先到 100%,瓶颈在 CPU;磁盘 util 先到 100%,瓶颈在 IO;两者都没到顶但延迟开始飙升,通常是锁竞争或内部 mutex 争用。4.3 不同线程数梯度下的拐点识别跑压测时我喜欢用逐步加压的方式而不是一次拉满。做法是:8、16、32、64、128 逐级跑,每轮结束后记录 TPS 和延迟。五轮跑完,把结果放在同一张表里看,拐点一目了然。虽然比较费时间(每轮 300 秒,五轮就要 25 分钟),但对于定位系统瓶颈来说非常值得。加了--report-interval=10之后,还能看到每 10 秒的 TPS 波动。如果波动很大(比如从 1500 掉到 800 再涨回来),通常意味着系统在周期性刷盘或触发检查点,这时候要重点关注磁盘 IO 的表现。注意:sysbench 的延迟和 TPS 反映的是这 300 秒内的平均状态,但生产系统的峰值往往是突发的。建议在确认系统稳定后,把--threads设置为略低于拐点值的水平,再拉长--time到 600 秒做一轮长稳测试,看看长时间运行后 TPS 是否会下降。如果长稳测试后发现 TPS 逐 10 分钟阶梯式下降,大概率是内存或临时空间的问题。5. 压测过程中一定得注意的细节和坑5.1 测试数据量不足,结果虚高这是压测里最容易犯的、也最容易误导人的错误。如果你的测试数据总量只有 500MB,而 buffer pool 是 12G,那意味着整份测试数据都能完完整整地装进内存。所有的读请求都直接命中内存,磁盘读写比例极低,压测出来的 TPS 高得不像话。但生产环境不是这样的。生产库的数据量通常是 buffer pool 的几倍甚至几十倍,大量查询需要真正落到磁盘上。所以测试数据量至少要能填满缓冲池的 2~3 倍,才能模拟出真实的数据访问形态。一个快速判断方法:跑完一轮压测后,看监控里的 buffer pool 命中率,如果接近 100%,说明数据量太小或者访问模式太集中,需要加大数据量或者换成 pareto 分布访问。5.2 压测机先被打满,数据库表示很冤枉另一个常见的坑是压测机的性能不够。系统提示里也说了,压测机和数据库服务器是两个角色,压力工具本身的 CPU 和内存消耗不可低估。我遇到过一回:压测机是台 2 核 4G 的共享型机器,数据库是 32 核。线程数拉到 32 之后,压测机 CPU 直接 100%,sysbench 的 report-interval 输出变得非常慢,最终结果 TPS 只有预期的三分之一。后来把压测机换到同机房的 8 核专用机器上,同样参数跑出来 TPS 直接翻了两倍。所以,压测报告里最好同时记录压测机的 CPU 使用率。如果压测机 CPU 超过 80%,当前结果基本不具备参考价值。加并发之前先确认压测机有余量,这个步骤虽然简单,但能省掉很多排查时间。5.3 buffer pool 没预热和随机数据陷阱还有一种很有意思的现象:每次跑完 prepare 后,数据是全新的,buffer pool 是冷的。第一轮压测时,大量查询会触发磁盘读,TPS 普遍偏低;跑着跑着,数据慢慢进入 buffer pool,TPS 慢慢爬升。很多人在第一轮压测开始时看到 TPS 几百,就以为系统有问题。其实不是,而是在等待缓存预热。我的做法是:压测前先跑一个短时间的读负载(比如 60 秒的 point_select),把主要数据预热进 buffer pool,然后再开始正式压测。或者更简单一点,正式压测的前 60 秒数据不记录,等系统进入稳态后再看结果。另外,sysbench 生成的数据全是随机填充的随机字符串。这和线上真实数据相比,索引区分度更高,压缩效果更差。如果你要关注的是磁盘 IO 上限,随机数据其实是偏保守的——线上数据通常有更高的重复率,压缩和页合并效果会更好。5.4 结果可比性:每次压测都要固定变量压测最大的陷阱不是测不出来,而是测完一轮调了个参数,结果却不知道怎么对比。可变因素太多,比如 Sysbench 版本变了、测试机换了、网络延迟不同、MySQL 参数不同、测试数据不同——任何一个变了,结果都不具备可比性。我做压测有个习惯:把环境信息写进每次压测的备注里。像这样:# 压测环境备注 # 实例:生产-新-1 (16C32G SSD) # sysbench:1.0.20(yum 安装) # MySQL:8.0.33,innodb_buffer_pool_size=12G # 测试数据:16表*100万行,约3GB # 压测机:压测专用-8C16G(同机房内网) # 网络延迟:<0.5ms # 命令:见 /root/sysbench_scripts/run_oltp_rw.sh这一串备注看上去啰嗦,但当你一个月后回头看压测数据时,就会发现这是救命的。否则你根本不知道那次结果高是因为改好了参数,还是因为压测机换了台高配的。6. 从压测结果到生产环境:参数调整与二次验证的真实映射6.1 哪些参数能通过压测直接判断效果压测的价值不仅在于测一遍得出“行还是不行”,更在于验证参数调整后的实际效果。很多参数通过压测能直接看出差别,比如:innodb_buffer_pool_size:调大 buffer pool,读多写少的场景 TPS 通常会明显上升。压测数据量不变的情况下,buffer pool 从 8G 调到 16G,读命中率提升,avg latency 会下降。innodb_log_file_size或innodb_redo_log_capacity:写多场景下,redo log 太小会导致频繁切换、刷盘压力大,TPS 会周期性波动。增大容量后,写入 TPS 会更稳定。innodb_flush_log_at_trx_commit:从 1 改成 2 会提升写入性能,但会牺牲一部分崩溃安全。这个参数要在明确的业务容忍度下调整,不能只为了压测分数好看。max_connections:压测时线程数如果超过连接数上限,新连接会被拒绝,表现出的现象是“事务失败”。所以压测前可以先把这个值调大,避免它成为瓶颈,压测完再调回生产值。6.2 参数调整后的复测方法参数调整不是改完就行,还得用压测来验证。复测的方法其实就一句话:只改一个变量,然后跑同一套压测命令。这里面的“同一套”是指线程数、时间、测试数据量、sysbench 版本、压测机型号全部保持不变。我调优常用一个简单流程:先跑一遍当前参数,作为基线(比如 32 线程,TPS 1550)。修改目标参数,比如把innodb_buffer_pool_size从 12G 调到 16G。重启 MySQL 让参数生效。重新 prepare 测试数据(保证冷启动条件一致)。跑和步骤 1 完全一致的压测命令。对比结果。如果 TPS 提升超过 3%~5%,并且 95th 延迟有下降,说明调整有效;否则可以回滚。这个流程虽然简单,但执行起来要注意一个细节:改参数后不要立刻压测。重启后的 buffer pool 是冷的,直接压测会把冷启动的 IO 开销也算进去,导致结果偏低。正确的做法是先预热一段时间,或者把正式压测前 60 秒的结果丢弃掉。6.3 压测结论如何写进发布清单最后一点经验,关于压测结论的记录。我强烈建议把压测报告作为上线发布清单的必备项,而不是口头说一句“压过了”就完事。报告至少包含四块内容:压测环境(实例规格、sysbench 版本、测试数据量)、压测结果(不同并发梯度下的 TPS、延迟分位)、瓶颈分析(瓶颈在 CPU、IO 还是锁竞争)、建议的上限并发(也就是当前实例能稳定支撑的并发水平)。这部分做得好,后续容量规划、故障排查都会受益。一次压测花一小时,但给后面节省的时间远远不止这个数。写到这里,关于 sysbench 压测的完整流程基本聊完了。从选脚本、准备数据、跑命令、看结果,到调参复测,这一整套方法用熟之后,你会发现自己对生产环境的掌控力会上一个档次——至少在新库上线前,你心里会有底:这台实例能扛住多大并发,瓶颈在哪里,什么时候该扩容。这套方法我一直在用,也建议你在自己的压测环境里先跑几轮,把流程跑顺了再上生产。
接手过几套生产 MySQL 之后,我越发觉得:sysbench 压测这件事,不是跑个命令把 TPS 截图发群里就算干完了。它最大的价值,是在新库上线前、或者一次大的参数调整之后,用一套标准化的负载提前暴露那些“平时看着没事、一上线就出问题”的隐患——磁盘 IO 扛不住、复制延迟突然升高、连接数撑不住峰值、某些 SQL 在压力下的执行计划完全跑偏。这篇文章是《关于生产环境下的 MySQL》系列的中篇,专门聊 sysbench 压测。我见过不少团队做压测,就是拿默认参数跑了个 oltp_read_only,看到 TPS 两万就以为数据库稳了。结果上线第一个大促,业务读写一上来,延迟从 5ms 直接飙到 200ms。问题在哪?不是 sysbench 不准,而是压测的方法没对准生产环境的真实场景。这篇我就把压测从准备、执行到结果解读的完整链路拆开讲,包括我们自己踩过的坑和总结出来的判断方法。内容偏实操,适合正在准备新库上线、或者想评估现有实例性能冗余的 DBA 和后端同学参考。1. 压测到底在验证什么:Sysbench 和真实业务之间隔着一层纱1.1 你压的是基准模型,不是业务流量很多人把 sysbench 当成一个“数据库跑分工具”,这是一个常见的误用。sysbench 本质上是一个可编程的基准测试框架,它通过内置的 lua 脚本来模拟某种固定的负载模型。比如 oltp_read_write 模拟的是索引点查、范围查询、插入、更新、删除的混合流量,而 oltp_point_select 则纯粹模拟主键点查。这些模型的特点是:SQL 模式固定、访问分布均匀、没有业务上的热点和倾斜。这和真实业务差了多远?举几个例子:真实场景里的 SQL 通常长短不一,有的查询要关联五六张表,有的更新会触发大范围二级索引变更;真实数据的访问频率往往是幂律分布的,热门商品、热门用户会被反复查询,而 sysbench 默认生成的随机主键访问是均匀分布的;真实事务的并发模式也和脚本不同,业务系统里会有长事务、行锁等待、死锁重试,sysbench 的简单事务模型很难完整复现这些行为。所以我对压测的定位从来不是“模拟线上流量”,而是“验证组件能力”。它最擅长回答的问题是:这个实例在某个并发度下,每秒能处理多少事务、延迟处于什么水平、瓶颈在哪一层。上线前用 sysbench 压一轮,我能判断当前的磁盘能否支撑预估的写入吞吐、连接池的并发设置是否合理、buffer pool 是否够用。这些问题一旦到线上才暴露,代价就非常高了。1.2 不同 lua 脚本的选择判定sysbench 从 1.0 开始内置了几个经典脚本,选择哪个要看你想验证什么:oltp_read_only:纯只读负载,适合评估查询能力、buffer pool 命中率和读路径的瓶颈。oltp_write_only:纯写入负载,适合评估磁盘刷盘能力、redo log 写入性能和 IO 子系统上限。oltp_read_write:读写混合负载,默认比例大致是 3 个点查、1 个范围查、1 个更新、1 个插入、1 个删除,更接近一般 OLTP 业务的整体形态。oltp_point_select:单条主键点查,主要用于测试 CPU 和索引查找的极限,通常结果最高。实际操作中,如果你只打算跑一轮,就跑 oltp_read_write;如果是上线前常规体检,我会再加一轮 oltp_write_only 和 oltp_read_only,分别确认读和写的天花板。只跑混合负载容易掩盖某个单一场景的短板——比如你的磁盘写入其实已经快撑爆了,但混合模型里写入占比不高,可能总量上看不出来,单独跑一轮 write_only,redo log 的瓶颈就非常明显。1.3 生产环境压测必须建立安全护栏还有一个很多人不太放在心上的问题:压测打出来的负载是真实打到 MySQL 上的。如果直接在生产实例、在业务高峰时段跑,哪怕你只是跑五分钟,也可能把 RT 打上去,拖垮正在运行的业务。我们这边的原则是:不直接在核心业务实例上做压力测试。要么在从库、容灾实例上先验证大致的容量水位,要么在低峰期、用限流后的并发度去跑,而且全程盯着慢查询和监控大盘。新库上线前的压测则相对自由——它本来就是一台还没接流量的实例,压测可以放开了跑,因为现在出问题比上线后再出问题要好处理得多。注意:压测之前一定先在测试环境确认好并发度上限,不要一上来就 128 线程直接怼到生产库上。我见过压测把数据库连接数打满、把磁盘 IO util 跑到 100%,最后连监控都拉不起来的情况。压测也是需要“先小后大、逐步加压”的。2. 动手之前的硬准备:从 sysbench 安装到压测机的条件2.1 sysbench 版本怎么选,源码安装还是二进制sysbench 目前主流版本是 1.0.x,1.1 还在开发阶段。Linux 发行版的软件源里通常都带,但版本可能比较老(比如 0.4.12 这种上古版本)。0.4 和 1.0 的命令行参数差别非常大,网上很多老教程用的是 0.4 的--num-threads这类参数,拿到 1.0 上跑是会直接报错的。所以我的建议是:统一用 1.0.20,也是目前社区使用最普遍的版本。安装方式我推荐优先用包管理器:# CentOS / Rocky Linux yum install sysbench # Ubuntu / Debian apt install sysbench如果包管理器里的版本太旧,再考虑源码编译。源码编译并不复杂,依赖主要是 libtool、make、gcc、mysql-devel:wget https://github.com/akopytov/sysbench/archive/refs/tags/1.0.20.tar.gz tar zxvf 1.0.20.tar.gz && cd sysbench-1.0.20 ./autogen.sh ./configure --prefix=/usr/local/sysbench --with-mysql make -j$(nproc) make install注意编译时一定要保证 MySQL 客户端库能找得到。如果是用云厂商的 RDS,本机没装 mysql-devel,编译会卡在 MySQL 连接驱动上。此时可以退一步直接用 yum 装,反正压测走的是 MySQL 协议,不需要本地有 server。2.2 压测机的配置要求与网络压测最容易犯的一个错误是:数据库服务器很强大,但压测机自己先扛不住了。sysbench 本身是单机多线程工具,压测机在高并发下会消耗大量 CPU 来生成负载和解析结果。如果你的压测机只有 2 核,去压一台 32 核的数据库,线程一拉到 64,压测机 CPU 直接 100%,sysbench 报出来的 TPS 和延迟就全是噪声了。我给一个经验参考:压测机的 CPU 核数不要低于数据库实例核数的一半,内存不要低于 4GB,网络建议走内网,不要跨公网压测——公网的延迟抖动和带宽瓶颈会让结果完全失真。还有一点容易被忽略:压测机和数据库之间的网络延迟要提前确认。ping一下,如果是同机房同网段,延迟应该小于 0.5ms;如果是跨可用区,可能有 2~5ms。本地延迟过高时,sysbench 报出的延迟会包含大量网络往返时间,这时候你衡量的是网络质量而不是数据库性能。所以,压测尽量在数据库所在的内网环境里发起。2.3 测试表结构与 MySQL 基线记录建表方式 sysbench 会自己处理,但你要决定测试数据规模。我的默认口径是:表数量不低于 16 张,每张表的行数不低于 100 万行。为什么要这个量级?因为生产环境的数据量和 buffer pool 的关系决定了 IO 行为,如果数据总量远小于 buffer pool,所有数据都能常驻内存,测出来的结果就是“纯内存性能”,掩盖了磁盘 IO 的真实瓶颈。这个坑后面专门讲,这里先记住结论:压测数据要让 buffer pool 装不下。MySQL 侧的环境变量也要记录好,作为压测基线。建议至少记录以下几项:参数记录值示例用途版本MySQL 8.0.33不同版本的性能差异很大buffer pool 大小12G决定数据是否能全部缓存redo log 容量innodb_redo_log_capacity = 16G影响写入吞吐上限实例规格16C32G和压测结果做对照磁盘类型SSD(本地盘)IO 能力影响延迟并发连接数上限max_connections = 1000防止压测把连接打满这些参数在压测开始前记录好,后面调优复测时才有对比基准。3. 从 prepare 到 run:完整的 sysbench 压测流程与关键参数3.1 prepare 阶段的数据量与建表参数sysbench 的压测分三个阶段:prepare(准备数据)→run(执行压测)→cleanup(清理数据)。prepare 阶段做的事情是创建测试库、建表、插入指定行数的随机数据。以 oltp_read_write 为例,准备数据的命令长这样:sysbench /usr/local/share/sysbench/oltp_read_write.lua \ --mysql-host=192.168.1.100 \ --mysql-port=3306 \ --mysql-user=bench_user \ --mysql-password=your_password \ --mysql-db=bench_db \ --tables=16 \ --table-size=1000000 \ --threads=8 \ prepare几个参数说明:--tables=16:生成 16 张表。多表比单表更接近真实库表结构,也能分散 InnoDB 内部的竞争。--table-size=1000000:每张表 100 万行。16 张表就是 1600 万行,按每行约 200 字节算,总数据量在 3GB 左右。--threads=8:prepare 阶段不用太高并发,避免导入数据时把磁盘 IO 打满,影响后续测试环境。--mysql-db=bench_db:测试库需要提前创建。prepare 阶段会比较久。数据量 3GB 左右、普通 SSD 磁盘,大概 5~10 分钟能完成。如果用了较大的数据量(比如 10GB 以上),建议在凌晨或者空闲时段准备,避免长时间占用 IO。注意:prepare 生成的表都是 InnoDB 表,主键是自增整数,索引和数据都是随机字符串。它和线上表的存储特征不完全一样,但作为基准测试已经足够。对于压测来说,数据是否“像线上”远不如“数据量级是否足够大到触发磁盘 IO”重要。3.2 run 阶段的核心参数和完整命令数据准备好之后,进入正式的压测环节。完整的 run 命令:sysbench /usr/local/share/sysbench/oltp_read_write.lua \ --mysql-host=192.168.1.100 \ --mysql-port=3306 \ --mysql-user=bench_user \ --mysql-password=your_password \ --mysql-db=bench_db \ --tables=16 \ --table-size=1000000 \ --threads=16 \ --time=300 \ --report-interval=10 \ --rand-type=uniform \ --max-requests=0 \ run逐个说下参数含义:--threads=16:压测并发线程数,这是最重要的变量。一般从 8 开始,逐级往上加。--time=300:压测持续 300 秒。不建议低于 120 秒,时间太短数据波动大,MySQL 的 buffer pool 预热、刷盘行为都没有进入稳态。--report-interval=10:每 10 秒打印一次中间结果。这个对观察变化趋势很重要,后面看瓶颈拐点全靠它。--rand-type=uniform:主键访问是均匀随机分布。生产环境热点访问更明显,但做基准测试时 uniform 是标准做法。如果模拟热点,可以用--rand-type=pareto,能产生类似二八分布的效果。--max-requests=0:0 表示不限制总请求数,以--time为准。跑完一轮之后,记得把结果完整保留下来。sysbench 会在结束时输出一个汇总报告,包含事务统计、延迟统计、每秒执行次数等。这个报告是后续分析的第一手资料。3.3 输出日志里每个数字到底是什么意思一轮压测结束后,sysbench 会在终端输出类似这样的汇总(数据为示例值):SQL statistics: queries performed: read: 381744 write: 109070 other: 47216 total: 538030 transactions: 109070 (363.53 per sec.) queries: 538030 (1793.43 per sec.) ignored errors: 0 (0.00 per sec.) reconnects: 0 (0.00 per sec.) General statistics: total time: 300.0502s total number of events: 109070 Latency (ms): min: 3.12 avg: 43.27 max: 312.94 95th percentile: 78.59 sum: 4720500.32 Threads fairness: events (avg/stddev): 6816.8750/88.62 execution time (avg/stddev): 295.0313/0.78这里面的核心数字:transactions和per sec.:这就是事务数 TPS。上面示例是 363.53 TPS,说明这个实例在 16 线程下每秒能完成约 363 个混合读写事务。queries per sec.:这是 QPS,通常比 TPS 高,因为一个事务里包含多条 SQL。Latency里的avg和95th percentile:平均延迟和 95 分位延迟。95th percentile 比 avg 更能反映大多数请求的真实体验——平均值会被极端值拉高,但 95% 的请求延迟处于这个值以下,往往才是线上用户能感知到的性能边界。Threads fairness:各线程间任务分布是否均匀。如果 stddev 很大,说明负载分配不均,压测机可能已经达到瓶颈。3.4 cleanup 以及与定时压测的配合压测完成后,用 cleanup 清理数据:sysbench /usr/local/share/sysbench/oltp_read_write.lua \ --mysql-host=192.168.1.100 \ --mysql-port=3306 \ --mysql-user=bench_user \ --mysql-password=your_password \ --mysql-db=bench_db \ --tables=16 \ --table-size=1000000 \ cleanupcleanup 会删掉全部测试表。我会在压测结束后把测试库整个 drop 掉,避免测试数据残留在生产实例上占空间。如果要在同一台实例上做多轮压测,建议每轮之间重新 prepare,因为上一轮的测试可能已经把表结构搞乱了,缓冲池里的也全是测试数据,重新 prepare 能保证每轮起始条件一致。4. 解读结果:不要只盯着 TPS,要看延迟曲线和瓶颈拐点4.1 一组典型的压测结果示例我拿之前做过的一轮压测举例。环境是 16C32G 的云主机,测试库 16 张表、每张 100 万行,buffer pool 设成 12G,磁盘为 SSD。sysbench oltp_read_write,线程数从 8 增加到 64,每轮 300 秒,结果汇总如下:线程数TPSQPSavg latency (ms)95th latency (ms)8520260015.324.616940470016.928.1321550775020.538.9641830915034.968.4这张表信息量很大。从 8 线程增加到 32 线程,TPS 几乎线性增长,延迟只从 15ms 涨到 20ms,说明系统资源还有冗余,瓶颈不在这。但当线程数从 32 翻到 64 时,TPS 只从 1550 涨到 1830,增幅不到 20%,但 95th 延迟从 38.9ms 涨到 68.4ms,接近翻倍。这就是典型的拐点信号:继续加并发已经换不来吞吐,只会让延迟急剧恶化。4.2 TPS、QPS、延迟分位数的判断逻辑所以解读结果的基本逻辑是:TPS 看趋势,延迟看分位,拐点定瓶颈。如果 TPS 随着并发线性增长,延迟保持在低位,说明实例还很空闲,可以继续加压。如果 TPS 增长放缓,同时延迟快速增长,说明某个资源已经接近极限——CPU、IO、锁竞争,三者必居其一。如果 TPS 不升反降,说明过高的并发已经引发了严重竞争,比如 InnoDB 的行锁争用、buffer pool mutex 争用、或者磁盘 IO 队列过长。具体的瓶颈定位要看监控。理想情况下,压测时应该开着监控面板观察数据库实例的 CPU、磁盘 IO util、IO 等待、InnoDB 行锁等待等指标。CPU 先到 100%,瓶颈在 CPU;磁盘 util 先到 100%,瓶颈在 IO;两者都没到顶但延迟开始飙升,通常是锁竞争或内部 mutex 争用。4.3 不同线程数梯度下的拐点识别跑压测时我喜欢用逐步加压的方式而不是一次拉满。做法是:8、16、32、64、128 逐级跑,每轮结束后记录 TPS 和延迟。五轮跑完,把结果放在同一张表里看,拐点一目了然。虽然比较费时间(每轮 300 秒,五轮就要 25 分钟),但对于定位系统瓶颈来说非常值得。加了--report-interval=10之后,还能看到每 10 秒的 TPS 波动。如果波动很大(比如从 1500 掉到 800 再涨回来),通常意味着系统在周期性刷盘或触发检查点,这时候要重点关注磁盘 IO 的表现。注意:sysbench 的延迟和 TPS 反映的是这 300 秒内的平均状态,但生产系统的峰值往往是突发的。建议在确认系统稳定后,把--threads设置为略低于拐点值的水平,再拉长--time到 600 秒做一轮长稳测试,看看长时间运行后 TPS 是否会下降。如果长稳测试后发现 TPS 逐 10 分钟阶梯式下降,大概率是内存或临时空间的问题。5. 压测过程中一定得注意的细节和坑5.1 测试数据量不足,结果虚高这是压测里最容易犯的、也最容易误导人的错误。如果你的测试数据总量只有 500MB,而 buffer pool 是 12G,那意味着整份测试数据都能完完整整地装进内存。所有的读请求都直接命中内存,磁盘读写比例极低,压测出来的 TPS 高得不像话。但生产环境不是这样的。生产库的数据量通常是 buffer pool 的几倍甚至几十倍,大量查询需要真正落到磁盘上。所以测试数据量至少要能填满缓冲池的 2~3 倍,才能模拟出真实的数据访问形态。一个快速判断方法:跑完一轮压测后,看监控里的 buffer pool 命中率,如果接近 100%,说明数据量太小或者访问模式太集中,需要加大数据量或者换成 pareto 分布访问。5.2 压测机先被打满,数据库表示很冤枉另一个常见的坑是压测机的性能不够。系统提示里也说了,压测机和数据库服务器是两个角色,压力工具本身的 CPU 和内存消耗不可低估。我遇到过一回:压测机是台 2 核 4G 的共享型机器,数据库是 32 核。线程数拉到 32 之后,压测机 CPU 直接 100%,sysbench 的 report-interval 输出变得非常慢,最终结果 TPS 只有预期的三分之一。后来把压测机换到同机房的 8 核专用机器上,同样参数跑出来 TPS 直接翻了两倍。所以,压测报告里最好同时记录压测机的 CPU 使用率。如果压测机 CPU 超过 80%,当前结果基本不具备参考价值。加并发之前先确认压测机有余量,这个步骤虽然简单,但能省掉很多排查时间。5.3 buffer pool 没预热和随机数据陷阱还有一种很有意思的现象:每次跑完 prepare 后,数据是全新的,buffer pool 是冷的。第一轮压测时,大量查询会触发磁盘读,TPS 普遍偏低;跑着跑着,数据慢慢进入 buffer pool,TPS 慢慢爬升。很多人在第一轮压测开始时看到 TPS 几百,就以为系统有问题。其实不是,而是在等待缓存预热。我的做法是:压测前先跑一个短时间的读负载(比如 60 秒的 point_select),把主要数据预热进 buffer pool,然后再开始正式压测。或者更简单一点,正式压测的前 60 秒数据不记录,等系统进入稳态后再看结果。另外,sysbench 生成的数据全是随机填充的随机字符串。这和线上真实数据相比,索引区分度更高,压缩效果更差。如果你要关注的是磁盘 IO 上限,随机数据其实是偏保守的——线上数据通常有更高的重复率,压缩和页合并效果会更好。5.4 结果可比性:每次压测都要固定变量压测最大的陷阱不是测不出来,而是测完一轮调了个参数,结果却不知道怎么对比。可变因素太多,比如 Sysbench 版本变了、测试机换了、网络延迟不同、MySQL 参数不同、测试数据不同——任何一个变了,结果都不具备可比性。我做压测有个习惯:把环境信息写进每次压测的备注里。像这样:# 压测环境备注 # 实例:生产-新-1 (16C32G SSD) # sysbench:1.0.20(yum 安装) # MySQL:8.0.33,innodb_buffer_pool_size=12G # 测试数据:16表*100万行,约3GB # 压测机:压测专用-8C16G(同机房内网) # 网络延迟:<0.5ms # 命令:见 /root/sysbench_scripts/run_oltp_rw.sh这一串备注看上去啰嗦,但当你一个月后回头看压测数据时,就会发现这是救命的。否则你根本不知道那次结果高是因为改好了参数,还是因为压测机换了台高配的。6. 从压测结果到生产环境:参数调整与二次验证的真实映射6.1 哪些参数能通过压测直接判断效果压测的价值不仅在于测一遍得出“行还是不行”,更在于验证参数调整后的实际效果。很多参数通过压测能直接看出差别,比如:innodb_buffer_pool_size:调大 buffer pool,读多写少的场景 TPS 通常会明显上升。压测数据量不变的情况下,buffer pool 从 8G 调到 16G,读命中率提升,avg latency 会下降。innodb_log_file_size或innodb_redo_log_capacity:写多场景下,redo log 太小会导致频繁切换、刷盘压力大,TPS 会周期性波动。增大容量后,写入 TPS 会更稳定。innodb_flush_log_at_trx_commit:从 1 改成 2 会提升写入性能,但会牺牲一部分崩溃安全。这个参数要在明确的业务容忍度下调整,不能只为了压测分数好看。max_connections:压测时线程数如果超过连接数上限,新连接会被拒绝,表现出的现象是“事务失败”。所以压测前可以先把这个值调大,避免它成为瓶颈,压测完再调回生产值。6.2 参数调整后的复测方法参数调整不是改完就行,还得用压测来验证。复测的方法其实就一句话:只改一个变量,然后跑同一套压测命令。这里面的“同一套”是指线程数、时间、测试数据量、sysbench 版本、压测机型号全部保持不变。我调优常用一个简单流程:先跑一遍当前参数,作为基线(比如 32 线程,TPS 1550)。修改目标参数,比如把innodb_buffer_pool_size从 12G 调到 16G。重启 MySQL 让参数生效。重新 prepare 测试数据(保证冷启动条件一致)。跑和步骤 1 完全一致的压测命令。对比结果。如果 TPS 提升超过 3%~5%,并且 95th 延迟有下降,说明调整有效;否则可以回滚。这个流程虽然简单,但执行起来要注意一个细节:改参数后不要立刻压测。重启后的 buffer pool 是冷的,直接压测会把冷启动的 IO 开销也算进去,导致结果偏低。正确的做法是先预热一段时间,或者把正式压测前 60 秒的结果丢弃掉。6.3 压测结论如何写进发布清单最后一点经验,关于压测结论的记录。我强烈建议把压测报告作为上线发布清单的必备项,而不是口头说一句“压过了”就完事。报告至少包含四块内容:压测环境(实例规格、sysbench 版本、测试数据量)、压测结果(不同并发梯度下的 TPS、延迟分位)、瓶颈分析(瓶颈在 CPU、IO 还是锁竞争)、建议的上限并发(也就是当前实例能稳定支撑的并发水平)。这部分做得好,后续容量规划、故障排查都会受益。一次压测花一小时,但给后面节省的时间远远不止这个数。写到这里,关于 sysbench 压测的完整流程基本聊完了。从选脚本、准备数据、跑命令、看结果,到调参复测,这一整套方法用熟之后,你会发现自己对生产环境的掌控力会上一个档次——至少在新库上线前,你心里会有底:这台实例能扛住多大并发,瓶颈在哪里,什么时候该扩容。这套方法我一直在用,也建议你在自己的压测环境里先跑几轮,把流程跑顺了再上生产。