MySQL慢查询监控与告警实战:从slow_log采集到分钟级定位慢SQL的完整链路配置

一、收银变慢,监控却说一切正常

月初收到门店反馈:下午高峰期收银结账变慢,偶尔要等5-6秒才出结果。

值班同事第一反应看服务器监控------CPU 35%、内存占用60%、磁盘IO正常、网络延迟2ms。全绿。

接着查应用日志,没有报错。查Nginx access log,发现 /api/checkout 接口P99响应时间从正常的200ms飙到了6200ms,但只在14:00-16:00出现。

最后定位到原因:有一条统计查询没加索引,随着订单表数据增长到300万行,全表扫描耗时从1秒涨到8秒,恰好在高峰期被频繁触发。

问题本身不复杂------加个索引就解决了。但暴露的链路断点很致命:

slow_log其实已经记录了这条SQL整整两周,但没有任何机制把它变成一个告警或一个待处理事项。

这不是个例。大部分多门店系统的慢查询管理现状:

环节 现状 问题
slow_log 开了 日志文件越来越大,没人看
分析 偶尔手动看 没有定期解析和聚合
告警 慢查询不在告警体系内
定位 靠人肉grep 不知道哪条SQL影响最大
优化 等出问题再查 被动,无法提前发现退化
工单联动 发现了慢SQL也没有派单和跟进闭环

理想状态是:慢查询一旦触发告警,自动生成一张带SQL指纹和所属数据库的工单,DBA直接从工单拿上下文开始优化,优化完关单,月底统计哪个库的慢查询工单最多------这才是"监控→告警→处置→闭环"的完整链路。

下面给出一套从采集到告警的完整链路。


二、慢查询监控的完整链路

整条链路分6步:

复制代码
MySQL slow_log → pt-query-digest 定期解析 → 结果入库/文件 → Zabbix 自定义采集 → 告警规则判断 → 通知 + Grafana可视化

每一步的核心任务:

步骤 工具 核心产出
1. 采集 MySQL原生slow_log 原始慢查询记录
2. 解析 pt-query-digest SQL指纹聚合 + 统计摘要
3. 存储 JSON文件 / MySQL表 可查询的结构化数据
4. 监控 Zabbix UserParameter 指标采集(数量/最大耗时/Top SQL)
5. 告警 Zabbix Trigger 分级告警(P1/P2/P3)
6. 展示 Grafana 趋势 + Top SQL + 分布分析

三、Step 1-2:slow_log配置 + pt-query-digest定期解析

3.1 MySQL slow_log 配置
sql 复制代码
-- 查看当前慢查询配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

-- 开启慢查询日志(动态生效,无需重启)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;          -- 超过1秒记录
SET GLOBAL log_queries_not_using_indexes = ON;  -- 未使用索引的也记录
SET GLOBAL min_examined_row_limit = 1000; -- 扫描行数>1000才记录(过滤小表全扫)

-- 持久化配置(写入 my.cnf)
-- [mysqld]
-- slow_query_log = 1
-- slow_query_log_file = /var/log/mysql/slow.log
-- long_query_time = 1
-- log_queries_not_using_indexes = 1
-- min_examined_row_limit = 1000

关键参数选择建议

参数 建议值 理由
long_query_time 1秒(业务系统) / 0.5秒(核心交易) 太短日志量爆炸,太长漏掉退化趋势
log_queries_not_using_indexes ON 提前发现缺索引SQL
min_examined_row_limit 1000 避免小表全扫干扰
3.2 pt-query-digest 定期解析

安装 Percona Toolkit:

bash 复制代码
# CentOS/RHEL
yum install -y percona-toolkit

# Ubuntu/Debian
apt-get install -y percona-toolkit

# 验证安装
pt-query-digest --version

定时解析脚本 /opt/scripts/slow_query_analyze.sh

bash 复制代码
#!/bin/bash
# 慢查询定期解析脚本
# 每10分钟执行一次,解析增量日志并输出JSON格式结果

SLOW_LOG="/var/log/mysql/slow.log"
OUTPUT_DIR="/opt/slow_query_reports"
TIMESTAMP=$(date +%Y%m%d_%H%M)
LAST_POS_FILE="/tmp/slow_log_last_pos"

mkdir -p "$OUTPUT_DIR"

# 获取上次读取位置(增量解析)
if [ -f "$LAST_POS_FILE" ]; then
    LAST_POS=$(cat "$LAST_POS_FILE")
else
    LAST_POS=0
fi

# 当前文件大小
CURRENT_SIZE=$(stat -c%s "$SLOW_LOG" 2>/dev/null || stat -f%z "$SLOW_LOG")

# 如果日志被轮转(当前大小小于上次位置),重置
if [ "$CURRENT_SIZE" -lt "$LAST_POS" ]; then
    LAST_POS=0
fi

# 增量读取并解析
tail -c +$((LAST_POS + 1)) "$SLOW_LOG" | pt-query-digest \
    --output json \
    --limit 20 \
    --order-by Query_time:sum \
    > "$OUTPUT_DIR/report_${TIMESTAMP}.json" 2>/dev/null

# 同时输出简要摘要供Zabbix采集
tail -c +$((LAST_POS + 1)) "$SLOW_LOG" | pt-query-digest \
    --output json \
    --limit 5 \
    > "$OUTPUT_DIR/latest_summary.json" 2>/dev/null

# 记录当前位置
echo "$CURRENT_SIZE" > "$LAST_POS_FILE"

# 提取关键指标供监控采集
TOTAL_QUERIES=$(cat "$OUTPUT_DIR/latest_summary.json" | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    print(len(data.get('classes', [])))
except:
    print(0)
")

MAX_TIME=$(cat "$OUTPUT_DIR/latest_summary.json" | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    classes = data.get('classes', [])
    if classes:
        print(max(c.get('metrics', {}).get('Query_time', {}).get('max', 0) for c in classes))
    else:
        print(0)
except:
    print(0)
")

# 写入Zabbix可读的指标文件
echo "$TOTAL_QUERIES" > /tmp/slow_query_count
echo "$MAX_TIME" > /tmp/slow_query_max_time

配置 crontab:

bash 复制代码
# 每10分钟执行一次慢查询解析
*/10 * * * * /opt/scripts/slow_query_analyze.sh >> /var/log/slow_query_analyze.log 2>&1

四、Step 3-5:Zabbix自定义采集 + 分级告警

4.1 Zabbix UserParameter 配置

在被监控主机的 Zabbix Agent 配置中添加自定义监控项:

ini 复制代码
# /etc/zabbix/zabbix_agentd.d/mysql_slow_query.conf

# 慢查询数量(10分钟内新增的不同SQL指纹数)
UserParameter=mysql.slow_query.count,cat /tmp/slow_query_count 2>/dev/null || echo 0

# 最大查询耗时(秒)
UserParameter=mysql.slow_query.max_time,cat /tmp/slow_query_max_time 2>/dev/null || echo 0

# 当前slow_log文件大小(MB)
UserParameter=mysql.slow_query.log_size,echo "scale=2; $(stat -c%s /var/log/mysql/slow.log 2>/dev/null || echo 0) / 1048576" | bc

# 累计慢查询总数(从MySQL状态变量获取)
UserParameter=mysql.slow_query.total,mysqladmin -u zabbix_monitor -p'MonitorPass123' extended-status 2>/dev/null | grep -w Slow_queries | awk '{print $4}'

重启 Zabbix Agent:

bash 复制代码
systemctl restart zabbix-agent
# 验证
zabbix_agentd -t mysql.slow_query.count
zabbix_agentd -t mysql.slow_query.max_time
4.2 Zabbix 告警规则(Trigger)
yaml 复制代码
# 告警分级设计
triggers:
  # P1 - 严重:存在超过10秒的慢查询(可能阻塞业务)
  - name: "MySQL慢查询严重:存在超10秒SQL [P1]"
    expression: "last(/MySQL Slow Query/mysql.slow_query.max_time)>10"
    severity: High
    description: "最大查询耗时超过10秒,可能阻塞业务事务,需立即排查"

  # P2 - 警告:10分钟内新增5种以上慢SQL指纹
  - name: "MySQL慢查询增多:10分钟新增{ITEM.LASTVALUE}种慢SQL [P2]"
    expression: "last(/MySQL Slow Query/mysql.slow_query.count)>5"
    severity: Warning
    description: "短时间内出现大量不同的慢SQL,可能存在批量索引缺失或数据量突增"

  # P3 - 信息:存在超过3秒的慢查询
  - name: "MySQL慢查询注意:存在超3秒SQL [P3]"
    expression: "last(/MySQL Slow Query/mysql.slow_query.max_time)>3 and last(/MySQL Slow Query/mysql.slow_query.max_time)<=10"
    severity: Information
    description: "存在3-10秒慢查询,建议排查是否需要索引优化"

  # 趋势告警:慢查询数量持续增长(退化预警)
  - name: "MySQL慢查询趋势恶化:连续3个周期递增"
    expression: "last(/MySQL Slow Query/mysql.slow_query.count)>avg(/MySQL Slow Query/mysql.slow_query.count,1h)*2 and last(/MySQL Slow Query/mysql.slow_query.count)>3"
    severity: Warning
    description: "慢查询数量持续增长,可能存在数据量增长导致的性能退化"
4.3 告警通知模板(企业微信 Webhook)
python 复制代码
#!/usr/bin/env python3
# /opt/scripts/slow_query_alert_wechat.py
# Zabbix 动作调用,发送告警到企业微信

import sys
import json
import requests
import os

WEBHOOK_URL = os.environ.get('WECHAT_WEBHOOK', 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY')

def send_alert(subject, message):
    # 解析告警信息
    content = f"""**{subject}**

{message}

---
> 处理建议:
> 1. 登录对应服务器查看 /opt/slow_query_reports/latest_summary.json
> 2. 找到最慢的SQL指纹,确认是否缺索引
> 3. 如为核心交易SQL,优先评估索引优化方案"""

    payload = {
        "msgtype": "markdown",
        "markdown": {"content": content}
    }

    resp = requests.post(WEBHOOK_URL, json=payload, timeout=10)
    return resp.status_code == 200

if __name__ == '__main__':
    subject = sys.argv[1] if len(sys.argv) > 1 else "MySQL慢查询告警"
    message = sys.argv[2] if len(sys.argv) > 2 else ""
    send_alert(subject, message)

五、Step 6:Grafana慢查询分析看板

看板包含4个核心面板,数据源为 Zabbix(通过 Zabbix-Grafana 插件)或直接查询存储慢查询统计的MySQL表。

面板1:慢查询数量趋势(Time Series)
sql 复制代码
-- 数据源:Zabbix history 或直接查询统计表
SELECT
    FROM_UNIXTIME(clock) AS time,
    value AS slow_query_count
FROM history
WHERE itemid = {SLOW_QUERY_COUNT_ITEMID}
    AND clock > UNIX_TIMESTAMP(NOW() - INTERVAL 24 HOUR)
ORDER BY clock;
面板2:Top 10 慢SQL排行(Table)
sql 复制代码
-- 如果使用pt-query-digest --review模式写入数据库
SELECT
    SUBSTR(fingerprint, 1, 80) AS 'SQL摘要',
    ROUND(avg_query_time, 2) AS '平均耗时(s)',
    exec_count AS '执行次数',
    ROUND(max_query_time, 2) AS '最大耗时(s)',
    ROUND(avg_query_time * exec_count, 1) AS '总耗时(s)',
    db_name AS '数据库',
    last_seen AS '最后出现'
FROM slow_query_review
WHERE last_seen > NOW() - INTERVAL 24 HOUR
ORDER BY avg_query_time * exec_count DESC
LIMIT 10;
面板3:响应时间分布(Histogram)
sql 复制代码
SELECT
    CASE
        WHEN query_time < 1 THEN '0-1s'
        WHEN query_time < 3 THEN '1-3s'
        WHEN query_time < 5 THEN '3-5s'
        WHEN query_time < 10 THEN '5-10s'
        ELSE '10s+'
    END AS time_bucket,
    COUNT(*) AS count
FROM slow_query_log_parsed
WHERE timestamp > NOW() - INTERVAL 24 HOUR
GROUP BY time_bucket
ORDER BY FIELD(time_bucket, '0-1s', '1-3s', '3-5s', '5-10s', '10s+');
面板4:按数据库维度聚合(Pie Chart)
sql 复制代码
SELECT
    db_name AS '数据库',
    COUNT(*) AS '慢查询数'
FROM slow_query_log_parsed
WHERE timestamp > NOW() - INTERVAL 24 HOUR
GROUP BY db_name
ORDER BY COUNT(*) DESC;

六、落地效果与避坑清单

这套链路在我们的环境跑了4个月,核心数据变化:

指标 之前 之后
慢查询发现到处理平均时间 3-7天(等用户投诉) 10分钟内告警
累计慢SQL指纹数 不知道 每日可追踪
因慢SQL导致的业务投诉 月均4-5次 月均0-1次
索引优化响应周期 被动、无优先级 P1当天处理、P2 3天内
避坑清单
现象 解决
slow_log 文件暴增 几天就几个G 配合 logrotate 按天轮转,保留7天
pt-query-digest 解析慢 大文件解析要几分钟 用增量解析(记录offset),每次只处理新增部分
long_query_time 设太短 大量无意义记录 配合 min_examined_row_limit 过滤小查询
监控只看"有没有"不看"变化趋势" 慢SQL一直有3条没人管 加趋势告警:数量持续递增才报
告警后不知道查哪条SQL 只知道"有慢查询"但不知道是哪条 告警通知里带Top 1 SQL指纹摘要
logrotate 配置参考
bash 复制代码
# /etc/logrotate.d/mysql-slow-log
/var/log/mysql/slow.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 640 mysql mysql
    postrotate
        /usr/bin/mysqladmin flush-logs
    endscript
}

七、和运维体系结合

慢查询监控不是孤立的------它是整个数据库运维可观测性的一环。前面第一节提到的"理想状态",在我们的环境里已经跑通了。

Zabbix采集到的慢查询告警通过Webhook推送到工单系统,自动创建一张P2/P3工单。工单里带着慢SQL指纹摘要、所属数据库和触发时段,DBA直接从工单拿上下文开始排查,不需要再登服务器翻日志。我们用的是冠服云EMS平台做这层告警→工单的自动联动------告警事件进来后按规则匹配到对应数据库的资产记录,工单自动挂到那条资产下面。这样做有一个额外好处:月底拉一下资产维度的工单统计,"哪个库的慢查询工单最多、处理周期多长"一目了然,能帮团队决定下一步索引优化的优先级。

另外一个实际验证的经验:告警创建工单时,我们让工单标题自动带上Top 1慢SQL的指纹摘要(前60个字符),这样运维主管在工单列表里扫一眼就能判断优先级,不需要点进去看详情。这个小细节把慢查询工单的平均响应时间从4小时压到了40分钟。

这套链路的关键不是工具选什么,而是两点:第一,慢查询必须进入你的告警体系------不被告警覆盖的问题,就是不存在的问题;第二,告警必须能自动变成可追踪的工单------否则告警看了、没人跟、下次还来。


八、落地Checklist

  • MySQL slow_log 开启并配置合理阈值(建议1秒)
  • logrotate 配置慢查询日志轮转(日切,保留7天)
  • 安装 Percona Toolkit,验证 pt-query-digest 可用
  • 部署增量解析脚本,crontab 每10分钟执行
  • Zabbix 配置 UserParameter,验证指标可采集
  • 配置三级告警规则(P1 >10s / P2 >5种指纹 / P3 >3s)
  • 告警通知模板包含SQL摘要信息
  • Grafana 看板部署(趋势+Top SQL+分布+维度)
  • 验证端到端:手动执行一条 SELECT SLEEP(5) 确认告警触发

本文基于 MySQL 5.7/8.0 + Zabbix 6.x + Grafana 9.x 环境验证。核心思路适用于 MariaDB 和 PostgreSQL(pg_stat_statements 替代 slow_log)。

相关推荐
My_Java_Life8 小时前
SpringAI基于Mysql jdbc方式存储对话记忆
mysql·ai
沐言人生9 小时前
ReactNative 源码分析12——Native View创建流程onBatchComplete
android·react native
caicai_xiaobai9 小时前
QT搭建安卓开发环境
android
YF02119 小时前
Android 异形屏与横屏全屏沉浸式适配技术方案
android·app
清平乐的技术专栏9 小时前
一文读懂Kafka中的“消费”(对标MySQL数据库)
数据库·mysql·kafka
2501_941982059 小时前
通过 API 实时监听企业微信外部群变更事件并同步本地数据库
android·自动化·企业微信·rpa
阳光九叶草LXGZXJ9 小时前
达梦数据库-学习-57-读写数据页超时告警排查(page[x,x,xxxxxx] disk write uses)-DSC集群版
linux·运维·服务器·数据库·sql·学习
Ggsddu1112223339 小时前
制造企业MES系统推荐:2026年MES选型指南与主流系统深度评测
mysql·制造
Java成神之路-9 小时前
MySQL 参数 completion_type 深入浅出:搞懂事务提交后的“隐藏动作”
mysql