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秒输出一次中间结果
我的经验:线程数不要一开始就拉满。我一般从4、8、16、32、64这样递增,观察吞吐量(TPS)的拐点。超过某个值后,TPS不再增长甚至下降,那就是数据库的极限了。

自定义基准测试: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;
}
注意:上面的代码只是演示框架。实际使用时,你需要处理参数化查询(使用预处理语句),否则SQL注入和性能问题会让你头疼。我曾经在一个项目中直接用字符串拼接SQL,结果测试数据一多,内存直接爆了。

结果分析:看懂数据背后的故事

跑完测试,你会得到一堆数字。但数字本身没有意义,你得会解读。

关键指标

指标 含义 参考范围
TPS(每秒事务数) 数据库每秒能处理多少完整事务 OLTP场景:1000-10000+
QPS(每秒查询数) 数据库每秒能处理多少查询 简单查询:10000-100000+
平均延迟 每个请求的平均响应时间 OLTP:<10ms 优秀,<50ms 可接受
P99延迟 99%的请求在多少毫秒内完成 通常要求 <100ms
连接数 当前活跃连接数 取决于配置,通常几百到几千

分析步骤

  1. 看趋势:TPS是否稳定?有没有突然下降?
  2. 找拐点:随着并发增加,TPS增长变缓甚至下降的点在哪里?
  3. 查资源:CPU、内存、磁盘I/O、网络,哪个先到瓶颈?
  4. 对比基线:和之前的测试结果比,是变好了还是变差了?

我曾经踩过的坑:有一次测试结果特别漂亮,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);
}

知识体系图

下面这张图展示了基准测试的完整流程和关键环节:

数据库性能基准测试知识体系 测试准备 环境搭建 · 数据准备 工具选择 sysbench · 自定义C代码 执行测试 并发控制 · 时长设置 关键指标采集 TPS QPS 平均延迟 P99延迟 资源使用率 结果分析 趋势分析 拐点识别 瓶颈定位 基线对比 报告生成 · 结论与建议

实战建议

最后,分享几个我这些年总结出来的经验:

  • 先跑小规模测试:别一上来就压100万数据。先跑1万行,确认工具和脚本没问题。
  • 多次取平均值:每次测试跑3-5遍,取中位数或平均值。单次测试偶然因素太多。
  • 监控系统资源:用top、iostat、vmstat等工具同时监控。有时候数据库没问题,是磁盘扛不住了。
  • 保留原始数据:每次测试的原始输出都保存下来。方便以后回溯和对比。
  • 不要只看平均值:P99延迟往往比平均值更能反映真实体验。平均值可能被大量短请求拉低,掩盖了长尾延迟问题。

一个小技巧:测试前记得把数据库的查询缓存关掉。否则第二次测试会快得离谱,但那不是真实性能。我见过有人拿这个“优化成果”去汇报,结果上线后被打脸。

基准测试不是一次性工作。每次数据库升级、配置变更、硬件更换,都应该重新跑一遍。只有持续测量,你才能知道系统到底处于什么状态。

公众号:蓝海资料掘金营,微信deep3321