MySQL 慢 sql 监控怎么做

1、思路

之前用 mysql 一直没有考虑到这点,mysql 慢 sql 监控是很重要的,它能帮我们梳理我们的业务 sql 到底是哪里处了问题,那么慢 sql 监控怎么做呢?

有两种思路来实现:

    1. 在应用层做,比如我们的系统使用 mybatis,则可以使用 mybatis 的拦截器,在想要监控的 sql 前后进行即时,超时后打 error 日志。如果日志接入日志平台,那么日志平台可以配置报警邮件,直接发邮件提醒开发。
    1. 在 mysql 层做,mysql 开启慢日志,记录慢查询 sql。可以读取慢查询日志,分析哪些 sql 属于慢查询,然后发邮件提醒等。这等于做了一个慢 sql 预警系统,一般大公司都会有这样的一套系统(https://github.com/wxisme/slowsql-monitor 类似于这种)。

开启 mysql 慢查询日志的方法如下(https://github.com/wxisme/slowsql-monitor 可以参照这个工程):

1.First configure the MySQL slow query log,you need to log in to the mysql client. Turn on MySQL slow query log switch:
show variables like 'slow_query_log';
set global slow_query_log='ON';

2.Set the long query time in seconds:
show variables like 'long_query_time';
set global long_query_time=1;

3.Set the log queries not using indexes:
show variables like 'log_queries_not_using_indexes';
set global log_queries_not_using_indexes='ON';

4.show slow log location:
show variables like 'slow_query_log_file';

mysql 的 slowlog 的内容如下:

/usr/local/mysql/bin/mysqld, Version: 5.7.28-log (MySQL Community Server (GPL)). started with:
Tcp port: 3306  Unix socket: /tmp/mysql.sock
Time                 Id Command    Argument
# Time: 2021-04-10T07:22:09.260226Z
# User@Host: root[root] @ localhost [127.0.0.1]  Id:  7405
# Query_time: 0.247682  Lock_time: 0.025775 Rows_sent: 103  Rows_examined: 103
use deepwise;
SET timestamp=1618039329;
/* ApplicationName=DataGrip 2018.3.1 */ SELECT t.* FROM deepwise.DW_AI_RESULT t
LIMIT 501;
# Time: 2021-04-10T07:22:37.999494Z
# User@Host: root[root] @ localhost [127.0.0.1]  Id:  7406
# Query_time: 0.018695  Lock_time: 0.017633 Rows_sent: 0  Rows_examined: 0
SET timestamp=1618039357;
/* ApplicationName=DataGrip 2018.3.1 */ SELECT t.* FROM deepwise.dw_ai_result_process t
LIMIT 501;
# Time: 2021-04-10T07:23:04.504722Z
# User@Host: root[root] @ localhost [127.0.0.1]  Id:  7404
# Query_time: 0.008424  Lock_time: 0.002104 Rows_sent: 0  Rows_examined: 103
SET timestamp=1618039384;
/* ApplicationName=DataGrip 2018.3.1 */ select * from DW_AI_RESULT where PATIENT_ID like '%4468%';

剩下的就是解析这个 log 日志,然后把数据给前端进行页面渲染什么的。

2、mybatis 拦截器方案

package com.best.ecboss.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.BeanUtils;

import java.beans.PropertyDescriptor;
import java.util.Date;
import java.util.Map;
import java.util.Properties;

@Intercepts(
        @Signature(type = Executor.class, method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
)
public class QueryDaoMybatisInterceptor implements Interceptor {

    private final Log logger = LogFactory.getLog(QueryDaoMybatisInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs(); //方法参数
        Date begin = new Date();
        Object ret = invocation.proceed();
        Date end = new Date();
        try {
            MappedStatement mappedStatement = (MappedStatement) args[0];
            // 方法参数
            Object params = args[1];
            // mapper 中的方法名称,如 “com.xushu.mysql.slowsql.dao.AccountMapper.selectList”
            String mapperId = mappedStatement.getId();
            // 获取 sql 语句,并格式化 sql,将很多空格替换成一个空格
            String sql = formatSql(mappedStatement.getBoundSql(params).getSql());

            log(mapperId, sql, params, begin, end);
        } catch (Exception e) {
        }
        return ret;
    }

    private String formatSql(String sql) {
        return sql.replaceAll("\\s+", " ");
    }

    private void log(String statementId, String sql, Object params, Date begin, Date end) {
        // 这边可以记录自己想要的查询方法,比如 “selectList”
        if (statementId == null || !statementId.contains(".selectList")) {
            return;
        }
        long ms = end.getTime() - begin.getTime();
        String paramStr = parseParams(params);
        if (ms > 10000) {
            logger.error("ms:[" + ms + "],sql:" + sql + ",param:[" + paramStr + "]");
        } else if (ms > 1000) {
            logger.warn("ms:[" + ms + "],sql:" + sql + ",param:[" + paramStr + "]");
        } else {
            logger.info("ms:[" + ms + "],sql:" + sql + ",param:[" + paramStr + "]");
        }
    }

    // 格式化传参
    private String parseParams(Object params) {
        StringBuilder sb = new StringBuilder();
        try {
            if (params instanceof Map) {
                Map map = (Map) params;
                map.forEach((k, v) -> {
                    sb.append(",").append(k).append(":").append(v);
                });
            } else if (BeanUtils.isSimpleProperty(params.getClass())) {
                sb.append(",").append(params.getClass().getSimpleName()).append(":").append(params).append(",");
            } else {
                // 反射getter属性
                PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(params.getClass());
                for (PropertyDescriptor pd : pds) {
                    if (pd.getReadMethod() != null && !"class".equals(pd.getName())) {
                        String name = pd.getName();
                        Object value = null;
                        value = pd.getReadMethod().invoke(params);
                        sb.append(",").append(name).append(":").append(value);
                    }
                }
            }
        } catch (Exception e) {
        }
        if (sb.length() > 0) {
            return sb.toString().substring(1);
        }
        return "";
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容