当前位置: 首页 > news >正文

MySQL 本机压测分析

想看看本机上MySQL的性能怎么样,做了个测试

MySQL 本机压测分析报告

1. 测试背景

本次测试在本地 MySQL 实例上进行,主要针对表 tb_sku (之前博客有表的相关信息,是一张1000w的表MySQL深分页优化及试验)的查询性能。测试目标是分析:

  • 不同并发数对查询吞吐(QPS)的影响
  • 每次查询 ID 数量对性能的影响
  • 查询字段复杂度(idid,name*)对延迟和吞吐的影响
  • 请求耗时分布情况(平均/最大/最小)

压测总请求数固定为 100 次,每个组合重复执行。


2. 测试参数

参数说明
并发数10、60、110、160、210
每次查询 ID 数10、110、210、310、410、510
查询字段idid,name*
总请求数100 次

3. 性能指标

  • 总请求数:每个组合执行请求总数
  • 总命中行数:每次查询返回的总行数(没什么用的,跑完发现只是个数学问题)
  • 总耗时(ms):该组合完成所有请求的总耗时
  • QPS:每秒处理请求数
  • 平均请求耗时(ms):每次请求的平均耗时
  • 最大请求耗时(ms):单次请求耗时最高值
  • 最小请求耗时(ms):单次请求耗时最低值

4. 测试结果概览

4.1 并发 10

每次查询 ID 数字段总耗时(ms)QPS平均(ms)最大(ms)最小(ms)
10id35.012856.433.4421.900.51
10id+name71.431399.926.9724.261.78
10*29.423399.522.844.231.06
110id71.471399.196.9012.133.83
110id+name263.25379.8626.0730.1221.26
110*272.07367.5526.8430.8823.41

数据很多就不全放上来了

观察:

  • 查询字段越复杂,平均请求耗时增加明显,QPS 降低
  • 小 ID 数量查询时耗时最小,QPS 最大

4.2 并发 60

每次查询 ID 数字段总耗时(ms)QPS平均(ms)最大(ms)最小(ms)
10id28.513507.3310.9926.101.01
10id+name20.444891.638.7316.952.24
10*22.674410.388.4619.962.26
110id45.512197.5422.9344.907.40

观察:

  • 并发增加到 60,QPS 提升显著
  • 大 ID 查询组合(如 210、310、410、510)耗时随字段复杂度大幅增加
  • 平均请求耗时更能反映查询效率,极值差异较大

4.3 并发 110、160、210

  • 高并发下,QPS 达到 5000+,平均请求耗时维持在 6–30ms 级别
  • 查询字段复杂度对性能影响明显:* 查询最大请求耗时最高
  • 大 ID 数量查询组合在高并发下容易出现最大耗时峰值(>200ms)

一些合乎理论的情况


4.4 分析图

在这里插入图片描述
在这里插入图片描述

命中率是一个无用的指标,忽略掉

5. 性能分析结论

  1. 查询字段复杂度影响大
    • 简单字段 id:吞吐最高、平均耗时最低
    • 完整字段 *:吞吐最低、耗时最高
  2. 并发提升有效
    • QPS 随并发线性增加,但到一定程度(>160)后,最大请求耗时增长明显,说明存在 IO 或锁竞争
  3. 查询 ID 数量影响
    • 小 ID 数量查询更快,QPS 更高
    • 大量 ID 查询时,平均请求耗时与最大耗时增幅明显
  4. 请求耗时分布
    • 平均耗时较稳定
    • 最大耗时波动大,说明部分请求受复杂查询或数据库缓存策略影响

6. 总结与实践建议

通过本次 MySQL 压测试验,可以得到以下结论:

  1. 查询字段复杂度影响显著
    • 试验中,查询简单字段(如 id)的 QPS 明显高于查询完整字段(*)。
    • 理论上,查询字段越多,数据库需要读取更多行数据和列数据,IO 压力增加,同时返回结果集越大,网络传输耗时也增加。
    • 实践建议:仅查询必要字段,避免 SELECT *,尤其是在高并发场景下。
  2. 并发提升与吞吐关系
    • 随着并发数增加,QPS 线性提升,但最大请求耗时增幅明显。
    • 理论上,MySQL 的连接和查询线程有限,过高并发会引发线程竞争和锁等待,导致部分请求耗时增加。
    • 实践建议:根据硬件和实例配置合理设置并发上限,避免单机 MySQL 出现请求堆积。对于业务量突增场景,可考虑读写分离或数据库水平拆分。
  3. 查询 ID 数量与延迟关系
    • 小批量查询(ID 数少)平均请求耗时低,QPS 高;大批量查询(ID 数多)耗时和最大请求耗时明显增加。(这次都是in语法,没有比较join或临时表的方式,所以下面的理论和本次实践没什么关系,不过写出来下次再试验比较一下)
    • 现象就是IN中字段超过200个,速度就慢了不少。
  • 理论上,大批量查询生成较长的 IN 条件,会增加解析和执行成本,同时导致返回结果集大,消耗更多内存和网络带宽。 - 实践建议:大批量查询应拆分为多次小批量请求或使用临时表/JOIN 方式,避免单次请求过重。

7 附录:代码

7.1 Golang

package bigtableimport ("fmt""os""sync""testing""time"
)// 生成压测组合参数(连续、细粒度)
func generateTestCombos() (concurrencyList, idsPerQueryList []int, fieldsList []string) {// 并发数:10,20,30,...,200for c := 10; c <= 210; c += 50 {concurrencyList = append(concurrencyList, c)}// 每次查询ID数:10,20,30,...,500for n := 10; n <= 510; n += 100 {idsPerQueryList = append(idsPerQueryList, n)}// 查询字段保持原来的组合fieldsList = []string{"id", "id,name", "*"}return
}// 写 CSV 头
func writeCSVHeader(file *os.File) {file.WriteString("并发数,每次查询ID数,查询字段,总请求数,总命中行数,命中率(%),总耗时(ms),QPS,平均请求耗时(ms),最大请求耗时(ms),最小请求耗时(ms)\n")
}// 执行压测并输出 CSV
func TestSkuQueryQPSDetailed(t *testing.T) {db := openDB()defer db.Close()// 总请求数totalRequests := 100maxID := 10000000// 获取组合参数concurrencyList, idsPerQueryList, fieldsList := generateTestCombos()// 创建 CSV 文件file, err := os.Create("sku_qps_detailed.csv")if err != nil {t.Fatal(err)}defer file.Close()writeCSVHeader(file)for _, concurrency := range concurrencyList {for _, idsPerQuery := range idsPerQueryList {for _, fields := range fieldsList {var wg sync.WaitGroupvar mu sync.Mutexvar totalCount intrequestTimes := make([]float64, 0, totalRequests)start := time.Now()sem := make(chan struct{}, concurrency)for i := 0; i < totalRequests; i++ {wg.Add(1)sem <- struct{}{}go func() {defer wg.Done()defer func() { <-sem }()reqStart := time.Now()ids := randomIDs(idsPerQuery, maxID)inParams := ""for i, id := range ids {if i > 0 {inParams += ","}inParams += fmt.Sprintf("%d", id)}query := fmt.Sprintf("SELECT %s FROM tb_sku WHERE id IN (%s)", fields, inParams)rows, err := db.Query(query)if err != nil {t.Error(err)return}defer rows.Close()var count intfor rows.Next() {count++}reqDuration := time.Since(reqStart).Seconds() * 1000 // msmu.Lock()totalCount += countrequestTimes = append(requestTimes, reqDuration)mu.Unlock()}()}wg.Wait()duration := time.Since(start)qps := float64(totalRequests) / duration.Seconds()hitRate := float64(totalCount) * 100 / float64(totalRequests*idsPerQuery)// 单次请求耗时统计var minReq, maxReq, sumReq float64minReq = 1e9for _, r := range requestTimes {sumReq += rif r > maxReq {maxReq = r}if r < minReq {minReq = r}}avgReq := sumReq / float64(len(requestTimes))// 写 CSVif fields == "id,name" {fields = "id+name"}file.WriteString(fmt.Sprintf("%d,%d,%s,%d,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f\n",concurrency, idsPerQuery, fields,totalRequests, totalCount, hitRate,duration.Seconds()*1000, qps, avgReq, maxReq, minReq,))// 控制台日志t.Logf("并发: %d, 每次查询ID数: %d, 字段: %s | 总请求数: %d, 总命中行数: %d, 命中率: %.2f%%, 总耗时: %.2fms, QPS: %.2f, 平均: %.2fms, 最大: %.2fms, 最小: %.2fms",concurrency, idsPerQuery, fields, totalRequests, totalCount, hitRate, duration.Seconds()*1000, qps, avgReq, maxReq, minReq,)}}}
}

7.2 Python

先安装依赖

pip install matplotlib pandas seaborn
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import numpy as np
import os# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False# 从CSV文件读取数据
def load_data_from_csv(filename='sku_qps_detail.csv'):
# def load_data_from_csv(filename='sku_qps_result.csv'):"""从CSV文件加载性能测试数据"""if not os.path.exists(filename):raise FileNotFoundError(f"CSV文件 {filename} 不存在")df = pd.read_csv(filename)print(f"成功加载数据,共 {len(df)} 行记录")print(f"数据列: {list(df.columns)}")print("\n数据预览:")print(df.head())return df# 创建多子图分析
def create_performance_analysis(df):"""基于数据框创建性能分析图表"""fig, axes = plt.subplots(2, 3, figsize=(18, 12))fig.suptitle('MySQL查询性能分析报告 (基于CSV数据)', fontsize=16, fontweight='bold')# 1. QPS vs 并发数和查询ID数 (3D曲面图)try:ax1 = fig.add_subplot(231, projection='3d')# 按查询字段分组,选择id字段的数据df_id = df[df['查询字段'] == 'id']x = df_id['并发数']y = df_id['每次查询ID数']z = df_id['QPS']ax1.plot_trisurf(x, y, z, cmap='viridis', alpha=0.8)ax1.set_xlabel('并发数')ax1.set_ylabel('每次查询ID数')ax1.set_zlabel('QPS')ax1.set_title('QPS分布(3D) - 仅查询id字段')except Exception as e:print(f"3D图表创建失败: {e}")# 2. QPS热力图 - 按查询字段分别显示ax2 = axes[0, 1]# 选择特定查询字段的数据pivot_qps = df.pivot_table(values='QPS', index='并发数', columns='每次查询ID数', aggfunc='mean')sns.heatmap(pivot_qps, annot=True, fmt='.0f', cmap='YlOrRd', ax=ax2)ax2.set_title('QPS热力图 (所有查询字段平均)')ax2.set_xlabel('每次查询ID数')ax2.set_ylabel('并发数')# 3. 查询字段性能对比ax3 = axes[0, 2]field_performance = df.groupby('查询字段')['QPS'].mean().sort_values(ascending=False)bars = ax3.bar(range(len(field_performance)), field_performance.values, color=['skyblue', 'lightgreen', 'lightcoral'])ax3.set_xlabel('查询字段')ax3.set_ylabel('平均QPS')ax3.set_title('不同查询字段性能对比')ax3.set_xticks(range(len(field_performance)))ax3.set_xticklabels(field_performance.index, rotation=45)# 在柱状图上显示数值for i, bar in enumerate(bars):height = bar.get_height()ax3.text(bar.get_x() + bar.get_width()/2., height,f'{height:.0f}', ha='center', va='bottom')# 4. QPS折线图 - 按查询字段分别显示ax4 = axes[1, 0]for field in df['查询字段'].unique():for concurrency in df['并发数'].unique():subset = df[(df['查询字段'] == field) & (df['并发数'] == concurrency)]if not subset.empty:ax4.plot(subset['每次查询ID数'], subset['QPS'], marker='o', linewidth=2, label=f'{field}-并发{concurrency}')ax4.set_xlabel('每次查询ID数')ax4.set_ylabel('QPS')ax4.set_title('QPS vs 查询ID数 (按字段和并发)')ax4.legend(bbox_to_anchor=(1.05, 1), loc='upper left')ax4.grid(True, alpha=0.3)# 5. 响应时间分析ax5 = axes[1, 1]time_metrics = ['平均请求耗时(ms)', '最大请求耗时(ms)', '最小请求耗时(ms)']time_data = df.groupby('查询字段')[time_metrics].mean()x = np.arange(len(time_data))width = 0.25for i, metric in enumerate(time_metrics):ax5.bar(x + i*width, time_data[metric], width, label=metric)ax5.set_xlabel('查询字段')ax5.set_ylabel('耗时(ms)')ax5.set_title('响应时间分析')ax5.set_xticks(x + width)ax5.set_xticklabels(time_data.index)ax5.legend()# 6. 命中率分析ax6 = axes[1, 2]hit_rate_pivot = df.pivot_table(values='命中率(%)', index='并发数', columns='每次查询ID数', aggfunc='mean')sns.heatmap(hit_rate_pivot, annot=True, fmt='.1f', cmap='Blues', ax=ax6)ax6.set_title('命中率分析(%)')ax6.set_xlabel('每次查询ID数')ax6.set_ylabel('并发数')# 调整布局plt.tight_layout()return fig# 生成详细分析图表
def create_detailed_analysis(df):"""创建详细的分析图表"""fig, axes = plt.subplots(2, 2, figsize=(15, 12))fig.suptitle('MySQL查询性能详细分析', fontsize=16, fontweight='bold')# 子图1: 查询字段性能对比(详细)ax1 = axes[0, 0]field_concurrency_perf = df.pivot_table(values='QPS', index='查询字段', columns='并发数', aggfunc='mean')field_concurrency_perf.plot(kind='bar', ax=ax1)ax1.set_ylabel('QPS')ax1.set_title('不同查询字段在不同并发下的QPS')ax1.legend(title='并发数', bbox_to_anchor=(1.05, 1), loc='upper left')ax1.grid(True, alpha=0.3)# 子图2: 响应时间分布ax2 = axes[0, 1]time_columns = ['平均请求耗时(ms)', '最大请求耗时(ms)', '最小请求耗时(ms)']time_stats = df.groupby('查询字段')[time_columns].mean()time_stats.plot(kind='bar', ax=ax2)ax2.set_ylabel('耗时(ms)')ax2.set_title('响应时间统计')ax2.legend(bbox_to_anchor=(1.05, 1), loc='upper left')ax2.grid(True, alpha=0.3)# 子图3: 命中率分析ax3 = axes[1, 0]hit_rate_by_field = df.groupby(['查询字段', '每次查询ID数'])['命中率(%)'].mean().unstack()hit_rate_by_field.plot(kind='bar', ax=ax3)ax3.set_ylabel('命中率(%)')ax3.set_title('不同查询字段和ID数的命中率')ax3.legend(title='每次查询ID数', bbox_to_anchor=(1.05, 1), loc='upper left')ax3.grid(True, alpha=0.3)# 子图4: 性能优化建议ax4 = axes[1, 1]ax4.axis('off')# 计算最佳配置best_qps = df.loc[df['QPS'].idxmax()]best_response = df.loc[df['平均请求耗时(ms)'].idxmin()]best_hit_rate = df.loc[df['命中率(%)'].idxmax()]recommendations = [f"🔥 最高QPS配置:",f"   字段: {best_qps['查询字段']}",f"   并发: {best_qps['并发数']}, ID数: {best_qps['每次查询ID数']}",f"   QPS: {best_qps['QPS']:.0f}","",f"⚡ 最快响应配置:",f"   字段: {best_response['查询字段']}",f"   平均耗时: {best_response['平均请求耗时(ms)']:.2f}ms","",f"🎯 最高命中率:",f"   命中率: {best_hit_rate['命中率(%)']:.1f}%",f"   字段: {best_hit_rate['查询字段']}","",f"📊 数据统计:",f"   总测试数: {len(df)}",f"   平均QPS: {df['QPS'].mean():.0f}",f"   平均命中率: {df['命中率(%)'].mean():.1f}%"]for i, rec in enumerate(recommendations):ax4.text(0.05, 0.95 - i*0.05, rec, fontsize=10, transform=ax4.transAxes, verticalalignment='top')plt.tight_layout()return fig# 生成性能洞察报告
def generate_insights(df):"""生成性能洞察报告"""print("=" * 60)print("MySQL查询性能测试关键洞察:")print("=" * 60)# 基本统计print(f"测试配置总数: {len(df)}")print(f"查询字段类型: {df['查询字段'].unique().tolist()}")print(f"并发数范围: {df['并发数'].min()} - {df['并发数'].max()}")print(f"每次查询ID数范围: {df['每次查询ID数'].min()} - {df['每次查询ID数'].max()}")# 按查询字段分析print("\n1. 按查询字段性能分析:")field_stats = df.groupby('查询字段').agg({'QPS': ['mean', 'max', 'min'],'命中率(%)': 'mean','平均请求耗时(ms)': 'mean'}).round(2)print(field_stats)# 找出最佳配置best_qps = df.loc[df['QPS'].idxmax()]best_response = df.loc[df['平均请求耗时(ms)'].idxmin()]best_hit_rate = df.loc[df['命中率(%)'].idxmax()]print(f"\n2. 最佳性能配置:")print(f"   🚀 最高QPS: {best_qps['QPS']:.0f}")print(f"      配置: {best_qps['查询字段']}, 并发{best_qps['并发数']}, {best_qps['每次查询ID数']}个ID")print(f"   ⚡ 最快响应: {best_response['平均请求耗时(ms)']:.2f}ms")print(f"      配置: {best_response['查询字段']}, 并发{best_response['并发数']}, {best_response['每次查询ID数']}个ID")print(f"   🎯 最高命中率: {best_hit_rate['命中率(%)']:.1f}%")print(f"      配置: {best_hit_rate['查询字段']}, 并发{best_hit_rate['并发数']}, {best_hit_rate['每次查询ID数']}个ID")# 性能趋势分析print(f"\n3. 性能趋势:")for field in df['查询字段'].unique():field_data = df[df['查询字段'] == field]qps_vs_ids = field_data['每次查询ID数'].corr(field_data['QPS'])if qps_vs_ids < -0.3:trend = "下降"elif qps_vs_ids > 0.3:trend = "上升"else:trend = "稳定"print(f"   {field}: ID数增加 → QPS {trend} (相关性: {qps_vs_ids:.2f})")# 优化建议print(f"\n4. 优化建议:")if best_qps['查询字段'] == 'id':print("   ✅ 仅查询id字段可获得最高QPS")else:print("   ✅ 多字段查询在特定场景下表现良好")if df['QPS'].max() > 3000:print("   🎉 性能优秀: 达到3000+ QPS")elif df['QPS'].max() > 1000:print("   👍 性能良好")else:print("   ⚠️  性能有待优化")# 主函数
def main():try:# 从CSV文件加载数据df = load_data_from_csv('sku_qps_detailed.csv')# 生成性能洞察generate_insights(df)# 创建主分析图表fig1 = create_performance_analysis(df)plt.savefig('mysql_performance_analysis_new.png', dpi=300, bbox_inches='tight')print("\n✅ 主分析图表已保存: mysql_performance_analysis_new.png")# 创建详细分析图表fig2 = create_detailed_analysis(df)plt.savefig('mysql_detailed_analysis_new.png', dpi=300, bbox_inches='tight')print("✅ 详细分析图表已保存: mysql_detailed_analysis_new.png")# 显示图表plt.show()except FileNotFoundError as e:print(f"❌ 错误: {e}")print("请确保 sku_qps_detailed.csv 文件在当前目录下")except Exception as e:print(f"❌ 处理数据时出错: {e}")import tracebacktraceback.print_exc()if __name__ == "__main__":main()
http://www.dtcms.com/a/441813.html

相关文章:

  • 华清远见25072班C++学习假期10.4作业
  • 建网站学什么软件全国医院的网站建设
  • 【深度学习计算机视觉】09:语义分割和数据集——应用场景与前沿探索
  • 【LeetCode热题100】No.1——两数之和(Java)
  • 系分论文《论边缘计算在工业质检系统中的分析与设计》
  • 利用 ArcMap 的 MXD 布局视图以及ArcPy 脚本实现批量自动生成油井点之记并导出 PDF(实操+亲测)
  • 计算机工作原理(简单介绍)
  • 自己如何建设网站聊天室做医药代表去什么招聘网站
  • 指针和数组解析
  • 【AI4S】3DSMILES-GPT:基于词元化语言模型的3D分子生成
  • Transformer推理优化全景:从模型架构到硬件底层的深度解析
  • MySQL 索引全解析:结构、优化与索引下推实战指南​
  • clear configuration interface概念及题目
  • 设计模式(C++)详解——策略模式(1)
  • 基于html5设计的网站建设做一些购物网站
  • Vivado综合通关指南:从IP打包失败到工具崩溃的四重考验
  • 语义分割概述
  • 数据结构之排序算法
  • 绍兴网站建设优化手机企业网站建设开发
  • 偏导数解释
  • Linux内核与设备管理:USB存储驱动usb_storage/uas的安全卸载与复原
  • fallocate: fallocate failed: Text file busy
  • visio实现扇形图绘制的方式方法-以三等分扇形为例
  • 以太坊私有链搭建与智能合约部署指南
  • 网站开发 教学大纲网页设计图片与图片的位置
  • python+flask_socketio+pyautogui实现简易远程桌面功能
  • flask_socketio+pyautogui实现的具有加密传输功能的极简远程桌面
  • 深入了解linux网络—— TCP网络通信(上)
  • Android Jetpack 核心组件实战:ViewModel + LiveData + DataBinding 详解
  • 商务厅网站建设意见怎么做网站注册推广