MySQL如何获取当前执行的SQL

MySQL如何获取当前执行的SQL

在MySQL的客户端中通过show [full] processlist可以看到当前所有线程,以及线程的状态,当然也包括执行中的sql,但是显示的语句不完全,并且有很多空闲线程会造成干扰。

这里要介绍一个非常好用的开源工具innotop(sourceforge已经被墙了,要过去先找梯子),由于是用perl写的,所以需要安装相关的perl模块,包括DBIDBD::mysqlTerm::ReadKeyTime::HiRes,这些模块都可以到CPAN找到。

innotop的Q模式则可以完美的解决获取当前运行的SQL的问题。innotop -m Q 或者innotop进入后再按shift+q进入Query list模式:

Query List (? for help) mysql01, 75+03:16:16, 774.20 QPS, 83 thd, 5.1.24-rc-log

CXN    When   Load  QPS     Slow  QCacheHit  KCacheHit  BpsIn    BpsOut
my120  Now    0.00  774.20     0     40.22%    100.00%  207.98k    1.46M
my120  Total  0.00  212.69     2     29.70%    100.00%   56.90k  402.15k

CXN     Cmd    ID      User    Host           DB      Time   Query
mysql01  Query   20936  poster  192.168.1.1    poster  00:00
select a.poster_id, a.pic_id, a.gmt_create, a.gmt_modified, a.url, a.no

然后按e并输入thread ID显示执行计划或者按f显示完整sql语句,或者按o显示系统优化过的语句(需要MySQL的版本支持EXPLAIN EXTENDED)。个人感觉,还是e最有用,其他两个选项,则有点鸡肋了。

Query List (? for help) mysql01, 75+03:16:16, 774.20 QPS, 83 thd, 5.1.24-rc-log
EXPLAIN PARTITIONS
select a.poster_id, a.pic_id, a.gmt_create, a.gmt_modified, a.url, a.notes,
a.type, a.indexed, a.user_id, a.user_nick, b.pic_path
from poster.poster_pic a inner join poster.picture b on
(a.pic_id = b.id) where a.poster_id = 3390 order by a.indexed
______________ Sub-Part 1 ______________  ________ Sub-Part 1 ________
Select Type: SIMPLE                       Select Type: SIMPLE
      Table: a                                  Table: b
 Partitions:                               Partitions:
       Type: ref                                 Type: eq_ref
 Poss. Keys: PRIMARY                       Poss. Keys: PRIMARY
      Index: PRIMARY                            Index: PRIMARY
 Key Length: 4                             Key Length: 8
  Index Ref: const                          Index Ref: poster.a.PIC_ID
  Row Count: 14                             Row Count: 1
    Special: Using where; Using filesort      Special:                

Press e to explain, f for full query, o for optimized query
Query List (? for help) mysql01, 75+03:16:16, 774.20 QPS, 83 thd, 5.1.24-rc-log
select a.poster_id, a.pic_id, a.gmt_create, a.gmt_modified, a.url, a.notes,
a.type, a.indexed, a.user_id, a.user_nick, b.pic_path
from poster.poster_pic a inner join poster.picture b on
(a.pic_id = b.id) where a.poster_id = 3390 order by a.indexed

Press e to explain, f for full query, o for optimized query
Query List (? for help) mysql01, 75+03:16:16, 774.20 QPS, 83 thd, 5.1.24-rc-log
select a.poster_id, a.pic_id, a.gmt_create, a.gmt_modified, a.url, a.notes,
a.type, a.indexed, a.user_id, a.user_nick, b.pic_path
from poster.poster_pic a inner join poster.picture b on
(a.pic_id = b.id) where a.poster_id = 3390 order by a.indexed
Note:
select `poster`.`a`.`POSTER_ID` AS `poster_id`,`poster`.`a`.`PIC_ID` AS
`pic_id`,`poster`.`a`.`GMT_CREATE` AS `gmt_create`,
`poster`.`a`.`GMT_MODIFIED` AS `gmt_modified`,
`poster`.`a`.`URL` AS `url`,`poster`.`a`.`NOTES` AS `notes`,
`poster`.`a`.`TYPE` AS `type`,`poster`.`a`.`INDEXED` AS `indexed`,
`poster`.`a`.`USER_ID` AS `user_id`,`poster`.`a`.`user_nick` AS
`user_nick`,`poster`.`b`.`PIC_PATH` AS `pic_path`
from `poster`.`poster_pic` `a` join `poster`.`picture` `b`
where ((`poster`.`b`.`ID` = `poster`.`a`.`PIC_ID`) and
(`poster`.`a`.`POSTER_ID` = 3390))
order by `poster`.`a`.`INDEXED`

Press e to explain, f for full query, o for optimized query

那么innotop是从哪里取的数据呢?应该是通过information_schema.processlist来获得完整的sql语句,并且根据COMMAND来过滤掉空闲线程的。

mysql> desc information_schema.processlist;
+---------+-------------+------+-----+---------+-------+
| Field   | Type        | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| ID      | bigint(4)   | NO   |     | 0       |       |
| USER    | varchar(16) | NO   |     |         |       |
| HOST    | varchar(64) | NO   |     |         |       |
| DB      | varchar(64) | YES  |     | NULL    |       |
| COMMAND | varchar(16) | NO   |     |         |       |
| TIME    | bigint(7)   | NO   |     | 0       |       |
| STATE   | varchar(64) | YES  |     | NULL    |       |
| INFO    | longtext    | YES  |     | NULL    |       |
+---------+-------------+------+-----+---------+-------+
8 rows in set (0.00 sec)

用Perl的hash数组实现个性化监控

对于DBA来说,一个准确稳定的监控系统,不啻于一柄尚方宝剑。几十上百套系统,如果每天都靠人工来检查,工作量之大无法想象,而且人工也无法做到实时捕获错误。

但是这么多数据库系统,每个库承载的压力不一样,对于整个系统的重要度也不一样,负责的DBA也不可能是同一个人。如果都按同样的KPI同样的门限来做监控,则有些重要的系统可能无法准确的告警,有些不重要的系统却又会频繁误报。

比如系统的load,有些核心库由于采用了比较高端的硬件,即使一直在20~30左右都是正常的,而一些边缘的库则可能超过5就比较危险了,所以对于load,不同的库必须设置不同的检查门限。

如果要通过shell脚本来实现这个需求,可能需要写一堆的if或者case判断,一旦要做配置变更就十分的头疼。不过perl的hash数组可以很好的解决这个问题。

#!/usr/bin/perl -w
# creator: NinGoo
############################################################
use strict;

# 配置各主机各KPI的检查门限
# 这里配置了两个门限值,达到第一个值发IM提醒,达到第二个值发手机短信
#      server, load1 load2
my %cutoff=("db1", [ 30,  40 ],
       "db2", [ 20,  35 ],
       "db3", [ 10,  18 ]
           );

# 配置各个门限值的数组中的位置,这样在以后即使修改门限的位置,也不需要修改功能代码
# 将配置和功能分开,将极大的简化后续维护工作
my ($load1,$load2)=(0,1);
############################################################
# 检查load的函数
sub check_load{
  my($server)=@_; 
  my $load=get_server_load(); #获得数据库主机当前load,具体实现这里就不赘述了
  my $cutoff1=$cutoff{$server}[$load1];
  my $cutoff2=$cutoff{$server}[$load2];
 
  if ( $load > $cutoff2){
    my $mesg=$server." load more than ".$cutoff2.",now is ".$load."\n";
    send_mobile($server,$mesg);  # 大于load2,发送手机告警
  }
  elsif( $load > $cutoff1){
    my $mesg=$server." load more than ".$cutoff1.",now is ".$load."\n";
    send_wangwang($server,$mesg); # 大于load1,发送旺旺告警
  }
}
############################################################
#循环检查所有主机的load
foreach my $server(keys(%cutoff)){     
  print "---- ".$server." -----\n";
  check_load($server);                     
}

在send_mobile和send_wangwang实现告警发送的函数中,传入了$server参数,同样可以使用hash数组配置发送给不同的责任人,基本思想和这里没有大的差别,就不重复贴代码了。

编写Perl模块

这两天在用Perl编写一些监控脚本,其实写代码也是一件挺有意思的事情,就是挺废时间的。而且,由于语法不太熟,基本想到一个东西都要先Google一下看怎么实现。Perl的语法相对来说有点晦涩,主要是一些内置变量和函数处理得相当精巧,而过于精致的东西理解起来就有点费力。相对于Shell,Perl在很多方面更加强大高效,比如Hash(关联数组)就非常好用,如果要将不同主机发出来的告警信息发送到不同的责任人,在shell中要做非常多的if或者case判断,修改起来也非常费力,而使用关联数组,则可以轻松实现该功能,代码简洁,配置方便,一个字,爽。

Perl支持以模块对代码进行封装。著名的CPAN上就有非常多好用的Module,可以极大的减轻开发量。一个简单的模块:

package NinGoo;
require Exporter;
use strict;
use warnings;

our @ISA     = qw(Exporter);
our @EXPORT  = qw(fun_public); #要输出给外部调用的函数或者变量,以空格分隔
our @version = 1.0;
sub func_private{
  print "This is a private function";
}

sub func_public{
 print "Hello,world\n";
 func_private();
}

1;
__END__

模块的文件名一般以.pm做后缀,名字和package相同,也就是上面这个Module的名字为NinGoo.pm。然后在普通的.脚本中调用:

#!/usr/bin/perl -w
# creator: NinGoo
# function: test perl module

BEGIN {
    push (@INC,'/home/module');
}

use strict;
use NinGoo;

func_public();

O’Relly出版了一系列Perl的图书,比较著名的大骆驼《Programming Perl》在这里有一个在线中文版本的,可以看看。

Redhat上安装Perl DBD::mysql驱动

打算熟悉下perl,自然主要用于数据库管理和监控方面的,所以需要连接数据库。Perl连接数据库可以通过DBI模块和相应数据库的DBD驱动,上一篇记录了DBI模块的安装,这里记录下DBD::mysql驱动的安装。可以从CPAN下载相应DBD驱动

安装过程很简单,下载解压:

gzip -d DBD-mysql-4.006.tar.gz
tar xvf DBD-mysql-4.006.tar

然后进入DBD-mysql-4.006目录,执行:

perl Makefile.PL
make
make test
make install

检查已经安装的Perl模块:

# ./perl-module.pl
DBD::mysql
DBI
Perl

安装DBD::mysql需要mysql_config,包含在Headers and libraries安装包中(MySQL-devel-community-5.1.23-0.rhel4.i386.rpm),并且PATH环境变量中必须包含mysql_config所在的路径。否则在执行perl Makefile.PL生成makefile的时候会报错:

Can’t exec “mysql_config”: No such file or directory at Makefile.PL line 76.

Cannot find the file ‘mysql_config’! Your execution PATH doesn’t seem
not contain the path to mysql_config. Resorting to guessed values!
Can’t exec “mysql_config”: No such file or directory at Makefile.PL line 466.

通过调用DBI->available_drivers()函数可以查看当前系统中已经安装好的所有驱动。安装好DBI和DBD::mysql后,就可以访问MySQL数据库啦,首先在test库中创建一个table:

create table test(id int,name varchar(30))

然后执行下面的Perl脚本插入一条数据并且查询得到结果:

#!/usr/bin/perl
use DBI;
my $dbh = DBI->connect("DBI:mysql:database=test;host=localhost","NinGoo","password", {'RaiseError' => 1});
my $rows = $dbh->do("INSERT INTO test (id, name) VALUES (1, 'NinGoo')");
my $query = $dbh->prepare("SELECT name FROM test");
$query->execute();
while(my $rs = $query->fetchrow_hashref()) {
    print "$rs->{'name'}\n";
}