Anemometer 是一个图形化显示MySQL慢日志的工具。结合pt-query-digest,Anemometer可以很轻松的帮你去分析慢查询日志,让你很容易就能找到哪些SQL需要优化。
一、首先安装LNMP环境
二、percona-toolkit工具的安装
pt-query-digest是percona-toolkit里面一个工具,其作用就是分析慢查询日志,将MySQL慢查询日志进行统计并友好的显示出来
1、安装依赖包
[root@zabbix_server bin]# yum install perl-DBI perl-DBD-MySQL perl-IO-Socket-SSL perl-Digest-MD5 -y
2、下载percona-toolkit二进制包
[root@zabbix_server conf.d]# mkdir /usr/local/src/percona-toolkit [root@zabbix_server conf.d]# cd /usr/local/src/percona-toolkit [root@zabbix_server percona-toolkit]# wget https://www.percona.com/downloads/percona-toolkit/3.0.10/binary/tarball/percona-toolkit-3.0.10_x86_64.tar.gz --2020-04-07 16:44:49-- https://www.percona.com/downloads/percona-toolkit/3.0.10/binary/tarball/percona-toolkit-3.0.10_x86_64.tar.gz 正在解析主机 www.percona.com... 74.121.199.234 正在连接 www.percona.com|74.121.199.234|:443... 已连接。 已发出 HTTP 请求,正在等待回应... 200 OK 长度:8170395 (7.8M) [application/x-gzip] 正在保存至: “percona-toolkit-3.0.10_x86_64.tar.gz” 28% [============================> ] 2,312,153 693K/s eta(英国中部时33% [==================================> ] 2,770,905 759K/s eta(英国中部时39% [========================================> ] 3,229,657 817K/s eta(英国中部时45% [==============================================> ] 3,688,409 871K/s eta(英国中部时50% [====================================================> ] 4,147,161 916K/s eta(英国中部时56% [==========================================================> ] 4,605,913 953K/s eta(英国中部时61% [================================================================> ] 5,064,665 986K/s eta(英国中部时67% [======================================================================> ] 5,523,417 1014K/s eta(英国中部时73% [============================================================================> ] 5,982,169 1.02M/s eta(英国中部时77% [=================================================================================> ] 6,326,233 1.01M/s eta(英国中部时83% [=======================================================================================> ] 6,850,521 1.10M/s eta(英国中部时87% [============================================================================================> ] 7,178,201 1.15M/s eta(英国中部时91% [================================================================================================> ] 7,505,881 1.25M/s eta(英国中部时96% [=====================================================================================================> ] 7,866,329 1.29M/s eta(英国中部时100%[=========================================================================================================>] 8,170,395 1.37M/s in 7.1s 2020-04-07 16:44:59 (1.09 MB/s) - 已保存 “percona-toolkit-3.0.10_x86_64.tar.gz” [8170395/8170395]) [root@zabbix_server percona-toolkit]#
解压
[root@zabbix_server percona-toolkit]# tar -zxvf percona-toolkit-3.0.10_x86_64.tar.gz percona-toolkit-3.0.10/ percona-toolkit-3.0.10/CONTRIBUTE.md percona-toolkit-3.0.10/Makefile.PL percona-toolkit-3.0.10/docker-compose.yml percona-toolkit-3.0.10/CONTRIBUTING.md percona-toolkit-3.0.10/Gopkg.lock percona-toolkit-3.0.10/README.md percona-toolkit-3.0.10/bin/ percona-toolkit-3.0.10/bin/pt-summary percona-toolkit-3.0.10/bin/pt-slave-delay percona-toolkit-3.0.10/bin/pt-mongodb-query-digest percona-toolkit-3.0.10/bin/pt-slave-restart percona-toolkit-3.0.10/bin/pt-variable-advisor percona-toolkit-3.0.10/bin/pt-fingerprint percona-toolkit-3.0.10/bin/pt-secure-collect percona-toolkit-3.0.10/bin/pt-index-usage percona-toolkit-3.0.10/bin/pt-archiver percona-toolkit-3.0.10/bin/pt-find percona-toolkit-3.0.10/bin/pt-heartbeat percona-toolkit-3.0.10/bin/pt-fifo-split percona-toolkit-3.0.10/bin/pt-fk-error-logger percona-toolkit-3.0.10/bin/pt-mysql-summary percona-toolkit-3.0.10/bin/pt-online-schema-change percona-toolkit-3.0.10/bin/pt-table-usage percona-toolkit-3.0.10/bin/pt-align percona-toolkit-3.0.10/bin/pt-query-digest percona-toolkit-3.0.10/bin/pt-ioprofile percona-toolkit-3.0.10/bin/pt-visual-explain percona-toolkit-3.0.10/bin/pt-stalk percona-toolkit-3.0.10/bin/pt-mext percona-toolkit-3.0.10/bin/pt-table-checksum percona-toolkit-3.0.10/bin/pt-show-grants percona-toolkit-3.0.10/bin/pt-pmp percona-toolkit-3.0.10/bin/pt-upgrade percona-toolkit-3.0.10/bin/pt-diskstats percona-toolkit-3.0.10/bin/pt-sift percona-toolkit-3.0.10/bin/pt-config-diff percona-toolkit-3.0.10/bin/pt-slave-find percona-toolkit-3.0.10/bin/pt-kill percona-toolkit-3.0.10/bin/pt-duplicate-key-checker percona-toolkit-3.0.10/bin/pt-deadlock-logger percona-toolkit-3.0.10/bin/pt-mongodb-summary percona-toolkit-3.0.10/bin/pt-table-sync percona-toolkit-3.0.10/lib/ percona-toolkit-3.0.10/docs/ percona-toolkit-3.0.10/docs/percona-toolkit.pod percona-toolkit-3.0.10/Gopkg.toml percona-toolkit-3.0.10/MANIFEST percona-toolkit-3.0.10/COPYING percona-toolkit-3.0.10/Changelog percona-toolkit-3.0.10/INSTALL [root@zabbix_server percona-toolkit]# mv percona-toolkit-3.0.10 /usr/local/
3、运行查看版本
[root@zabbix_server bin]# ./pt-query-digest --version pt-query-digest 3.0.10 [root@zabbix_server bin]#
三、安装anemometer
1、下载源码
[root@zabbix_server www]# cd /var/www/ [root@zabbix_server www]# git clone git://github.com/box/Anemometer.git anemometer Initialized empty Git repository in /var/www/anemometer/.git/ remote: Enumerating objects: 1218, done. remote: Total 1218 (delta 0), reused 0 (delta 0), pack-reused 1218 Receiving objects: 100% (1218/1218), 1.51 MiB | 2 KiB/s, done. Resolving deltas: 100% (627/627), done. [root@zabbix_server www]#
2、在nginx配置anemometer根目录
server { 2 listen 8083; 3 server_name 172.28.18.75; 4 5 #charset koi8-r; 6 #access_log /var/log/nginx/host.access.log main; 7 8 location / { 9 root /var/www/anemometer; 10 index index.php index.html index.htm; 11 } 12
浏览器打开http://172.28.18.75:8083
3、配置目标数据库源
首先复制一份示例配置文件sample.config.inc.php并改名为config.inc.php
[root@zabbix_server conf]# cp sample.config.inc.php config.inc.php [root@zabbix_server conf]#
首先修改数据库连接配置文件datasource_localhost.inc.php
[root@zabbix_server conf]# vim datasource_localhost.inc.php 1 <?php 2 $conf['datasources']['localhost'] = array( 3 'host' => 'localhost', 4 'port' => 3306, 5 'db' => 'slow_query_log', 6 'user' => 'root', 7 'password' => 'xxxxxxxx', 8 'tables' => array( 9 'global_query_review' => 'fact', 10 'global_query_review_history' => 'dimension' 11 ), 12 'source_type' => 'slow_query_log' 13 ); 14
db:slow_query_log :这是慢查询日志存放的数据库,由/var/www/anemometer/install.sql脚本创建,下面的操作将会有导入这个SQL文件
4、导入install.sql初始化数据源的数据库表的配置
mysql> source /var/www/anemometer/install.sql; Query OK, 0 rows affected, 1 warning (0.05 sec) Query OK, 1 row affected (0.00 sec) Database changed Query OK, 0 rows affected (0.10 sec) ERROR 1067 (42000): Invalid default value for 'ts_min'
报错,查看install.sql,发现ts_min列的定义为`ts_min` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
这是mysql5.7以上版本里设置的sql_mode参数的关系,查看sql_mode参数设置
mysql> show variables like '%sql_mode%'; +---------------+-------------------------------------------------------------------------------------------------------------------------------------------+ | Variable_name | Value | +---------------+-------------------------------------------------------------------------------------------------------------------------------------------+ | sql_mode | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | +---------------+-------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.01 sec)
NO_ZERO_IN_DATE,NO_ZERO_DATE:表示不允许日期数据为零值,将sql_mode重新设置
mysql> set sql_mode='STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> show variables like '%sql_mode%'; +---------------+-------------------------------------------------------------------------------------------+ | Variable_name | Value | +---------------+-------------------------------------------------------------------------------------------+ | sql_mode | STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | +---------------+-------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
再导入install.sql
mysql> source /var/www/anemometer/install.sql; Query OK, 1 row affected (0.00 sec) Query OK, 1 row affected (0.00 sec) Database changed Query OK, 0 rows affected (0.01 sec) Query OK, 0 rows affected (0.03 sec) mysql>
新建了一个slow_query_log数据库里面新建了两个表:global_query_review和global_query_review_history,用于保存从目标数据库利用pt-query-digest工具收集的慢日志数据。
5、在目标数据库上使用pt-query-digest工具收集日志推送到slow_query_log数据库中
[root@zabbix_server bin]# ./pt-query-digest --user=root --password=Zaq1xsw@ --review h=172.28.18.75,D=slow_query_log,t=global_query_review --history h=172.28.18.75,D=slow_query_log,t=global_query_review_history --no-report --limit=0% --filter=" \$event->{Bytes} = length(\$event->{arg}) and \$event->{hostname}=\"$HOSTNAME\"" /home/mysql_data/mysql/zabbix_server-slow.log
执行完上面语句,global_query_review和global_query_review_history就会有统计的数据信息
刷新页面,可以看到慢日志数据展示
四、数据库说明
主要字段说明:
- hostname_max : MySQL服务所在主机名称
- db_max: 数据库名称
- checksum : 同global_query_review表中的checksum,两张表通过该值关联
- sample : sql示例
- ts_min : 本次统计(每10分钟一次)该类型sql语句出现的最小时间
- ts_max: 本次统计(每10分钟一次)该类型sql语句出现的最大时间
- ts_cnt : 本次统计该sql语句出现的次数
- Query_time_sum : 本次统计该类型sql语句花费的总时间
- Query_time_min : 本次统计该类型sql语句执行最快的那个sql语句花费的时间
- Query_time_max: 本次统计该类型sql语句执行最慢的那个sql语句花费的时间
- Query_time_pct_95: 本次统计该类型sql语句执行时间位于95%分位的sql执行时间
- Query_time_stddev: 本次统计该类型sql语句执行时间标准差(统计学概念)
- Query_time_median: 本次统计该类型sql语句执行时间位于中位数位置的sql执行时间
- index_ratio:表示的是扫描的行数/返回的结果行数
五、配置anemometer
1、首先将“custom_fields“数组里的”'snippet' => 'LEFT(dimension.sample,50)'改为'snippet' => 'dimension.sample',
2、history表里的ts_cnt列是Sum出来的,如果我们5分钟收集一次日志,这个结果完全不是想要的,增加一个数组元素'query_cnt' => 'MAX(ts_cnt)',这个数据为语句的执行次数,并且将history_defaults和report_defaults的table_fields配置中将ts_cnt改为了query_cnt。
3、单个查询的过去90天历史记录里,它是以ts_min为group by的,因为history表里ts_min只有一个值,既语句第一次执行的时间,所以这会只显示一条数据,而不是每天的数据,这里改成ts_max。既语句最后执行时间将'date' => 'DATE(ts_min)改为'ate' => 'DATE(ts_max)'
4、$conf['report_defaults'] = array数组保存页面统计列表里的数据字段和查询条件,可以在这里进行个性化设置,来显示需要的数据
// custom fields 339 'custom_fields' => array( 340 'checksum' => 'checksum', 341 'snippet' => 'dimension.sample', 342 'query_cnt' => 'MAX(ts_cnt)', 343 'query_max' => 'ROUND(MAX(query_time_max),2)', 344 'query_min' => 'ROUND(MIN(Query_time_min),2)', 345 'query_avg' => 'ROUND(SUM(Query_time_sum)/SUM(ts_cnt),2)', 346 'lock_max' => 'ROUND(MAX(Lock_time_max),4)', 347 'lock_min' => 'ROUND(MIN(Lock_time_min),4)', 348 'lock_avg' => 'ROUND(SUM(Lock_time_sum)/SUM(ts_cnt),4)', 349 'rows_sent_avg' => 'ROUND(SUM(Rows_sent_sum)/SUM(ts_cnt))', 350 'rows_examined_avg' => 'ROUND(SUM(Rows_examined_sum)/SUM(ts_cnt))', 351 'date' => 'DATE(ts_max)', 352 'hour' => 'substring(ts_min,1,13)', 353 'hour_ts' => 'round(unix_timestamp(substring(ts_min,1,13)))', 354 'minute_ts' => 'round(unix_timestamp(substring(ts_min,1,16)))', 355 'minute' => 'substring(ts_min,1,16)',
5、对于某些慢日志里Query ID 是0xFF6BA40AE1ADBA294B52E89F1929E3F8类似这样的ID,在页面显示中会查询不到历史记录,这是因为Anemometer把checksum列设置为了bigint类型,所以,我们需要将checksum列设置为varchar(60)字符型,并修改PHP代码中相应的地方
修改config.inc.php文件,搜索"dec2hex"
将这句注释掉
360 'callbacks' => array( 361 'table' => array( 362 'date' => function ($x) { $type=''; if ( date('N',strtotime($x)) >= 6) { $type = 'weekend'; } return array($x,$type); }, 363 #'checksum' => function ($x) { return array(dec2hex($x), ''); } 364 ) 365 )
修改lib/Anemometer.php文件搜索"translate_checksum()"函数,将里面的"return $this->bchexdec($checksum);"注释掉,在下面增加一句"return $checksum;"
private function translate_checksum($checksum) 359 { 360 if (!in_array($this->data_model->get_source_type(), array('slow_query_log','default'))) 361 { 362 return $checksum; 363 } 364 365 if (preg_match('/^[0-9]+$/', $checksum)) 366 { 367 return $checksum; 368 } 369 else if (preg_match('/^[0-9A-Fa-f]+$/', $checksum)) 370 { 371 #return $this->bchexdec($checksum); 372 return $checksum; 373 } 374 else if (strlen($checksum) == 0) 375 { 376 return null; 377 } 378 else 379 { 380 throw new Exception("Invalid query checksum"); 381 } 382 }
修改AnemometerModel.php 文件里相关使用$checksum作为条件查询的语句,在变量的两边加上单引号,表示引用的是字符串
搜索"$checksum"进行相应修改
public function checksum_exists($checksum) { 200 $checksum_field_name = $this->get_field_name('checksum'); 201 $query = "SELECT `{$checksum_field_name}` FROM `{$this->fact_table}` WHERE `{$checksum_field_name}`='" . $this->mysqli->real_escape_string($checksum) . "'";
public function update_query($checksum, $fields) { 218 $mysqli = $this->mysqli; 219 $checksum_field_name = $this->get_field_name('checksum'); 220 $sql = "UPDATE `{$this->fact_table}` SET "; 221 $sql .= join( 222 ',', array_map( 223 function ($x, $y) use ($mysqli) { 224 if ($y == 'NULL') { 225 return "{$x} = NULL"; 226 } 227 return "`{$x}` = \"" . $mysqli->real_escape_string($y) . '"'; 228 }, array_keys($fields), array_values($fields) 229 ) 230 ); 231 $sql .= " WHERE `{$checksum_field_name}`='" . $this->mysqli->real_escape_string($checksum) . "'";
public function get_query_by_checksum($checksum) { 244 $checksum_field_name = $this->get_field_name('checksum'); 245 $result = $this->mysqli->query("SELECT * FROM `{$this->fact_table}` WHERE `{$checksum_field_name}`='{$checksum}'"); 246 check_mysql_error($result, $this->mysqli); 247 if ($row = $result->fetch_assoc()) { 248 return $row; 249 } 250 return null; 251 }
public function get_query_samples($checksum, $limit = 1, $offset = 0) { 262 $checksum_field_name = $this->get_field_name('checksum'); 263 $time_field_name = $this->get_field_name('time'); 264 $table = $this->dimension_table; 265 if ($this->get_source_type() == 'performance_schema') 266 { 267 $table = $this->fact_table; 268 } 269 $sql = "SELECT * FROM `{$table}` WHERE `{$checksum_field_name}`='{$checksum}' ORDER BY `{$time_field_name}` DESC LIMIT {$limit} OFFSET {$offset}"; 270 return $this->mysqli->query($sql); 271 }
public function checksum_exists($checksum) { 200 $checksum_field_name = $this->get_field_name('checksum'); 201 $query = "SELECT `{$checksum_field_name}` FROM `{$this->fact_table}` WHERE `{$checksum_field_name}`='" . $this->mysqli->real_escape_string($checksum) . "'"; 202 //print "query: {$query}<br>"; 203 $result = $this->mysqli->query($query); 204 check_mysql_error($result, $this->mysqli); 205 if ($result->num_rows) { 206 return true; 207 } 208 return false; 209 }
保存退出后,即可查询正常