数据库压力测试:并发测试、负载模型、性能瓶颈分析、优化建议

说到数据库压力测试,我脑子里立刻浮现出几年前的一个深夜。那会儿我们做一款物联网平台,设备接入量从几千突然涨到几十万,数据库直接挂了。嗯,从那以后,我就把压力测试当成了项目上线前的必修课。

说白了,压力测试就是模拟真实场景,看看你的数据库到底能扛多少并发。不是跑个SELECT 1就完事,而是要模拟用户怎么操作、数据怎么增长、连接怎么建立。今天我就把这套方法论掰开揉碎了讲给你听。

并发测试:别被表面数字骗了

并发测试,核心就两个指标:并发连接数实际活跃线程数。很多人以为连接数就是并发数,其实不是。我见过一个项目,连接池配了200个,但实际同时执行SQL的只有8个线程——剩下的都在等锁。

并发测试的关键参数:

  • 连接池大小(Connection Pool Size)
  • 活跃连接数(Active Connections)
  • 等待队列长度(Wait Queue Length)
  • 事务超时时间(Transaction Timeout)

我个人习惯用sysbench做并发测试。举个例子,模拟100个线程同时写入:

sysbench --db-driver=mysql \
         --mysql-host=127.0.0.1 \
         --mysql-user=root \
         --mysql-password=123456 \
         --mysql-db=test \
         --threads=100 \
         --time=60 \
         --events=0 \
         oltp_write_only run

跑完之后,重点看这几个指标:

  • TPS(每秒事务数)—— 越高越好
  • QPS(每秒查询数)—— 读写混合场景下要平衡
  • p99延迟 —— 99%的请求在多少毫秒内完成
  • 错误数 —— 连接超时、死锁等

我的经验:并发数从10开始,逐步增加到200、500、1000。每次增加后观察TPS曲线。如果TPS不再线性增长,说明系统已经到瓶颈了。我曾经遇到过,加到300线程时TPS反而下降——那是锁竞争太严重了。

负载模型:模拟真实用户行为

负载模型不能瞎编。你得先搞清楚:用户是读多还是写多?是均匀访问还是热点集中?

我一般把负载模型分成三类:

模型类型 读写比例 典型场景
读密集型 90%读 / 10%写 内容管理系统、博客
写密集型 20%读 / 80%写 日志系统、物联网数据采集
均衡型 50%读 / 50%写 电商订单、社交动态

举个例子,模拟一个电商系统的负载模型:

-- 模拟用户浏览商品(读)
SELECT * FROM products WHERE category_id = ? LIMIT 20;

-- 模拟用户下单(写)
INSERT INTO orders (user_id, product_id, amount) VALUES (?, ?, ?);
UPDATE products SET stock = stock - 1 WHERE id = ? AND stock > 0;

你想想看,如果只测SELECT,不测UPDATE,那上线后库存扣减的并发问题根本发现不了。我有个朋友就是吃了这个亏——上线第一天,库存被扣成负数了。

性能瓶颈分析:找到那个最慢的环节

压力测试跑起来之后,别光盯着控制台看数字。你得学会拆解。我一般按这个顺序排查:

  1. CPU —— 是不是跑满了?如果是,看是用户态还是内核态。
  2. 内存 —— 有没有大量swap?buffer pool够不够?
  3. 磁盘IO —— iowait高不高?随机读写还是顺序读写?
  4. 网络 —— 带宽有没有打满?连接数是不是太多?
  5. 数据库内部 —— 慢查询、锁等待、索引失效。

避坑指南:我曾经遇到一个案例,压力测试时CPU只有30%,但TPS就是上不去。查了半天,发现是数据库连接池配置了同步模式,每个连接都在等上一个事务提交。换成异步模式后,TPS直接翻了三倍。

这里我画了一张图,帮你理清性能分析的思路:

性能瓶颈分析流程 压力测试运行 采集 TPS / QPS / 延迟 / 错误数 检查 CPU / 内存 / 磁盘IO / 网络 / 数据库内部 找到瓶颈? 优化并复测 增加并发继续测 循环迭代,直到系统达到设计目标

优化建议:从代码到配置,一个都不能少

找到瓶颈之后,怎么优化?我按优先级排了个序:

1. SQL语句优化

这是成本最低、效果最明显的一步。我见过太多人上来就调内核参数,结果慢查询根本没动。

  • 加索引 —— 但别加太多,写操作会变慢
  • 避免SELECT * —— 只取需要的字段
  • 用EXPLAIN分析执行计划 —— 看看有没有全表扫描

2. 连接池配置

连接池不是越大越好。我有个公式:连接数 = (CPU核心数 × 2) + 有效磁盘数。当然,这只是起点,具体还要压测验证。

我的习惯:用HikariCP连接池,配置如下:

spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000

3. 缓存策略

读多写少的场景,加一层Redis缓存,能把数据库压力降一个数量级。但要注意缓存穿透和雪崩。

4. 硬件与配置

  • 用SSD替代HDD —— 随机读写性能提升10倍以上
  • 调整innodb_buffer_pool_size —— 设为物理内存的70%左右
  • 开启查询缓存(MySQL 5.7及之前版本)

5. 架构层面

如果单机扛不住,考虑读写分离、分库分表。但这是最后的手段——架构越复杂,维护成本越高。

我曾经踩过的坑:有一次优化时,我把innodb_log_file_size从128M改成了2G,结果重启后数据库启动失败。原因是redo log文件太大,系统恢复时间超长。所以,改配置一定要在测试环境验证,别直接上生产。

好了,关于数据库压力测试,核心就是这三步:设计负载模型、跑并发测试、分析瓶颈并优化。记住,没有一次压测就能搞定的系统,都是反复迭代出来的。你手头的项目,不妨今天就跑一轮试试?


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