You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

394 lines
10 KiB

  1. #!@PERL@
  2. use strict;
  3. use warnings;
  4. use DBI;
  5. use Getopt::Long;
  6. $Getopt::Long::ignorecase=0;
  7. print "explain_log provided by http://www.mobile.de\n";
  8. print "=========== ================================\n";
  9. my $Param={};
  10. $Param->{host}='';
  11. $Param->{user}='';
  12. $Param->{password}='';
  13. $Param->{PrintError}=0;
  14. $Param->{socket}='';
  15. if (!GetOptions ('date|d:i' => \$Param->{ViewDate},
  16. 'host|h:s' => \$Param->{host},
  17. 'user|u:s' => \$Param->{user},
  18. 'password|p:s' => \$Param->{password},
  19. 'printerror|e:s' => \$Param->{PrintError},
  20. 'socket|s:s' => \$Param->{socket},
  21. )) {
  22. ShowOptions();
  23. }
  24. else {
  25. $Param->{UpdateCount} = 0;
  26. $Param->{SelectCount} = 0;
  27. $Param->{IdxUseCount} = 0;
  28. $Param->{LineCount} = 0;
  29. $Param->{Init} = 0;
  30. $Param->{Field} = 0;
  31. $Param->{Refresh} = 0;
  32. $Param->{QueryCount} = 0;
  33. $Param->{Statistics} =0;
  34. $Param->{Query} = undef;
  35. $Param->{ALL} = undef ;
  36. $Param->{Comment} = undef ;
  37. @{$Param->{Rows}} = (qw|possible_keys key type|);
  38. if ($Param->{ViewDate}) {
  39. $Param->{View} = 0;
  40. }
  41. else {
  42. $Param->{View} = 1;
  43. }
  44. #print "Date=$Param->{ViewDate}, host=$Param->{host}, user=$Param->{user}, password=$Param->{password}\n";
  45. $Param->{dbh}=DBI->connect("DBI:mysql:host=$Param->{host}".($Param->{socket}?";mysql_socket=$Param->{socket}":""),$Param->{user},$Param->{password},{PrintError=>0});
  46. if (DBI::err()) {
  47. print "Error: " . DBI::errstr() . "\n";
  48. }
  49. else {
  50. $Param->{Start} = time;
  51. while(<>) {
  52. $Param->{LineCount} ++ ;
  53. if ($Param->{ViewDate} ) {
  54. if (m/^(\d{6})\s+\d{1,2}:\d\d:\d\d\s.*$/) { # get date
  55. #print "# $1 #\n";
  56. if ($1 == $Param->{ViewDate}) {
  57. $Param->{View} = 1;
  58. }
  59. else {
  60. $Param->{View} = 0;
  61. }
  62. }
  63. }
  64. if ($Param->{View} ) {
  65. #print "->>>$_";
  66. 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)
  67. #print "C-$1--$2--$3------\n";
  68. RunQuery($Param);
  69. if (defined $3) {
  70. $Param->{CID}->{$2} = $3 ;
  71. #print "DB:$Param->{CID}->{$2} .. $2 .. $3 \n";
  72. }
  73. }
  74. 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)
  75. #print "\n <<<<<<<<<<<<<<<<<<----------------------------<<<<<<<<<<<<<<<< \n";
  76. #print "Connect \n";
  77. RunQuery($Param);
  78. }
  79. 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)
  80. #print "C-$1--$2--$3------\n";
  81. RunQuery($Param);
  82. if (defined $3) {
  83. $Param->{CID}->{$2} = $3 ;
  84. #print "DB:$Param->{CID}->{$2} .. $2 .. $3 \n";
  85. }
  86. }
  87. 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
  88. #print "Q-$1--$2--------\n";
  89. RunQuery($Param);
  90. delete $Param->{CID}->{$2} ;
  91. }
  92. 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
  93. #print "S1-$1--$2--$3------\n";
  94. RunQuery($Param);
  95. unless ($Param->{CID}->{$2}) {
  96. #print "Error: No Database for Handle: $2 found\n";
  97. }
  98. else {
  99. $Param->{DB}=$Param->{CID}->{$2};
  100. my $s = "$3";
  101. $s =~ s/from\s/from $Param->{DB}./i;
  102. $Param->{Query}="EXPLAIN $s";
  103. #$s =~ m/from\s+(\w+[.]\w+)/i;
  104. #$Param->{tab} =$1;
  105. #print "-- $Param->{tab} -- $s --\n";
  106. }
  107. }
  108. 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
  109. #print "S2--$1--$2--$3------\n";
  110. RunQuery($Param);
  111. unless ($Param->{CID}->{$2}) {
  112. #print "Error: No Database for Handle: $2 found\n";
  113. }
  114. else {
  115. $Param->{DB}=$Param->{CID}->{$2};
  116. my $ud = $3;
  117. $ud =~ m/^update\s+(\w+).+(where.+)$/i;
  118. $Param->{Query} ="EXPLAIN SELECT * FROM $1 $2";
  119. $Param->{Query} =~ s/from\s/from $Param->{DB}./i;
  120. #$Param->{Query} =~ m/from\s+(\w+[.]\w+)/i;
  121. #$Param->{tab} =$1;
  122. }
  123. }
  124. 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?
  125. $Param->{Statistics} ++;
  126. #print "Statistics--$1--$2--$3------\n";
  127. RunQuery($Param);
  128. }
  129. elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Query\s+(.+)$/i) { # get connection ID($2)
  130. $Param->{QueryCount} ++;
  131. #print "Query-NULL $3\n";
  132. RunQuery($Param);
  133. }
  134. elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Refresh\s+(.+)$/i) { # get connection ID($2)
  135. $Param->{Refresh} ++;
  136. #print "Refresh\n";
  137. RunQuery($Param);
  138. }
  139. elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Init\s+(.+)$/i) { # get connection ID($2)
  140. $Param->{Init} ++;
  141. #print "Init $3\n";
  142. RunQuery($Param);
  143. }
  144. elsif (m/^(\d{6}\s+\d{1,2}:\d\d:\d\d\s+|\s+)(\d+)\s+Field\s+(.+)$/i) { # get connection ID($2)
  145. $Param->{Field} ++;
  146. #print "Field $3\n";
  147. RunQuery($Param);
  148. }
  149. elsif (m/^\s+(.+)$/ ) { # command could be some lines ...
  150. #print "multi-lined ($1)\n";
  151. my ($A)=$1;
  152. chomp $A;
  153. $Param->{Query} .= " $1";
  154. #print "multi-lined ($1)<<$Param->{Query}>>\n";
  155. }
  156. }
  157. }
  158. $Param->{dbh}->disconnect();
  159. if (1 == 0) {
  160. print "\nunclosed handles----------------------------------------\n";
  161. my $count=0;
  162. foreach (sort keys %{$Param->{CID}}) {
  163. print "$count | $_ : $Param->{CID}->{$_} \n";
  164. $count ++;
  165. }
  166. }
  167. print "\nIndex usage ------------------------------------\n";
  168. foreach my $t (sort keys %{$Param->{Data}}) {
  169. print "\nTable\t$t: ---\n";
  170. foreach my $k (sort keys %{$Param->{Data}->{$t}}) {
  171. print " count\t$k:\n";
  172. my %h = %{$Param->{Data}->{$t}->{$k}};
  173. foreach (sort {$h{$a} <=> $h{$b}} keys %h) {
  174. print " $Param->{Data}->{$t}->{$k}->{$_}\t$_\n";
  175. }
  176. }
  177. }
  178. $Param->{AllCount}=0;
  179. print "\nQueries causing table scans -------------------\n\n";
  180. foreach (@{$Param->{ALL}}) {
  181. $Param->{AllCount} ++;
  182. print "$_\n";
  183. }
  184. print "Sum: $Param->{AllCount} table scans\n";
  185. print "\nSummary ---------------------------------------\n\n";
  186. print "Select: \t$Param->{SelectCount} queries\n";
  187. print "Update: \t$Param->{UpdateCount} queries\n";
  188. print "\n";
  189. print "Init: \t$Param->{Init} times\n";
  190. print "Field: \t$Param->{Field} times\n";
  191. print "Refresh: \t$Param->{Refresh} times\n";
  192. print "Query: \t$Param->{QueryCount} times\n";
  193. print "Statistics:\t$Param->{Statistics} times\n";
  194. print "\n";
  195. print "Logfile: \t$Param->{LineCount} lines\n";
  196. print "Started: \t".localtime($Param->{Start})."\n";
  197. print "Finished: \t".localtime(time)."\n";
  198. }
  199. }
  200. ###########################################################################
  201. #
  202. #
  203. #
  204. sub RunQuery {
  205. my $Param = shift ;
  206. if (defined $Param->{Query}) {
  207. if (defined $Param->{DB} ) {
  208. $Param->{Query} =~ m/from\s+(\w+[.]\w+|\w+)/i;
  209. $Param->{tab} =$1;
  210. #print "||$Param->{tab} -- $Param->{Query}\n";
  211. my $sth=$Param->{dbh}->prepare("USE $Param->{DB}");
  212. if (DBI::err()) {
  213. if ($Param->{PrintError}) {print "Error: ".DBI::errstr()."\n";}
  214. }
  215. else {
  216. $sth->execute();
  217. if (DBI::err()) {
  218. if ($Param->{PrintError}) {print "Error: ".DBI::errstr()."\n";}
  219. }
  220. else {
  221. $sth->finish();
  222. $sth=$Param->{dbh}->prepare($Param->{Query});
  223. if (DBI::err()) {
  224. if ($Param->{PrintError}) {print "Error: ".DBI::errstr()."\n";}
  225. }
  226. else {
  227. #print "$Param->{Query}\n";
  228. $sth->execute();
  229. if (DBI::err()) {
  230. if ($Param->{PrintError}) {print "[$Param->{LineCount}]<<$Param->{Query}>>\n";}
  231. if ($Param->{PrintError}) {print "Error: ".DBI::errstr()."\n";}
  232. }
  233. else {
  234. my $row = undef;
  235. while ($row = $sth->fetchrow_hashref()) {
  236. $Param->{SelectCount} ++;
  237. if (defined $row->{Comment}) {
  238. push (@{$Param->{Comment}}, "$row->{Comment}; $_; $Param->{DB}; $Param->{Query}");
  239. }
  240. foreach (@{$Param->{Rows}}) {
  241. if (defined $row->{$_}) {
  242. #if (($_ eq 'type' ) and ($row->{$_} eq 'ALL')) {
  243. if ($row->{type} eq 'ALL') {
  244. push (@{$Param->{ALL}}, "$Param->{Query}");
  245. #print ">> $row->{$_} $_ $Param->{DB} $Param->{Query}\n";
  246. }
  247. $Param->{IdxUseCount} ++;
  248. $Param->{Data}->{$Param->{tab}}->{$_}->{$row->{$_}} ++;
  249. }
  250. }
  251. }
  252. }
  253. }
  254. }
  255. }
  256. $sth->finish();
  257. }
  258. $Param->{Query} = undef ;
  259. }
  260. }
  261. ###########################################################################
  262. #
  263. #
  264. #
  265. sub ShowOptions {
  266. print <<EOF;
  267. Usage: $0 [OPTIONS] < LOGFILE
  268. --date=YYMMDD select only entrys of date
  269. -d=YYMMDD
  270. --host=HOSTNAME db-host to ask
  271. -h=HOSTNAME
  272. --user=USERNAME db-user
  273. -u=USERNAME
  274. --password=PASSWORD password of db-user
  275. -p=PASSWORD
  276. --socket=SOCKET mysqld socket file to connect
  277. -s=SOCKET
  278. --printerror=1 enable error output
  279. -e 1
  280. Read logfile from STDIN an try to EXPLAIN all SELECT statements. All UPDATE statements are rewritten to an EXPLAIN SELECT statement. The results of the EXPLAIN statement are collected and counted. All results with type=ALL are collected in an separete list. Results are printed to STDOUT.
  281. EOF
  282. }
  283. 1;
  284. __END__
  285. =pod
  286. =head1 NAME
  287. mysql_explain_log
  288. Feed a mysqld general logfile (created with mysqld --log) back into mysql
  289. and collect statistics about index usage with EXPLAIN.
  290. =head1 DISCUSSION
  291. To optimize your indices, you have to know which ones are actually
  292. used and what kind of queries are causing table scans. Especially
  293. if you are generating your queries dynamically and you have a huge
  294. amount of queries going on, this isn't easy.
  295. Use this tool to take a look at the effects of your real life queries.
  296. Then add indices to avoid table scans and remove those which aren't used.
  297. =head1 USAGE
  298. mysql_explain_log [--date=YYMMDD] --host=dbhost] [--user=dbuser] [--password=dbpw] [--socket=/path/to/socket] < logfile
  299. --date=YYMMDD select only entrys of date
  300. -d=YYMMDD
  301. --host=HOSTNAME db-host to ask
  302. -h=HOSTNAME
  303. --user=USERNAME db-user
  304. -u=USERNAME
  305. --password=PASSWORD password of db-user
  306. -p=PASSWORD
  307. --socket=SOCKET change path to the socket
  308. -s=SOCKET
  309. --printerror=1 enable error output
  310. -e 1
  311. =head1 EXAMPLE
  312. mysql_explain_log --host=localhost --user=foo --password=bar < /var/lib/mysql/mobile.log
  313. =head1 AUTHORS
  314. Stefan Nitz
  315. Jan Willamowius <jan@willamowius.de>, http://www.willamowius.de
  316. Dennis Haney <davh@davh.dk> (Added socket support)
  317. =head1 SEE ALSO
  318. mysql documentation
  319. =cut