MySQL 完全指南 / 第 20 章:性能测试
第 20 章:性能测试
不做性能测试就上线,等于裸奔上高速。本章详解 MySQL 压测方法论和工具。
20.1 压测方法论
20.1.1 压测目标
| 指标 | 说明 | 目标 |
|---|---|---|
| QPS | 每秒查询数 | 根据业务需求定 |
| TPS | 每秒事务数 | 根据业务需求定 |
| 响应时间 | P99 < 100ms | 用户体验 |
| 并发数 | 系统能承受的并发连接 | 预期峰值的 1.5-2 倍 |
| 错误率 | < 0.1% | 稳定性 |
20.1.2 压测步骤
1. 明确目标:QPS/TPS/响应时间
↓
2. 准备环境:与生产环境一致的硬件和配置
↓
3. 准备数据:与生产数据量级一致
↓
4. 预热:先跑一段时间让 Buffer Pool 预热
↓
5. 梯度压测:逐步增加并发,观察性能拐点
↓
6. 持续压测:在目标并发下持续运行 30 分钟以上
↓
7. 分析结果:找出瓶颈和优化点
⚠️ 压测环境要求:
- 硬件配置与生产一致(或等比例缩小)
- 数据量与生产一致(或等比例缩小)
- 不要在生产环境压测!
- 压测期间监控系统资源
20.2 sysbench
20.2.1 安装
# Ubuntu/Debian
sudo apt install sysbench
# CentOS/RHEL
sudo yum install sysbench
# Docker
docker pull severalnines/sysbench
20.2.2 内置测试用例
# ===================== OLTP 读写混合测试 =====================
# 准备数据(10 张表,每张 10 万行)
sysbench oltp_read_write \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=root \
--mysql-password='MyPass' \
--mysql-db=sbtest \
--tables=10 \
--table-size=100000 \
prepare
# 运行测试(32 线程,300 秒)
sysbench oltp_read_write \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=root \
--mysql-password='MyPass' \
--mysql-db=sbtest \
--tables=10 \
--table-size=100000 \
--threads=32 \
--time=300 \
--report-interval=10 \
run
# 清理测试数据
sysbench oltp_read_write \
--mysql-host=127.0.0.1 \
--mysql-user=root \
--mysql-password='MyPass' \
--mysql-db=sbtest \
--tables=10 \
cleanup
# ===================== 只读测试 =====================
sysbench oltp_read_only \
--mysql-host=127.0.0.1 \
--mysql-user=root \
--mysql-password='MyPass' \
--mysql-db=sbtest \
--threads=64 \
--time=300 \
run
# ===================== 写入测试 =====================
sysbench oltp_write_only \
--mysql-host=127.0.0.1 \
--mysql-user=root \
--mysql-password='MyPass' \
--mysql-db=sbtest \
--threads=32 \
--time=300 \
run
# ===================== 点查测试 =====================
sysbench oltp_point_select \
--mysql-host=127.0.0.1 \
--mysql-user=root \
--mysql-password='MyPass' \
--mysql-db=sbtest \
--threads=128 \
--time=300 \
run
20.2.3 自定义 Lua 脚本
-- custom_oltp.lua
pathtest = string.match("(.-)([^/\\]+%.lua)$", debug.getinfo(1, "S").source:sub(2))
function prepare()
local drv = sysbench.sql.driver()
local con = drv:connect()
con:query([[
CREATE TABLE IF NOT EXISTS orders (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
total_amount DECIMAL(12,2) NOT NULL,
status TINYINT NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_status (status)
) ENGINE=InnoDB
]])
for i = 1, sysbench.opt.table_size do
con:query(string.format(
"INSERT INTO orders (user_id, total_amount, status) VALUES (%d, %.2f, %d)",
sb_rand(1, 100000), sb_rand(10, 10000), sb_rand(0, 5)
))
end
end
function event()
local user_id = sb_rand(1, 100000)
-- 读操作(70%)
if sb_rand(1, 100) <= 70 then
db_query(string.format(
"SELECT * FROM orders WHERE user_id = %d ORDER BY created_at DESC LIMIT 10",
user_id
))
-- 写操作(30%)
else
db_query(string.format(
"INSERT INTO orders (user_id, total_amount, status) VALUES (%d, %.2f, 0)",
user_id, sb_rand(10, 1000)
))
end
end
sysbench custom_oltp \
--mysql-host=127.0.0.1 \
--mysql-user=root \
--mysql-password='MyPass' \
--mysql-db=sbtest \
--threads=32 \
--time=300 \
--table-size=100000 \
run
20.2.4 输出结果解读
SQL statistics:
queries performed:
read: 567890 -- 读查询总数
write: 123456 -- 写查询总数
other: 45678 -- 其他查询
total: 737024 -- 总查询数
transactions: 56789 (1893.02 per sec.) -- TPS
queries: 737024 (24567.33 per sec.) -- QPS
ignored errors: 0 (0.00 per sec.)
reconnects: 0 (0.00 per sec.)
General statistics:
total time: 30.0015s -- 总耗时
total number of events: 56789 -- 事件总数
Latency (ms):
min: 0.51 -- 最小延迟
avg: 1.69 -- 平均延迟
max: 45.23 -- 最大延迟
95th percentile: 3.55 -- P95 延迟
sum: 95892.34 -- 总延迟
20.3 mysqlslap
# 基本用法
mysqlslap -uroot -p \
--concurrency=50 \
--iterations=5 \
--auto-generate-sql \
--auto-generate-sql-add-autoincrement \
--auto-generate-sql-load-type=mixed \
--auto-generate-sql-write-number=1000 \
--number-of-queries=100000
# 使用自定义 SQL
mysqlslap -uroot -p \
--concurrency=100 \
--iterations=3 \
--query="SELECT * FROM myapp.users WHERE id = FLOOR(RAND() * 100000)" \
--number-of-queries=100000
# 创建测试表并压测
mysqlslap -uroot -p \
--concurrency=50 \
--iterations=3 \
--create-schema=sbtest \
--query="INSERT INTO sbtest.test (name, value) VALUES ('test', RAND())" \
--number-of-queries=50000
20.4 梯度压测脚本
#!/bin/bash
# benchmark_gradient.sh - 梯度压测脚本
HOST="127.0.0.1"
USER="root"
PASS="MyPass"
DB="sbtest"
TABLES=10
TABLE_SIZE=100000
DURATION=300
RESULT_DIR="./benchmark_results"
mkdir -p ${RESULT_DIR}
# 准备数据
sysbench oltp_read_write \
--mysql-host=${HOST} --mysql-user=${USER} --mysql-password=${PASS} \
--mysql-db=${DB} --tables=${TABLES} --table-size=${TABLE_SIZE} prepare
# 梯度压测:8, 16, 32, 64, 128, 256 线程
for THREADS in 8 16 32 64 128 256; do
echo "=========================================="
echo "Testing with ${THREADS} threads..."
echo "=========================================="
sysbench oltp_read_write \
--mysql-host=${HOST} --mysql-user=${USER} --mysql-password=${PASS} \
--mysql-db=${DB} --tables=${TABLES} --table-size=${TABLE_SIZE} \
--threads=${THREADS} --time=${DURATION} \
--report-interval=10 \
run > ${RESULT_DIR}/result_${THREADS}_threads.txt 2>&1
sleep 10 # 等待系统恢复
done
# 汇总结果
echo ""
echo "============ 结果汇总 ============"
for THREADS in 8 16 32 64 128 256; do
TPS=$(grep "transactions:" ${RESULT_DIR}/result_${THREADS}_threads.txt | awk '{print $2}' | sed 's/(//')
QPS=$(grep "queries:" ${RESULT_DIR}/result_${THREADS}_threads.txt | head -1 | awk '{print $2}' | sed 's/(//')
P95=$(grep "95th percentile" ${RESULT_DIR}/result_${THREADS}_threads.txt | awk '{print $NF}')
echo "Threads: ${THREADS} | TPS: ${TPS} | QPS: ${QPS} | P95: ${P95}ms"
done
20.5 压测监控
# 压测期间同时监控
# 系统资源
vmstat 1 # CPU、内存、IO
iostat -x 1 # 磁盘 I/O
top -p $(pidof mysqld) # MySQL 进程
# MySQL 状态
watch -n 1 'mysqladmin -uroot -p"MyPass" status extended-status | grep -E "Queries|Threads|Innodb"'
# 慢查询
tail -f /var/log/mysql/slow.log
20.6 性能对比测试
# 对比不同配置的性能
# 例如:对比 innodb_flush_log_at_trx_commit = 1 vs 2
# 配置 1(安全模式)
mysql -uroot -p -e "SET GLOBAL innodb_flush_log_at_trx_commit = 1; SET GLOBAL sync_binlog = 1;"
sysbench oltp_write_only --threads=64 --time=300 run
# 配置 2(性能模式)
mysql -uroot -p -e "SET GLOBAL innodb_flush_log_at_trx_commit = 2; SET GLOBAL sync_binlog = 0;"
sysbench oltp_write_only --threads=64 --time=300 run
业务场景
场景 1:上线前性能验证
1. 用 prod 相同配置搭建测试环境
2. 导入 prod 的数据快照
3. 梯度压测,找到性能拐点
4. 在目标并发下持续压测 30 分钟
5. 监控 CPU、内存、磁盘 I/O、MySQL 状态
6. 确认 P99 响应时间满足 SLA
7. 记录基线数据,后续对比