home *** CD-ROM | disk | FTP | other *** search
- #!@PERL@
- # Copyright (C) 2001-2003, 2006 MySQL AB
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; version 2 of the License.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
- use strict;
- use warnings;
- use DBI;
-
- use Getopt::Long;
- $Getopt::Long::ignorecase=0;
-
- my $Param={};
-
- $Param->{host}='';
- $Param->{user}='';
- $Param->{password}='';
- $Param->{PrintError}=0;
- $Param->{socket}='';
-
- my $help;
-
- if (!GetOptions (
- 'date|d:i' => \$Param->{ViewDate},
- 'host|h:s' => \$Param->{host},
- 'user|u:s' => \$Param->{user},
- 'password|p:s' => \$Param->{password},
- 'printerror|e:s' => \$Param->{PrintError},
- 'socket|s:s' => \$Param->{socket},
- 'help|h' => \$help,
- )) {
- ShowOptions();
- exit(0);
- }
- if (defined ($help)) {
- ShowOptions();
- exit(0);
- }
-
- print "explain_log provided by http://www.mobile.de\n";
- print "=========== ================================\n";
-
- $Param->{UpdateCount} = 0;
- $Param->{SelectCount} = 0;
- $Param->{IdxUseCount} = 0;
- $Param->{LineCount} = 0;
-
- $Param->{Init} = 0;
- $Param->{Field} = 0;
- $Param->{Refresh} = 0;
- $Param->{QueryCount} = 0;
- $Param->{Statistics} =0;
-
- $Param->{Query} = undef;
- $Param->{ALL} = undef ;
- $Param->{Comment} = undef ;
-
- @{$Param->{Rows}} = (qw|possible_keys key type|);
-
- if ($Param->{ViewDate}) {
- $Param->{View} = 0;
- }
- else {
- $Param->{View} = 1;
- }
-
- #print "Date=$Param->{ViewDate}, host=$Param->{host}, user=$Param->{user}, password=$Param->{password}\n";
-
- $Param->{dbh}=DBI->connect("DBI:mysql:host=$Param->{host}".($Param->{socket}?";mysql_socket=$Param->{socket}":""),$Param->{user},$Param->{password},{PrintError=>0});
- if (DBI::err()) {
- print "Error: " . DBI::errstr() . "\n";
- }
- else {
- $Param->{Start} = time;
- while(<>) {
- $Param->{LineCount} ++ ;
-
- if ($Param->{ViewDate} ) {
- if (m/^(\d{6})\s+\d{1,2}:\d\d:\d\d\s.*$/) { # get date
- #print "# $1 #\n";
- if ($1 == $Param->{ViewDate}) {
- $Param->{View} = 1;
- }
- else {
- $Param->{View} = 0;
- }
- }
- }
- if ($Param->{View} ) {
- #print "->>>$_";
-
- if (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Connect.+\s+on\s+(.*)$/i) { # get connection ID($2) and database($3)
- #print "C-$1--$2--$3------\n";
- RunQuery($Param);
- if (defined $3) {
- $Param->{CID}->{$2} = $3 ;
- #print "DB:$Param->{CID}->{$2} .. $2 .. $3 \n";
- }
- }
-
- elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Connect.+$/i) { # get connection ID($2) and database($3)
- #print "\n <<<<<<<<<<<<<<<<<<----------------------------<<<<<<<<<<<<<<<< \n";
- #print "Connect \n";
- RunQuery($Param);
- }
- elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Change user .*\s+on\s+(.*)$/i) { # get connection ID($2) and database($3)
- #print "C-$1--$2--$3------\n";
- RunQuery($Param);
- if (defined $3) {
- $Param->{CID}->{$2} = $3 ;
- #print "DB:$Param->{CID}->{$2} .. $2 .. $3 \n";
- }
- }
-
- elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Quit\s+$/i) { # remove connection ID($2) and querystring
- #print "Q-$1--$2--------\n";
- RunQuery($Param);
- delete $Param->{CID}->{$2} ;
- }
-
- elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Query\s+(select.+)$/i) { # get connection ID($2) and querystring
- #print "S1-$1--$2--$3------\n";
- RunQuery($Param);
- unless ($Param->{CID}->{$2}) {
- #print "Error: No Database for Handle: $2 found\n";
- }
- else {
- $Param->{DB}=$Param->{CID}->{$2};
-
- my $s = "$3";
- $s =~ s/from\s/from $Param->{DB}./i;
- $Param->{Query}="EXPLAIN $s";
-
- #$s =~ m/from\s+(\w+[.]\w+)/i;
- #$Param->{tab} =$1;
- #print "-- $Param->{tab} -- $s --\n";
- }
- }
-
- elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Query\s+(update.+)$/i) { # get connection ID($2) and querystring
- #print "S2--$1--$2--$3------\n";
- RunQuery($Param);
- unless ($Param->{CID}->{$2}) {
- #print "Error: No Database for Handle: $2 found\n";
- }
- else {
- $Param->{DB}=$Param->{CID}->{$2};
-
- my $ud = $3;
- $ud =~ m/^update\s+(\w+).+(where.+)$/i;
- $Param->{Query} ="EXPLAIN SELECT * FROM $1 $2";
- $Param->{Query} =~ s/from\s/from $Param->{DB}./i;
-
- #$Param->{Query} =~ m/from\s+(\w+[.]\w+)/i;
- #$Param->{tab} =$1;
- }
- }
-
- elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Statistics\s+(.*)$/i) { # get connection ID($2) and info?
- $Param->{Statistics} ++;
- #print "Statistics--$1--$2--$3------\n";
- RunQuery($Param);
- }
- elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Query\s+(.+)$/i) { # get connection ID($2)
- $Param->{QueryCount} ++;
- #print "Query-NULL $3\n";
- RunQuery($Param);
- }
- elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Refresh\s+(.+)$/i) { # get connection ID($2)
- $Param->{Refresh} ++;
- #print "Refresh\n";
- RunQuery($Param);
- }
- elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Init\s+(.+)$/i) { # get connection ID($2)
- $Param->{Init} ++;
- #print "Init $3\n";
- RunQuery($Param);
- }
- elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Field\s+(.+)$/i) { # get connection ID($2)
- $Param->{Field} ++;
- #print "Field $3\n";
- RunQuery($Param);
- }
-
- elsif (m/^\s+(.+)$/ ) { # command could be some lines ...
- #print "multi-lined ($1)\n";
- my ($A)=$1;
- chomp $A;
- $Param->{Query} .= " $1";
- #print "multi-lined ($1)<<$Param->{Query}>>\n";
- }
-
-
- }
-
- }
-
- $Param->{dbh}->disconnect();
-
- if (1 == 0) {
- print "\nunclosed handles----------------------------------------\n";
- my $count=0;
- foreach (sort keys %{$Param->{CID}}) {
- print "$count | $_ : $Param->{CID}->{$_} \n";
- $count ++;
- }
- }
-
- print "\nIndex usage ------------------------------------\n";
- foreach my $t (sort keys %{$Param->{Data}}) {
- print "\nTable\t$t: ---\n";
- foreach my $k (sort keys %{$Param->{Data}->{$t}}) {
- print " count\t$k:\n";
- my %h = %{$Param->{Data}->{$t}->{$k}};
- foreach (sort {$h{$a} <=> $h{$b}} keys %h) {
- print " $Param->{Data}->{$t}->{$k}->{$_}\t$_\n";
- }
- }
- }
-
- $Param->{AllCount}=0;
- print "\nQueries causing table scans -------------------\n\n";
- foreach (@{$Param->{ALL}}) {
- $Param->{AllCount} ++;
- print "$_\n";
- }
- print "Sum: $Param->{AllCount} table scans\n";
-
- print "\nSummary ---------------------------------------\n\n";
- print "Select: \t$Param->{SelectCount} queries\n";
- print "Update: \t$Param->{UpdateCount} queries\n";
- print "\n";
-
- print "Init: \t$Param->{Init} times\n";
- print "Field: \t$Param->{Field} times\n";
- print "Refresh: \t$Param->{Refresh} times\n";
- print "Query: \t$Param->{QueryCount} times\n";
- print "Statistics:\t$Param->{Statistics} times\n";
- print "\n";
-
- print "Logfile: \t$Param->{LineCount} lines\n";
- print "Started: \t".localtime($Param->{Start})."\n";
- print "Finished: \t".localtime(time)."\n";
-
- }
-
-
- ###########################################################################
- #
- #
- #
- sub RunQuery {
- my $Param = shift ;
-
- if (defined $Param->{Query}) {
- if (defined $Param->{DB} ) {
-
- $Param->{Query} =~ m/from\s+(\w+[.]\w+|\w+)/i;
- $Param->{tab} =$1;
- #print "||$Param->{tab} -- $Param->{Query}\n";
-
- my $sth=$Param->{dbh}->prepare("USE $Param->{DB}");
- if (DBI::err()) {
- if ($Param->{PrintError}) {print "Error: ".DBI::errstr()."\n";}
- }
- else {
- $sth->execute();
- if (DBI::err()) {
- if ($Param->{PrintError}) {print "Error: ".DBI::errstr()."\n";}
- }
- else {
- $sth->finish();
-
- $sth=$Param->{dbh}->prepare($Param->{Query});
- if (DBI::err()) {
- if ($Param->{PrintError}) {print "Error: ".DBI::errstr()."\n";}
- }
- else {
- #print "$Param->{Query}\n";
- $sth->execute();
- if (DBI::err()) {
- if ($Param->{PrintError}) {print "[$Param->{LineCount}]<<$Param->{Query}>>\n";}
- if ($Param->{PrintError}) {print "Error: ".DBI::errstr()."\n";}
- }
- else {
- my $row = undef;
- while ($row = $sth->fetchrow_hashref()) {
- $Param->{SelectCount} ++;
-
- if (defined $row->{Comment}) {
- push (@{$Param->{Comment}}, "$row->{Comment}; $_; $Param->{DB}; $Param->{Query}");
- }
- foreach (@{$Param->{Rows}}) {
- if (defined $row->{$_}) {
- #if (($_ eq 'type' ) and ($row->{$_} eq 'ALL')) {
- if ($row->{type} eq 'ALL') {
- push (@{$Param->{ALL}}, "$Param->{Query}");
- #print ">> $row->{$_} $_ $Param->{DB} $Param->{Query}\n";
- }
- $Param->{IdxUseCount} ++;
- $Param->{Data}->{$Param->{tab}}->{$_}->{$row->{$_}} ++;
- }
- }
- }
- }
- }
- }
- }
- $sth->finish();
- }
- $Param->{Query} = undef ;
- }
- }
-
- ###########################################################################
- #
- #
- #
- sub ShowOptions {
- print <<EOF;
- Usage: $0 [OPTIONS] < LOGFILE
-
- --help, -h
- Display this help message
- --date=YYMMDD, -d=YYMMDD
- Select entries from the log only for the given date
- --host=HOSTNAME, -h=HOSTNAME
- Connect to the MySQL server on the given host
- --user=USERNAME, -u=USERNAME
- The MySQL username to use when connecting to the server
- --password=PASSWORD, -p=PASSWORD
- The password to use when connecting to the server
- --socket=SOCKET, -s=SOCKET
- The socket file to use when connecting to the server
- --printerror=1, -e 1
- Enable error output
-
- mysql_explain_log reads its standard input for query log contents. It
- uses EXPLAIN to analyze SELECT statements found in the input. UPDATE
- statements are rewritten to SELECT statements and also analyzed with
- EXPLAIN. mysql_explain_log then displays a summary of its results.
- Results are printed to the standard output.
- EOF
- }
-
- 1;
-
- __END__
-
- =pod
-
- =head1 NAME
-
- mysql_explain_log
-
- Feed a mysqld general query logfile (created with mysqld --log) back
- into mysql and collect statistics about index usage with EXPLAIN.
-
- =head1 DISCUSSION
-
- To optimize your indexes, you must know which ones are actually used
- and what kind of queries are causing table scans. This may not be easy,
- especially if you are generating your queries dynamically and you have
- a huge number of queries being executed.
-
- Use this tool to take a look at the effects of your real life queries.
- Then add indexes to avoid table scans and remove indexes that aren't used.
-
- =head1 USAGE
-
- mysql_explain_log [--date=YYMMDD] --host=dbhost] [--user=dbuser] [--password=dbpw] [--socket=/path/to/socket] [--printerror=1] < logfile
-
- --help, -h
- Display this help message
- --date=YYMMDD, -d=YYMMDD
- Select entries from the log only for the given date
- --host=HOSTNAME, -h=HOSTNAME
- Connect to the MySQL server on the given host
- --user=USERNAME, -u=USERNAME
- The MySQL username to use when connecting to the server
- --password=PASSWORD, -p=PASSWORD
- The password to use when connecting to the server
- --socket=SOCKET, -s=SOCKET
- The socket file to use when connecting to the server
- --printerror=1, -e 1
- Enable error output
-
- =head1 EXAMPLE
-
- mysql_explain_log --host=localhost --user=foo --password=bar < /var/lib/mysql/mobile.log
-
- =head1 AUTHORS
-
- Stefan Nitz
- Jan Willamowius <jan@willamowius.de>, http://www.willamowius.de
- Dennis Haney <davh@davh.dk> (Added socket support)
-
- =head1 SEE ALSO
-
- mysql documentation
-
- =cut
-