MySQL跟踪SQL&慢查询分析工具
简介
之前的工作一直使用的SQL SERVER, 用过的都知道,SQL SERVER有配套的SQL跟踪工具SQL Profiler,开发或者定位BUG过程中,可以在操作页面的时候,实时查看数据库执行的SQL语句,十分方便。最近的项目使用MySQL,没有类似的功能,感觉到十分的不爽,网上也没有找到合适的免费工具,所以自己研究做了一个简单工具。
功能
- 实时查询MySql执行的SQL语句
- 查看性能异常的SQL(执行超过2秒)
技术方案
- 前端vue,样式bootstrap
- 后台dotnet core mvc
先看一下的效果:
实现原理
Mysql支持输出日志,通过以下命令查看当前状态
show VARIABLES like '%general_log%' //是否开启输出所有日志
show VARIABLES like '%slow_query_log%' //是否开启慢SQL日志
show VARIABLES like '%log_output%' //查看日志输出方式(默认file,还支持table)
show VARIABLES like '%long_query_time%' //查看多少秒定义为慢SQL
下面我们将所有日志、慢SQL日志打开,日志输出修改为table,定义执行2秒以上的为慢SQL
- set global log_output='table' //日志输出到table(默认file)
- set global general_log=on; //打开输出所有日志
- set global slow_query_log=on; //打开慢SQL日志
- set global long_query_time=2 //设置2秒以上为慢查询
- repair table mysql.general_log //修复日志表(如果general_log表报错的情况下执行)
注意:以上的设置,数据库重启后将失效,永久改变配置需要修改my.conf文件
现在日志文件都存在数据库表里面了,剩下的工作就是取数并展示出来就行了。本项目后台使用的MVC取数,然后VUE动态绑定,Bootstrap渲染样式。
前端代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>开发工具</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
</head>
<body>
<div id="app">
<ul id="myTab" class="nav nav-tabs">
<li class="active">
<a href="#trace" data-toggle="tab">
SQL跟踪
</a>
</li>
<li>
<a href="#slow" data-toggle="tab">
性能异常SQL
</a>
</li>
</ul>
<hr />
<div id="myTabContent" class="tab-content">
<div id="trace" class="tab-pane fade in active">
<div>
<input id="btnStart" class="btn btn-primary" type="button" value="开始" v-show="startShow" v-on:click="start" />
<input id="btnPause" class="btn btn-primary" type="button" value="暂停" v-show="pauseShow" v-on:click="pause" />
<input id="btnClear" class="btn btn-primary" type="button" value="清空" v-show="clearShow" v-on:click="clear" />
</div>
<hr />
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>时间</th>
<th>执行语句</th>
</tr>
</thead>
<tbody>
<tr v-for="log in logs">
<td>
{{log.time}}
</td>
<td>
@*<input class="btn btn-danger" type="button" value="复制" name="copy" />*@
{{log.sql}}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="slow" class="tab-pane fade">
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>执行时长(时:分:秒,毫秒)</th>
<th>锁定时长(时:分:秒,毫秒)</th>
<th>开始时间</th>
<th>数据库</th>
<th>操作者</th>
<th>执行语句</th>
</tr>
</thead>
<tbody>
<tr v-for="query in slowQuerys">
<td>
{{query.queryTime}}
</td>
<td>
@*<input class="btn btn-danger" type="button" value="复制" name="copy" />*@
{{query.lockTime }}
</td>
<td>
{{query.startTime }}
</td>
<td>
{{query.db }}
</td>
<td>
{{query.userHost}}
</td>
<td>
{{query.sql}}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
startShow: true,
pauseShow: false,
clearShow: true,
logs: [],
slowQuerys: []
},
methods: {
start: function () {
this.timer = setInterval(this.trace, 5000);
this.pauseShow = true;
this.startShow = false;
},
pause: function () {
clearInterval(this.timer);
this.pauseShow = false;
this.startShow = true;
},
clear: function () {
this.logs = null;
},
trace: function () {
//发送 post 请求
this.$http.post('/home/start', {}, { emulateJSON: true }).then(function (res) {
this.logs = res.body;
}, function (res) {
console.log(logs);
});
}
},
created: function () {
},
mounted: function () {
this.$http.post('/home/slow', {}, { emulateJSON: true }).then(function (res) {
this.slowQuerys = res.body;
}, function (res) {
console.log(this.slowQuerys);
});
},
destroyed: function () {
clearInterval(this.time)
}
});
</script>
</body>
</html>
后端代码
using Ade.Tools.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using MySql.Data.MySqlClient;
using Microsoft.Extensions.Caching.Memory;
namespace Ade.Tools.Controllers
{
public class HomeController : Controller
{
public IConfiguration Configuration { get; set; }
public HomeController(IConfiguration configuration)
{
this.Configuration = configuration;
this.ConnStr = Configuration["Sql:DefaultConnection"];
}
public static DateTime StartTime { get; set; } = DateTime.MinValue;
public static List<string> TableNames { get; set; }
public string ConnStr { get; set; }
public JsonResult Start()
{
if (StartTime == DateTime.MinValue)
{
StartTime = DateTime.Now;
}
int size = int.Parse(Configuration["Size"]);
string[] blackList = Configuration["Blacklist"].Split(",");
List<string> tableNames = GetTableNames();
List<LogItem> logItems = new List<LogItem>();
List<LogItemDTO> logItemDTOs = new List<LogItemDTO>();
using (MySqlConnection mySqlConnection = new MySqlConnection(this.ConnStr))
{
//string sqlStart = "set global log_output='table';set global general_log=on; repair table mysql.general_log;";
//Dapper.SqlMapper.Execute(mySqlConnection, sqlStart);
logItemDTOs = Dapper.SqlMapper.Query<LogItemDTO>(mySqlConnection, $" select * from mysql.general_log " +
$"where event_time>'{StartTime.ToString("yyyy-MM-dd HH:mm:ss")}' " +
$"order by event_time desc ")
//+ $"limit {size} "
.ToList();
}
logItemDTOs.ForEach(e =>
{
LogItem logItem = new LogItem()
{
Time = e.event_time.ToString("yyyy-MM-dd HH:mm:ss.fff"),
CommondType = e.command_type,
ServerId = e.server_id,
ThreadId = e.thread_id,
UserHost = e.user_host,
Sql = System.Text.Encoding.Default.GetString(e.argument)
};
if (tableNames.Any(a => logItem.Sql.Contains(a))
&& !blackList.Any(b => logItem.Sql.Contains(b))
)
{
logItems.Add(logItem);
}
});
return new JsonResult(logItems);
}
public JsonResult Slow()
{
List<SlowQuery> slowQueries = new List<SlowQuery>();
using (MySqlConnection mySqlConnection = new MySqlConnection(this.ConnStr))
{
string sql = "select * from mysql.slow_log order by query_time desc";
List<SlowQueryDTO> slowDtos = Dapper.SqlMapper.Query<SlowQueryDTO>(mySqlConnection, sql).ToList();
slowDtos.ForEach(e => {
slowQueries.Add(new SlowQuery()
{
DB = e.db,
LockTime = DateTime.Parse(e.lock_time.ToString()).ToString("HH:mm:ss.fffff"),
QueryTime = DateTime.Parse(e.query_time.ToString()).ToString("HH:mm:ss.fffff"),
RowsExamined = e.rows_examined,
RowsSent = e.rows_sent,
Sql = System.Text.Encoding.Default.GetString( (byte[])e.sql_text),
StartTime = e.start_time.ToString("yyyy-MM-dd HH:mm:ss"),
UserHost = e.user_host
});
});
}
return new JsonResult(slowQueries);
}
public string On()
{
using (MySqlConnection mySqlConnection = new MySqlConnection(this.ConnStr))
{
string sql = "set global log_output='table';set global general_log=on; repair table mysql.general_log;";
Dapper.SqlMapper.Execute(mySqlConnection, sql);
}
return "ok";
}
public string Off()
{
using (MySqlConnection mySqlConnection = new MySqlConnection(this.ConnStr))
{
string sql = "set global general_log=off;";
Dapper.SqlMapper.Execute(mySqlConnection, sql);
}
return "ok";
}
public string Clear()
{
using (MySqlConnection mySqlConnection = new MySqlConnection(this.ConnStr))
{
string sql = $@"
SET GLOBAL general_log = 'OFF';
RENAME TABLE general_log TO general_log_temp;
DELETE FROM `general_log_temp`;
RENAME TABLE general_log_temp TO general_log;
SET GLOBAL general_log = 'ON';
";
Dapper.SqlMapper.Execute(mySqlConnection, sql);
}
return "ok";
}
public IActionResult Index()
{
return View();
}
private List<string> GetTableNames()
{
MemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions());
var cacheKey = "MySqlProfile_TableNames";
List<string> tableNames = memoryCache.Get <List<string>>(cacheKey);
if (tableNames != null)
{
return tableNames;
}
string[] traceDbs = Configuration["TraceDatabaseNames"].Split(",");
string sqlTables = "SELECT distinct TABLE_NAME FROM information_schema.columns";
foreach (var db in traceDbs)
{
if (!sqlTables.Contains("WHERE"))
{
sqlTables += " WHERE table_schema='" + db + "'";
}
else
{
sqlTables += " OR table_schema='" + db + "'";
}
}
using (MySqlConnection mySqlConnection = new MySqlConnection(this.ConnStr))
{
// WHERE table_schema='mice'
tableNames = Dapper.SqlMapper.Query<string>(mySqlConnection, sqlTables).ToList();
}
memoryCache.Set(cacheKey, tableNames, TimeSpan.FromMinutes(30));
return tableNames;
}
}
}
源代码
修改完appsettings.json文件里面的连接字符串以及其他配置(详情自己看注释,懒得写了),就可以使用了。
https://github.com/holdengong/MysqlProfiler
最后一点
开启日志会产生大量的文件,需要注意定时清理
- SET GLOBAL general_log = 'OFF'; // 关闭日志
- RENAME TABLE general_log TO general_log_temp; //表重命名
- DELETE FROM
general_log_temp
; //删除所有数据 - RENAME TABLE general_log_temp TO general_log; //重命名回来
- SET GLOBAL general_log = 'ON'; //开启日志
MySQL跟踪SQL&慢查询分析工具的更多相关文章
- Mysql优化_慢查询开启说明及Mysql慢查询分析工具mysqldumpslow用法讲解
Mysql优化_慢查询开启说明及Mysql慢查询分析工具mysqldumpslow用法讲解 Mysql慢查询开启 Mysql的查询讯日志是Mysql提供的一种日志记录,它用来记录在Mysql中响应 ...
- mysql慢查询分析工具比较与实战
00 前言 在进行mysql性能优化的时候,第一个想到的便是查看慢sql. 但是对于慢sql有没有什么好的工具进行分析呢? 推荐两个工具mysqldumpslow及pt-query-digest. m ...
- Red Gate系列之七 SQL Search 1.1.6.1 Edition SQL查询分析工具使用教程
原文:Red Gate系列之七 SQL Search 1.1.6.1 Edition SQL查询分析工具使用教程 Red Gate系列之七 SQL Search 1.1.6.1 Edition SQL ...
- mysql性能优化-慢查询分析、优化索引和配置 (慢查询日志,explain,profile)
mysql性能优化-慢查询分析.优化索引和配置 (慢查询日志,explain,profile) 一.优化概述 二.查询与索引优化分析 1性能瓶颈定位 Show命令 慢查询日志 explain分析查询 ...
- [转]一个用户SQL慢查询分析,原因及优化
来源:http://blog.rds.aliyun.com/2014/05/23/%E4%B8%80%E4%B8%AA%E7%94%A8%E6%88%B7sql%E6%85%A2%E6%9F%A5%E ...
- MySQL慢查询分析工具pt-query-digest详解
一.简介 pt-query-digest是用于分析mysql慢查询的一个工具,它可以分析binlog.General log.slowlog,也可以通过SHOWPROCESSLIST或者通过tcpdu ...
- mysql慢查询分析工具 mysqlsla 安装
概述 mysqlsla 是一款很不错的mysql慢查询日志分析工具,而且简单易用.mysqlsla 是用perl编写的脚本,运行需要perl-DBI和per-DBD-Mysql两模块的支持.mysql ...
- mysql性能优化-慢查询分析、优化索引和配置
一.优化概述 二.查询与索引优化分析 1性能瓶颈定位 Show命令 慢查询日志 explain分析查询 profiling分析查询 2索引及查询优化 三.配置优化 1) max_connec ...
- mysqlsla慢查询分析工具教程
mysqlsla是一款帮助语句分析.过滤.和排序的功能,能够处理MySQL慢查询日志.二进制日志等.整体来说, 功能非常强大. 能制作SQL查询数据报表,分析包括执行频率, 数据量, 查询消耗等. 且 ...
随机推荐
- apache POI技术的使用
Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能. 下载开发包: 解压上面的zip文件: 在项目中引入 ...
- 文本框只允许输入数字.net/javascript
<input type="text" name="test" onKeyUp="test1.value=(this.value=this.val ...
- GitKraken使用教程-基础部分(7)
8. 本地分支和标签 1) 在提交记录区中查看分支状态 提交记录区中每一个分支都位于一个提交记录所在的行中. 从图 2‑1中可以看到,服务器上的master分支停留在整理格式(把这个提交记录记为or ...
- join() 和 sleep() 区别
来源于<Java多线程编程核心技术> 一.join() 作用 在很多情况,主线程创建并启动子线程,如果子线程中需要进行大量的耗时计算,主线程往往早于子线程结束.这时,如果主线程想等待子线程 ...
- Python介绍以及Python环境搭建
Python介绍以及Python环境搭建 1.Python 发展历史 Python是由Guido van Rossum在八十年代末和九十年代初,在荷兰国家数学和计算机科学研究所设计出来的,据说是在圣诞 ...
- 零基础逆向工程37_Win32_11_事件_线程同步
1 内核对象 前面已经学过线程和互斥体两个内核对象.此节讲了事件这个内核对象.前面提出了内核对象这个概念,可能不太清晰,简单来说内核对象就是系统层的东西. 1.1 小结内核对象: 进程.线程.事件.互 ...
- 让DIV的滚动条自动滚动到最底部
一个在线聊天窗口,在做最后的修饰时,需要对获得的信息即时滚动以保证用户总能看到最新消息. 我得出的结论是:在选中div时,必须用原生js,jQuery不起作用 <!DOCTYPE> < ...
- int **a 和 int (*a)[]的区别
关于理论知识隔壁们的教程说的很详细了我就不多赘述了.我这边主要贴一段代码来看看这两种东西使用上的区别到底在哪. #include <stdio.h> int main(int argc, ...
- 进一步了解this和super
知乎上看到一问题很好,拿了与大家分享,原地址:https://www.zhihu.com/question/31548104. 问: JAVA 中this 和super与覆写冲突的问题? 实例一: 输 ...
- ModuleNotFoundError: No module named 'yaml'
ModuleNotFoundError: No module named 'yaml' 需要安装 pyyaml 包