
1. tpcc-mysql 与 TPC-C 的对应关系为什么端口会成为第一道坎把一套数据库基准测试跑起来最磨人的地方往往不是业务模型有多复杂而是最开始那一下连不上。我见过太多人卡在这一步库部署在 3307 端口压测工具的参数照着教程敲下去回车之后屏幕上只有一句冷冰冰的Cant connect to MySQL server on 127.0.0.1 (111)然后开始怀疑网络、怀疑防火墙、怀疑服务没起来折腾两个小时才发现是参数写错了。tpcc-mysql 这套工具本身不复杂但它有几个设计上的历史包袱尤其是连接参数的处理方式非常容易让人在端口这件事上栽跟头。先把话说明白tpcc-mysql 是一套用来做 TPC-C 基准测试的开源工具集主体是一堆 C 源码编译出两个可执行程序一个负责建库灌数据一个负责发起压测。很多资料里把后者叫 TPCCRunner、tpcc-runner 或者 tpcc_start叫法不统一但指的是同一个东西——那个模拟终端用户不停下单、付款、查订单、发货、查库存的运行器。它能告诉你一套 MySQL 实例在标准交易模型下每分钟能跑多少个新订单也就是大家常说的 tpmC。适合谁来参考准备做数据库选型对比的、要给新硬件出一份基准报告的、怀疑线上配置没调好想验证一下的以及单纯想搞明白 TPC-C 到底测的是什么的人。1.1 五类业务事务落到具体表上是什么样子TPC-C 模拟的是一个商品批发商的订单系统一共九张表仓库、区域、客户、历史订单、新订单、订单头、订单行、商品、库存。压测时五类事务按固定比例混合执行新订单占 45%付款占 43%订单查询占 4%发货占 4%库存查询占 4%。另外新订单和付款各有约 1% 的概率走回滚分支模拟真实业务里的失败重试。这五类事务的负载特征差异很大理解这一点才能看懂后面的结果。新订单是纯写入重负载要往订单头、订单行、新订单三张表插数据还要更新库存表的销量和订单数同时读商品、区域、仓库、客户。付款主要是更新写历史表、更新区域和仓库的销售额、更新客户余额。订单查询是纯读读客户、订单头、订单行。发货最重要先删新订单、更新订单头、更新订单行的发货时间、更新客户余额属于典型的范围删加更新。库存查询最轻读一下库存和订单行算个聚合。为什么值得掰扯这些因为后面你看到某个事务类型拖后腿、或者一致性校验里事务占比不对根子往往就在这几张表的索引设计或者锁竞争上而不是工具本身有问题。1.2 两个可执行程序各管一段别混着用编译出来的第一个程序负责装载数据参数里要指定主机、端口、库名、账号、密码、仓库数。仓库数是核心变量一个仓库大约对应 100MB 上下的数据量含索引随引擎和页大小浮动10 个仓库差不多 1GB 出头100 个仓库就是 10GB 级别。装载过程是单线程串行的吭哧吭哧往库里灌机器不行的话几个仓库就能耗掉半小时。第二个程序就是压测运行器除了连接参数还要给并发连接数、预热时长、正式跑多久、报表间隔。这里有个细节很多人第一次会懵运行器的连接数不是线程数的概念它开的是真实的数据库连接每个连接内部模拟一个终端跑完一个事务接着跑下一个连接之间互不共享。所以并发连接数直接对应数据库侧看到的活跃会话数。两个程序的连接参数格式是一样的也正因如此端口写错导致的失败会同时出现在两个阶段容易让人误以为是自己灌数据的方式有问题。1.3 端口问题的真正来源比想象中多端口连不上绝大多数情况不是网络问题而是下面这几种第一种大写-P和小写-p搞混了。-P是端口-p是密码。这两个参数在视觉上几乎没区别尤其是从网页上复制命令的时候稍微一改就串了。你把-P 3307写成-p 3307工具会很开心地把3307当成密码然后连默认端口报的错跟端口无关是认证失败或者连不上。第二种主机名写成localhost。这个坑非常隐蔽。MySQL 的 C 客户端库在解析主机时有个约定俗成的行为主机为localhost时会优先走 Unix socket 文件而不是 TCP。走 socket 的话端口参数就是废的它连的是 socket 文件对应的那个实例。你明明写了 3307实际连的可能是 3306或者干脆连不上。解决办法很简单把localhost换成127.0.0.1强制走 TCP。第三种服务端只监听了本地地址或者监听地址不对。配置文件里bind-address设成127.0.0.1那你从另一台机器压测就怎么也连不上但本机用命令行客户端一切正常对比起来特别迷惑人。第四种工具版本压根不支持端口参数。这个后面单独说是真正需要动代码的情况。第五种容器或者多实例环境下的端口映射。宿主机上看到的是 13307容器里是 3306你压测命令里写哪个取决于你是从宿主机发起还是从容器网络内发起。这类问题排查时第一步永远是ss -lntp看一眼到底谁在听哪个端口。2. 从编译到灌数一套可复现的建库流程标题里提到测试方法我想先把方法这条线走通。很多教程给的步骤是对的但没有解释照着敲能跑中间断一次就完全不知道从哪儿接。下面这套流程是我自己反复用过、也在不同机器上复现过的。2.1 编译前必须补齐的依赖以及 makefile 里那两个路径源码是纯 C 写的依赖 gcc、make 和 MySQL 的客户端开发库。不同发行版的包名不一样基于 RPM 系的通常是mysql-devel或者mariadb-devel基于 DEB 系的是libmysqlclient-dev或者default-libmysqlclient-dev。装完之后用mysql_config --cflags和mysql_config --libs能分别拿到头文件路径和链接参数这两个输出是排查编译错误的关键。官方带的 Makefile 里通常硬编码了/usr/include/mysql和-lmysqlclient。如果你的客户端库装在别的路径下比如/usr/local/mysql/include那编译时会报一堆mysql.h: No such file or directory。这时候有两个选择改 Makefile 里的CFLAGS加上-I/usr/local/mysql/include或者更省事的办法直接把mysql_config的输出拼进去make CFLAGS$(mysql_config --cflags) -O2 -Wall LIBS$(mysql_config --libs)链接阶段如果报cannot find -lmysqlclient说明库文件路径没在链接器的搜索范围里检查一下mysql_config --libs输出里有没有-L参数有的话原样带上就行。编译成功后会得到两个可执行文件file一下确认是当前架构的 ELF别稀里糊涂拷了个别的平台的过来。还有一点MySQL 8.0 之后默认的认证插件变了老的客户端库可能不支持新的认证方式表现是连接时报Authentication plugin caching_sha2_password cannot be loaded。要么升级客户端库到 8.0 版本要么给测试账号单独指定老的认证插件。测试环境里后者更快但不建议在生产环境照做。2.2 建表脚本里的字符集与主键长度两个容易忽略的细节源码目录下有个脚本目录里面有建表、加外键和索引、删表、统计行数几个 SQL 文件。建表脚本是官方写的字段类型和主键都是按 TPC-C 规范来的但字符集和存储引擎这两处经常需要手动调整。字符集默认可能是早期的 utf8在 MySQL 8.0 上虽然还能用但排序规则会和utf8mb4不一致混着用的时候偶尔会出现比较行为差异。更稳妥的做法是建表前统一在脚本头部加一句SET NAMES utf8mb4;或者干脆把建表语句里的字符集改成utf8mb4。注意主键长度客户表的姓名字段是 16 字符订单行和商品描述里有 24 到 50 字符的字段用utf8mb4的话一个字符占 4 字节最长索引前缀要留够别因为长度超限导致建表失败。存储引擎没什么好纠结的TPC-C 是有事务的负载必须用支持事务的引擎。脚本里默认就是 InnoDB保持不动就行。但如果你在 MySQL 8.0 上跑注意NO_ZERO_DATE这个 SQL 模式老脚本里如果有DEFAULT 0000-00-00之类的写法会直接报错改掉或者临时放宽模式。外键和二级索引是单独一个脚本官方建议灌完数据再加原因下一节讲。2.3 灌数阶段的速度控制别一上来就追求一次灌满数据装载是单线程的每张表逐行插入速度主要受三个东西制约服务端的刷盘策略、表上有没有二级索引和外键、以及磁盘的随机写能力。第一条测试环境里可以适当放宽持久性换速度。把innodb_flush_log_at_trx_commit临时设成 2把二进制日志关掉这两项加起来对灌数速度的提升相当可观。注意这是测试环境专属操作灌完数据准备正式压测前要改回来否则测出来的 tpmC 会虚高失去参考意义。第二条先建表不带二级索引和外键灌完数据再补。这个顺序上的差异不是一点点——每插入一行都要维护一堆 B 树索引越多插入越慢外键还要额外做父表的存在性检查。我做过对比同一台机器同一份数据带全部索引灌和裸表灌时间能差出一倍以上。第三条按仓库分批灌。有的分支版本支持指定仓库区间可以一批批往里灌灌完一批确认一下行数再灌下一批。如果手上的版本只支持一次性指定总仓库数那就要接受一个现实中途断了不能续只能清空重来。这一点踩过一次就记住了灌到第 60 个仓库的时候机器被人重启那个心情不解释了。所以灌大数据量之前先在 1 个仓库的小规模上把整套流程跑通确认脚本、账号、权限、磁盘空间都没问题再上量。# 先灌 1 个仓库验证流程 ./tpcc_load -h 127.0.0.1 -P 3307 -d tpcc100 -u tpcc -p Test2024 -w 1 # 流程没问题后清空重来灌目标规模 ./tpcc_load -h 127.0.0.1 -P 3307 -d tpcc100 -u tpcc -p Test2024 -w 1002.4 灌完之后必做的四件确认数据灌完不等于可以开测了中间还有几步不能省。第一核对行数。源码里有统计脚本跑一遍看看各表行数是不是符合预期。仓库表应该等于仓库数区域表是仓库数乘以 10客户表是仓库数乘以 30000订单表是仓库数乘以 30000订单行大约是订单数乘以 10 上下浮动。对不上的话说明中途有事务失败被吞掉了这种数据拿去压测一致性校验大概率过不了。第二把二级索引和外键加回去加完之后再跑一次ANALYZE TABLE更新统计信息。这一步很重要优化器没有准确的基数统计执行计划可能选错索引测出来的数字和你以为的完全不是一回事。第三确认表空间和磁盘剩余空间。100 个仓库灌完差不多 10GB后面压测过程中还有 undo 和 redo 的增长磁盘剩个几百兆的时候开始跑跑到一半空间满了那个报错会非常难定位。第四记下数据规模、字符集、索引情况、服务端配置。后面要对比多轮结果这些元信息缺一项数字就没法横向比较。3. TPCCRunner 连接串里的端口-P 与 -p 的致命混淆这一节专门讲端口因为标题里点了这个关键词而且这确实是最高频的卡点。我把两个程序的参数、版本差异和三种改法都摆出来。3.1 两个程序的参数对照装载程序的常用参数参数含义备注-h数据库主机写127.0.0.1而不是localhost-P端口大写非默认端口必填-d数据库名需提前建好-u用户名需有建表和写入权限-p密码小写注意和端口区分-w仓库数量决定数据规模和后续压测上限压测运行器的常用参数在此基础上多了几个参数含义经验取值-c并发连接数从 16 起逐步加压到 CPU 核数的 2 到 4 倍-r预热秒数不少于 60冷缓存场景给到 300-l正式运行秒数不少于 300短于此噪声太大-i报表间隔秒数10 到 30看你想看多细-f结果输出文件强烈建议带上屏幕滚没了就没了-n是否跳过一致性检查一般不要跳这几个参数没有哪个是可有可无的。预热时间太短测出来的是缓存冷启动状态下的性能运行时间太短一次 checkpoint 抖动就能把结果带偏不加输出文件跑完发现关键那一屏被刷过去了只能重跑。3.2 大写 P 和小写 p以及 localhost 走 socket 的真相先说最要命的那个。命令里同时出现-P 3307和-p Passw0rd这两个参数在解析时是独立的不会互相干扰。但如果你手滑写成-p 3307 -p Passw0rd工具会取最后一次出现的值当密码端口就保持默认。反过来写成-P Passw0rd它会试图把Passw0rd解析成端口号得到一个 0 或者一个荒谬的值。排查这类问题有个笨但有效的办法把命令里的密码参数先改成肯定错的看报错是认证失败还是连接失败。如果是认证失败说明端口和主机是通的问题在账号如果是连接被拒绝说明根本没到认证那一步问题在端口和主机。这个二分法能省掉大量瞎猜。再说localhost。C 客户端库在mysql_real_connect里对主机名有特殊处理主机为 NULL 或者localhost时走本地 socket 连接端口参数被忽略。这意味着你把实例放在 3307命令里写-h localhost -P 3307实际连的是 socket 文件对应的实例可能是 3306也可能因为 socket 路径不匹配直接失败。改成-h 127.0.0.1就强制走 TCP端口参数立刻生效。这条经验适用范围很广几乎所有基于 MySQL C 客户端库的工具都有这个行为。3.3 手上版本不支持端口参数时改源码的具体做法有些渠道拿到的源码比较老连接函数里端口位置写死成 0参数解析也没有-P这一项。这种情况下只能在源码里动刀。核心就一处找到调用连接函数的地方通常是类似这样的代码if (mysql_real_connect(mysql, host, user, password, db, 0, NULL, 0) NULL) { fprintf(stderr, error: %s\n, mysql_error(mysql)); exit(1); }第五个参数就是端口写死成 0 意思是用默认值。把它改成一个变量然后在参数解析的地方加一个分支int port 3306; /* 在 getopt 的选项字符串里加上 P: */ while ((c getopt(argc, argv, h:P:d:u:p:w:c:r:l:i:f:)) ! -1) { switch (c) { case P: port atoi(optarg); break; /* 其他分支略 */ } } if (mysql_real_connect(mysql, host, user, password, db, port, NULL, 0) NULL) { fprintf(stderr, connect failed: %s\n, mysql_error(mysql)); exit(1); }改完重新编译。注意这个改动要在装载程序和压测程序两边都做因为它们是两份独立的源码文件各有一处连接调用。只改一边的后果是灌数据能连上、压测连不上或者反过来排查起来更绕。改源码还有个替代方案如果不想动代码可以在一台中转机上用端口转发把非标准端口映射成 127.0.0.1:3306工具连本机默认端口就行。这个办法在临时验证时很快但会引入额外的网络跳转和缓冲区压测结果会有偏差正式出报告时不要用。3.4 密码里的特殊字符以及配置文件到底会不会被读密码里带、#、$这些字符时一定要用单引号包起来。Shell 会先把$展开、把#当成注释起始你传给程序的密码已经不是你写的那个了。稳妥写法是-p Pss#w0rd$1。另一个常见误解有人以为程序会读~/.my.cnf里的[client]段把里面的 host 和 port 带进去。MySQL 的命令行客户端确实会读但基于 C API 自己写的程序默认不读除非显式调用了读取配置文件的接口。所以你在my.cnf里改了端口命令行客户端立刻生效压测工具却完全无视两者表现不一致很容易误判成工具坏了。4. 六类典型报错的定位路径与修复手法压测跑起来之后出的错可以按连接层、SQL 层、结果层三层来分。分层的好处是每一步都能把排查范围砍掉一半。4.1 连接层连不上、认证失败、连接数打满连接被拒绝错误码 111按这个顺序查服务进程在不在、监听端口对不对、监听地址是不是只有 127.0.0.1、防火墙有没有放行、命令里的-h和-P是不是写反了。用ss -lntp | grep 3307一眼就能看到谁在听哪个地址比猜快得多。认证失败先确认账号能不能从当前主机登录。MySQL 的账号是用户名主机的组合tpcclocalhost和tpcc127.0.0.1在权限表里是两条记录。压测机和被压机不是同一台时要确保账号的主机部分是%或者压测机的实际地址。连接数打满报Too many connections。这种情况通常不是服务端max_connections太小而是压测端开的连接数超过了预期或者上一轮跑完的连接没释放干净。先看服务端的Threads_connected当前值再回头核对压测命令里的-c参数。4.2 SQL 层值超范围、主键冲突、表不存在Out of range value一般出现在自增主键上。TPC-C 的订单号是按区域分配的每个区域 3000 个订单循环使用如果区域数量和数据规模对不上自增列可能越界。还有一种情况是老版本脚本里字段类型定义得偏小数据量一大就顶到上限。Duplicate entry说明有重复主键。原因多半是上一轮的数据没清干净又在同一批主键上重新插入。每次重跑之前必须把业务表清空别指望程序自己处理。Table doesnt exist除了字面意思还有一个隐蔽原因库名大小写。Linux 上库名表名默认区分大小写命令里写TPCC而实际库叫tpcc就会报这个错。养成习惯建库和命令里用完全一样的小写名字。4.3 结果层一致性校验不通过该怎么读压测结束会打印一段约束检查这是整份报告里信息量最大的部分很多人扫一眼就过去了。它主要检查两件事事务占比和响应时间。事务占比检查看五类事务的实际比例是不是接近标准值。正常的偏差在几个百分点以内。如果某类事务占比明显偏低说明它执行得慢被其他事务抢了时间片或者因为锁等待卡住了。这时候要去查那类事务涉及的表有没有热点行比如库存表在仓库维度上的争用。响应时间检查看每类事务的 90 分位是否在规定上限内。新订单、付款、订单查询、发货的上限都是 5 秒库存查询是 20 秒。如果有超过的说明系统在压力下已经出现了明显的长尾这时候报出来的 tpmC 数字没有参考价值先解决延迟问题再谈吞吐。最后那个 tpmC 数字不要孤立地看。TPC-C 规范里每个仓库的吞吐上限是 12.86 tpmC也就是说 N 个仓库的理论最大吞吐是 12.86 乘以 N。如果你测出来的数字接近甚至超过这个上限那不是系统太强而是数据量不够、压力没打满结果不可信。反过来如果远低于上限才是正常的调优空间。4.4 压测中途异常中断的定位方法跑到一半突然断了屏幕上只有一行错误这种情况最难受。先把输出文件拿出来看最后一屏是什么事务类型、什么错误码。如果有死锁Deadlock found大概率是并发太高导致行锁争用降低-c重试一次能跑通就说明是并发度问题不是数据问题。如果是连接被重置MySQL server has gone away通常是服务端的wait_timeout或者max_allowed_packet触发的。压测场景下事务都是短事务理论上不会超时但要确认压测端有没有长时间的空闲连接。还有就是服务端 OOM 被系统杀掉这个去看系统日志里有没有进程被终止的记录。还有一种情况是磁盘写满。redo log 和 undo 在压测期间增长很快尤其是在调大了并发和事务量的情况下。压测前预留的空间不要只按数据量算至少留出数据量的两到三倍。5. 服务端参数与客户端参数如何左右 tpmC 的绝对值同一套硬件参数不同tpmC 能差出一倍。这不是夸张是刷盘策略和日志配置直接决定的。但要注意一味追求高数字没有意义你要的是这套配置在真实场景下的表现而不是把所有安全阀都拆掉之后的极限值。5.1 服务端三个直接决定写入吞吐的开关第一个是 redo log 的刷盘策略。设成 1 表示每次事务提交都刷盘最安全也最慢设成 2 表示每秒刷一次崩溃可能丢一秒数据设成 0 表示交给后台线程性能最好但风险最高。TPC-C 是写密集负载这一项对 tpmC 的影响是决定性的。测试环境里可以拿三档各跑一次把差距量化出来写进报告里比一句我们调优了有说服力得多。第二个是 redo log 文件大小。早期版本默认只有几十兆高并发写入时频繁触发 checkpoint性能曲线会呈现出规律的锯齿。调大到几百兆甚至 1G 以上锯齿会明显平缓。8.0 之后这一项改成了动态调整但思路是一样的——日志空间要能容纳一段时间的写入量。第三个是双一配置里的另一个sync_binlog。如果开了二进制日志每次提交都要同步 binlog这个开销不小。纯测试环境可以关掉 binlog或者把同步策略放宽。但如果你要测的是和生产一致的配置那就老老实实保持开启接受这个损耗。还有一个容易忽略的点NUMA 绑定和 CPU 亲和性。数据库进程在多路机器上跨 NUMA 节点访问内存延迟会明显上升。压测前用工具把进程绑到一个节点上数字往往能稳定不少。5.2 客户端连接数、预热时长、仓库数三者的匹配关系并发连接数不是越大越好。连接数从 16 加到 64tpmC 可能翻倍从 64 加到 256可能只是持平甚至下降因为锁争用和上下文切换吃掉了收益。我的做法是从小到大扫一遍画出连接数-吞吐曲线找到拐点那个拐点对应的连接数才是这台机器的合理工作点。预热时长的作用是让缓存热起来、让连接池稳定、让第一次 checkpoint 过去。预热太短前几十秒的数据会明显偏低把平均值拉下去。给到 60 秒是下限数据量大或者存储慢的机器给到 300 秒更稳妥。仓库数和连接的匹配有个经验比例每 10 个仓库配 10 到 20 个连接比较合适。仓库太少而连接太多所有连接都在抢同几行数据锁等待会非常严重报表里能看到某类事务的 90 分位飙到几秒。仓库够多而连接太少压力打不满机器闲着。5.3 结果解读别只盯着 tpmC 那一个数一份完整的压测结果至少要看四个维度吞吐、延迟分位、事务占比、资源使用率。吞吐就是 tpmC 和每分钟新订单数这个大家都知道。延迟分位要看 90 分位和最大值平均值会掩盖长尾而长尾才是用户真正感受到的卡顿。事务占比前面说过了是判断负载是否均衡的重要依据。资源使用率要看 CPU、磁盘 IOPS、磁盘利用率和网络如果 CPU 只用了 40% 但磁盘已经 100%那瓶颈在存储加 CPU 没用。把这四项放在一起看才能回答这套配置能扛多少这个问题。单看一个数字很容易得出错误结论。6. 重复跑批时的环境复位与结果归档跑一轮压测不算本事能连续跑十轮、每轮结果都可复现、可对比才是这套方法真正落地的地方。这一节讲两个动作复位和归档。6.1 每轮之间到底要清什么最少要清三样东西。第一是业务数据把九张表按依赖顺序清空或者删了重建注意先删子表再删父表或者临时关掉外键检查。第二是查询缓存和缓冲池FLUSH TABLES加上重启实例最彻底不想重启的话至少把缓冲池清一遍否则下一轮跑的是热缓存状态和前一轮不可比。第三是统计信息重新ANALYZE一遍让优化器基于干净的数据做决策。还有一样容易忘的服务端的状态计数器。SHOW GLOBAL STATUS里的累计值会跨轮次累加如果你想用Com_*这类计数器算事务量一定要在两轮之间取差值而不是直接读绝对值。清完之后做一次快速校验各表行数是不是零、缓冲池大小是不是回到初始值、账号连接数是不是归零。三步确认花不了一分钟能避免一整轮的返工。6.2 一份能用的测试记录该包含哪些字段我习惯每轮都记一张表字段不多但每项都必要字段示例为什么记轮次编号R07后续引用和复盘数据规模100 仓库决定了 tpmC 上限并发连接64和吞吐一起看才有意义预热/运行120s / 600s短跑和长跑结果不可比刷盘策略redo2, binlog 关闭直接决定写入吞吐tpmC8642核心指标新订单 90 分位128ms判断长尾一致性检查全部通过不通过则结果作废异常备注第 4 分钟出现一次 checkpoint 抖动解释曲线上的坑这张表的价值在于后面你要回答为什么这次比上次快了 15%的时候能一秒钟定位到是改了哪个参数而不是翻聊天记录。6.3 硬件与网络噪声最容易被忽略的变量虚拟化环境里跑压测同一个配置两次结果差 20% 是常事。原因可能是宿主机上的邻居在抢 IO可能是 CPU 频率降频可能是网络中断合并策略不同。所以正式出报告前用完全相同的配置连跑三次看结果的离散程度。三次之间标准差超过 5%那这台机器就不是一个合适的基准测试平台。还有个小技巧压测机和被压机不要放在同一台物理机上否则两者抢 CPU 和内存带宽测出来的是互相干扰的结果。网络也尽量走万兆直连别经过多层交换和防火墙策略否则测的可能是网络设备。我在实际操作中的体会是最难的不是把工具跑起来而是建立一套结论可复现的习惯。同一套配置、同一份数据、同样的预热和运行时长跑三次取中位数把每次的资源配置和参数改动都记下来这样出来的数字才敢拿去支撑决策。工具本身只是秤秤准不准取决于你有没有认真做过校准。