79、数据库性能基准测试:sysbench集成、自定义基准测试、结果分析、报告生成
性能基准测试,说白了就是给数据库“体检”。
你想想看,一个系统上线前,连它能扛多少并发、读写延迟多少都不知道,那不是盲人摸象吗?我在项目中遇到过好几次,开发环境跑得飞快,一上生产就卡成PPT。后来我养成了一个习惯——任何数据库上线前,必须先跑一轮基准测试。
这一章,我们就来聊聊怎么做这件事。我会用sysbench这个工具,也会讲讲怎么自己写测试代码,最后把结果整理成报告。
为什么需要基准测试
基准测试不是压力测试,也不是功能测试。它的目的是:
- 摸清数据库的极限能力
- 对比不同配置、不同硬件下的表现
- 为容量规划提供数据支撑
- 发现性能瓶颈
嗯,这里要注意:基准测试的结果只代表“在特定场景下的表现”。换一个业务模型,结果可能完全不同。
sysbench集成:最常用的基准测试工具
sysbench是个老牌工具了。我最早接触它是在2010年左右,那时候它还是个纯粹的OLTP测试工具。现在它已经支持CPU、内存、文件I/O、数据库等多种测试。
安装sysbench
# Ubuntu/Debian
sudo apt-get install sysbench
# CentOS/RHEL
sudo yum install sysbench
# 从源码编译
git clone https://github.com/akopytov/sysbench.git
cd sysbench
./autogen.sh
./configure --with-mysql
make -j4
sudo make install
准备测试数据
# 创建测试数据库
mysql -u root -p -e "CREATE DATABASE sbtest;"
# 准备测试表和数据
sysbench /usr/share/sysbench/oltp_read_write.lua \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=root \
--mysql-password=your_password \
--mysql-db=sbtest \
--table-size=1000000 \
--tables=10 \
prepare
这里我习惯把表大小设为100万行,10张表。太小了测不出效果,太大了测试时间太长。
运行基准测试
sysbench /usr/share/sysbench/oltp_read_write.lua \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=root \
--mysql-password=your_password \
--mysql-db=sbtest \
--table-size=1000000 \
--tables=10 \
--threads=16 \
--time=60 \
--report-interval=10 \
run
参数说明:
--threads=16:并发线程数,模拟16个客户端--time=60:测试持续60秒--report-interval=10:每10秒输出一次中间结果
自定义基准测试:C语言实现
sysbench虽然好用,但有时候我们需要测试一些特定场景。比如,你的业务有大量批量插入操作,或者有复杂的存储过程调用。这时候,自己写测试代码更灵活。
一个简单的基准测试框架
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h>
#include <sys/time.h>
typedef struct {
MYSQL *conn;
int thread_id;
long queries;
long errors;
double total_time;
} benchmark_thread_t;
// 获取当前时间(微秒)
double get_time_us() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000000.0 + tv.tv_usec;
}
// 执行一次查询并记录耗时
int run_query(MYSQL *conn, const char *sql, double *elapsed) {
double start = get_time_us();
int ret = mysql_query(conn, sql);
*elapsed = (get_time_us() - start) / 1000000.0; // 转为秒
if (ret != 0) {
fprintf(stderr, "Query failed: %s\n", mysql_error(conn));
return -1;
}
MYSQL_RES *result = mysql_store_result(conn);
if (result) {
mysql_free_result(result);
}
return 0;
}
// 基准测试主函数
void run_benchmark(MYSQL *conn, int iterations) {
const char *test_queries[] = {
"SELECT * FROM sbtest1 WHERE id = ?",
"INSERT INTO sbtest1 (k, c, pad) VALUES (?, ?, ?)",
"UPDATE sbtest1 SET c = ? WHERE id = ?",
"DELETE FROM sbtest1 WHERE id = ?"
};
double total_elapsed = 0.0;
long success_count = 0;
long fail_count = 0;
for (int i = 0; i < iterations; i++) {
double elapsed;
int ret = run_query(conn, test_queries[i % 4], &elapsed);
if (ret == 0) {
total_elapsed += elapsed;
success_count++;
} else {
fail_count++;
}
}
printf("=== 基准测试结果 ===\n");
printf("总请求数: %d\n", iterations);
printf("成功: %ld\n", success_count);
printf("失败: %ld\n", fail_count);
printf("总耗时: %.3f 秒\n", total_elapsed);
printf("平均延迟: %.3f 毫秒\n", (total_elapsed / success_count) * 1000);
printf("吞吐量: %.2f QPS\n", success_count / total_elapsed);
}
int main(int argc, char *argv[]) {
MYSQL *conn = mysql_init(NULL);
if (!mysql_real_connect(conn, "127.0.0.1", "root",
"your_password", "sbtest", 3306, NULL, 0)) {
fprintf(stderr, "Connection failed: %s\n", mysql_error(conn));
return 1;
}
printf("数据库连接成功,开始基准测试...\n");
run_benchmark(conn, 1000);
mysql_close(conn);
return 0;
}
结果分析:看懂数据背后的故事
跑完测试,你会得到一堆数字。但数字本身没有意义,你得会解读。
关键指标
| 指标 | 含义 | 参考范围 |
|---|---|---|
| TPS(每秒事务数) | 数据库每秒能处理多少完整事务 | OLTP场景:1000-10000+ |
| QPS(每秒查询数) | 数据库每秒能处理多少查询 | 简单查询:10000-100000+ |
| 平均延迟 | 每个请求的平均响应时间 | OLTP:<10ms 优秀,<50ms 可接受 |
| P99延迟 | 99%的请求在多少毫秒内完成 | 通常要求 <100ms |
| 连接数 | 当前活跃连接数 | 取决于配置,通常几百到几千 |
分析步骤
- 看趋势:TPS是否稳定?有没有突然下降?
- 找拐点:随着并发增加,TPS增长变缓甚至下降的点在哪里?
- 查资源:CPU、内存、磁盘I/O、网络,哪个先到瓶颈?
- 对比基线:和之前的测试结果比,是变好了还是变差了?
我曾经踩过的坑:有一次测试结果特别漂亮,TPS高达20000。我高兴了半天,后来才发现测试数据全在内存里,根本没落盘。把sync_binlog和innodb_flush_log_at_trx_commit改成安全配置后,TPS直接掉到3000。所以,基准测试一定要在“真实配置”下跑。
报告生成:让数据说话
测试结果不能只留在终端里。你需要一份报告,让团队、让老板看得懂。
报告应该包含什么
- 测试环境:硬件配置、数据库版本、参数设置
- 测试场景:读写比例、数据量、并发数
- 测试结果:TPS、QPS、延迟分布、资源使用率
- 对比分析:和基线对比,和不同配置对比
- 结论与建议:瓶颈在哪,怎么优化
用C语言生成报告
void generate_report(const char *filename,
double tps, double qps,
double avg_latency, double p99_latency,
int threads, int duration) {
FILE *fp = fopen(filename, "w");
if (!fp) {
perror("Failed to create report file");
return;
}
fprintf(fp, "# 数据库基准测试报告\n\n");
fprintf(fp, "## 测试概况\n");
fprintf(fp, "- 测试时间: %s\n", get_current_time());
fprintf(fp, "- 并发线程数: %d\n", threads);
fprintf(fp, "- 测试时长: %d 秒\n", duration);
fprintf(fp, "- 数据库版本: MySQL 8.0\n\n");
fprintf(fp, "## 测试结果\n\n");
fprintf(fp, "| 指标 | 数值 |\n");
fprintf(fp, "|------|------|\n");
fprintf(fp, "| TPS | %.2f |\n", tps);
fprintf(fp, "| QPS | %.2f |\n", qps);
fprintf(fp, "| 平均延迟 | %.2f ms |\n", avg_latency);
fprintf(fp, "| P99延迟 | %.2f ms |\n", p99_latency);
fprintf(fp, "\n## 结论\n\n");
if (avg_latency < 10.0) {
fprintf(fp, "数据库性能表现优秀,延迟在可接受范围内。\n");
} else if (avg_latency < 50.0) {
fprintf(fp, "数据库性能表现一般,建议检查索引和查询优化。\n");
} else {
fprintf(fp, "数据库性能较差,需要重点关注。\n");
fprintf(fp, "建议:\n");
fprintf(fp, "1. 检查慢查询日志\n");
fprintf(fp, "2. 优化数据库配置参数\n");
fprintf(fp, "3. 考虑增加硬件资源\n");
}
fclose(fp);
printf("报告已生成: %s\n", filename);
}
知识体系图
下面这张图展示了基准测试的完整流程和关键环节:
实战建议
最后,分享几个我这些年总结出来的经验:
- 先跑小规模测试:别一上来就压100万数据。先跑1万行,确认工具和脚本没问题。
- 多次取平均值:每次测试跑3-5遍,取中位数或平均值。单次测试偶然因素太多。
- 监控系统资源:用top、iostat、vmstat等工具同时监控。有时候数据库没问题,是磁盘扛不住了。
- 保留原始数据:每次测试的原始输出都保存下来。方便以后回溯和对比。
- 不要只看平均值:P99延迟往往比平均值更能反映真实体验。平均值可能被大量短请求拉低,掩盖了长尾延迟问题。
一个小技巧:测试前记得把数据库的查询缓存关掉。否则第二次测试会快得离谱,但那不是真实性能。我见过有人拿这个“优化成果”去汇报,结果上线后被打脸。
基准测试不是一次性工作。每次数据库升级、配置变更、硬件更换,都应该重新跑一遍。只有持续测量,你才能知道系统到底处于什么状态。