LHIDC

服务器性能测试中识别MySQL慢查询的核心指标

本文面向IT运维工程师,提供在服务器压测中定位MySQL慢查询根因的实操路径:先固定测试条件和QPS口径,再采集慢日志与连接层数据联合对比,判断是SQL索引问题还是连接池排队,并用复测确认优化方向。

服务器性能测试中识别MySQL慢查询的核心指标

先判断“慢的是执行”还是“慢的是等待”,再谈索引或连接池

压测里常见这种现象:QPS下滑了,慢日志告警在增,但 CPU 并没打满。很多人第一反应会把 SQL 语句拿去补索引,结果跑完两三轮才发现波动仍在,甚至更明显。 更准确的说法是:在服务器性能测试中,MySQL慢查询并不自动等于索引问题,尤其当线程端口被占满、应用层连接池在排队时,慢日志看到的只是“慢着执行完的尾巴”。

要让结论成立,先固定三个条件:测试阶段不迁移数据、不改主机配置(CPU、磁盘、内存比例不变)、压测脚本和并发固定。只要口径不变,才有意义区分“慢查询慢”和“慢连接慢”。

指标定义:先把读法统一,再做判断

先抓的核心字段(慢日志)

MySQL慢查询 复盘里,最先看的不是“慢到几秒”,而是与锁与扫描量的关系:

指标 来源 为什么重要
Query_time 慢日志 语句执行耗时主入口,决定是否是执行路径问题
Lock_time 慢日志 行/表级等待时间,偏大常提示锁争用
Rows_examined 慢日志 实际扫描行数,决定索引是否命中
Rows_sent 慢日志 返回行数,和 Rows_examined 一起看扫描效率
Rows_affected 慢日志 写入类语句的写放大参考
rows_ratio = Rows_examined / Rows_sent 计算字段 判断是否存在“大量无效扫描”
慢日志总数/时间窗口 慢日志 观察是否是“少量重问题”还是“广泛退化”

rows_ratio 很关键。Rows_examinedRows_sent 相差很大,通常说明同样结果用掉了过多扫描。即使 Query_time 不算特别离谱,也可能是索引缺失或条件不当造成。

同步看服务器侧连接状态(决定是否是连接池问题)

连接层指标可用来判断是否存在排队与抢占:

指标 来源(性能测试常用) 关键观察
Questions performance_schema.global_status 计算 QPS 的原始计数
Threads_connected performance_schema.global_status 已建立连接数
Threads_running performance_schema.global_status 当前运行线程数
Max_used_connections performance_schema.global_status 历史峰值,是否长期触顶
Aborted_connects performance_schema.global_status 连接异常数,池配置与认证波动参考
Innodb_row_lock_waits / wait_time performance_schema.global_statusSHOW ENGINE INNODB STATUS 与锁相关瓶颈和等待时延
连接池 pending/queue 应用监控(如 HikariCP) 判断是否拿不到连接

Threads_running 长期接近上限、且 pending 长时间>0 时,慢查询多半不是“单条SQL失控”,而是“连接与线程排队”。

QPS 口径统一

QPS 不建议直接看“某一秒”瞬时值,建议按窗口算:

QPS = (Questions_t2 - Questions_t1) / (t2 - t1)

保持这个口径能把慢日志窗口和负载窗口对齐,避免误判。

测试环境与工具:先把实验条件固定下来

以下示例按 Linux + MySQL 8.0+ 给出,低版本或 MariaDB 可有字段差异,请先核对版本和变量名。

mysql -uroot -p -Nre "SELECT VERSION();"
mysql -uroot -p -e "SHOW VARIABLES LIKE 'slow_query_log'; SHOW VARIABLES LIKE 'log_output'; SHOW VARIABLES LIKE 'long_query_time';"
# 压测窗口建议用 FILE 或 TABLE 都可,但建议 TABLE 便于按 SQL_text 聚合
SET GLOBAL slow_query_log = ON;
SET GLOBAL log_output = 'TABLE';
SET GLOBAL long_query_time = 0.5;              -- 按业务目标可调整
SET GLOBAL min_examined_row_limit = 100;        -- 过滤明显小查询,减少噪声
SET GLOBAL log_queries_not_using_indexes = ON;   -- 诊断窗口短期开启
-- 采集基础状态基线
SELECT VARIABLE_NAME, VARIABLE_VALUE
FROM performance_schema.global_status
WHERE VARIABLE_NAME IN ('Questions','Threads_running','Threads_connected','Max_used_connections','Aborted_connects')
ORDER BY VARIABLE_NAME;

压力工具用 sysbenchmysqlslap、内部压测脚本都可以,关键是可重复。示例用 sysbench

sysbench --db-driver=mysql --mysql-user=sbtest --mysql-password='***' --mysql-db=sbtest \
  --tables=8 --table-size=100000 --threads=64 --time=300 --report-interval=5 run

压测前后分别保留慢日志、状态和 QPS 快照,不能只看一次点位。

# 每 10 秒记录一次状态(实际建议用监控系统持久化)
while true; do
  mysql -uroot -p -N -e "
    SELECT NOW(),
           (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Questions') AS Questions,
           (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Threads_running') AS Threads_running,
           (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Threads_connected') AS Threads_connected;"
  sleep 10
done

执行流程:可复现、可回放的一轮分析

  1. 先跑 1~2 分钟基线,确认无外部批任务/备份/监控同步任务干扰。
  2. 再开启压测并固定脚本参数,观察至少 5 分钟以上,让缓存、连接池达到稳定。
  3. 结束后先不改参数,先做分析:
-- 慢查询统计(表输出模式)
SELECT
  COUNT(*) AS sample_cnt,
  ROUND(AVG(query_time),4) AS avg_query_time_s,
  ROUND(AVG(lock_time),4) AS avg_lock_time_s,
  ROUND(SUM(rows_examined)/NULLIF(SUM(rows_sent),0),2) AS examined_ratio,
  LEFT(sql_text,180) AS sql_sample
FROM mysql.slow_log
WHERE start_time >= NOW() - INTERVAL 10 MINUTE
GROUP BY sql_sample
ORDER BY avg_query_time_s DESC
LIMIT 20;
-- 性能快照与执行摘要对齐(更稳定)
SELECT
  DIGEST_TEXT,
  COUNT_STAR AS exec_cnt,
  ROUND(SUM_TIMER_WAIT/1e9/NULLIF(COUNT_STAR,0),2) AS avg_exec_ms,
  ROUND(SUM_LOCK_TIME/1e9/NULLIF(COUNT_STAR,0),2) AS avg_lock_ms,
  ROUND(SUM_ROWS_EXAMINED/NULLIF(COUNT_STAR,0),2) AS avg_rows_examined,
  ROUND(SUM_ROWS_SENT/NULLIF(COUNT_STAR,0),2) AS avg_rows_sent
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME='sbtest'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;
  1. 对同一时段对齐采样,补充连接池监控(应用端)确认 pending/active 与数据库 Threads_running 的趋势是否同步。
  2. 只变更一个方向:这轮只改索引或只改连接池,不要两者同时动。

异常结果解释:如何选择优先动作

结果指向索引优化

满足以下组合时,优先做 SQL 与索引核查:

  • examin_ratio 持续显著高,且大头 SQL 一样;
  • Query_time 提高,Lock_time 不一定高;
  • Threads_running 没有长期高位贴近上限;
  • 同步看 performance_schema,问题集中在少数几个 digest 上。

这时动作顺序是:

  1. EXPLAIN / EXPLAIN ANALYZE 查执行计划;
  2. 先确认过滤列、排序列、连接列是否被联合索引覆盖;
  3. 在测试环境先加索引或重写谓词;
  4. 重跑同一窗口,观察 examined_ratioavg_query_time 和 QPS 是否同步改善。

结果指向连接池优先

满足以下组合时,优先看连接池:

  • QPS 波动但 Thread_runningThreads_connected 同步高位;
  • 应用侧出现连接等待(pending/queue);
  • 慢日志 lock_time 不高、Rows_examined 也没显著扩大;
  • 同一 SQL 在慢日志里的频率不一定变化,但响应抖动明显。

这时动作顺序是:

  1. 先核对连接池 maxPoolSize、空闲回收、连接泄漏与超时;
  2. 校准数据库 max_connections 与连接池上限比例(避免数据库侧连接被拖死);
  3. 检查健康检查与保活查询是否占用执行线程;
  4. 再重测,确认 Threads_running 峰值回落且 pending 消失。

两者都在退化时

query_time 也高、lock_time 也高、Threads_running 也高,说明“SQL与连接层”可能同时受压。此时不能用一句话归因,必须先改单一变量做 A/B,否则看起来“全部变好”只是抵消误差。

影响因素:哪些情况会让判断偏差

  • long_query_time 设得过高会漏掉 100~300ms 区间的关键语句;建议在测试时先降到可见阈值,再恢复。
  • 缓存未热:全量冷读会把 Rows_examined 推高,第一次测试误判更大。
  • 短连接风暴:频繁建连会放大 Threads_connected,但根因可能在应用超时/重连逻辑。
  • 锁等待:Lock_timeInnodb_row_lock_waits 上升时,索引再好也可能拖不动。
  • 读写混合、复杂聚合、排序分页类语句天生会有较高 Rows_examined,先按语句类型分桶,不要只盯全局平均。

结果解释边界与复测方式

复测时至少做两个“可比轮次”,参数完全一致:同样数据量、同样并发、同样持续时间。 对比指标应包含三类:QPS、慢查询核心字段(尤其 query_timerows_ratio)、连接层状态(Threads_running 与应用 pending)。只有这三类都向“更低开销/更稳定”方向时,才可将问题定义为已被修复。 如果只改善了其中一类,或者改动后波动更大,结论就不成立,直接回到对应路径做下一轮单变量排查。

上一篇 美国服务器的线路实测报告该怎么看:别只盯延迟数字 下一篇 服务器日志时间戳偏移对故障定位准确性的影响

LHIDC 产品中心

继续查看可购买的海外服务器产品

文章用于辅助选型,最终价格、库存与配置请以产品详情页和下单页面展示为准。

查看产品 查看方案