This commit is contained in:
Jean-Marie RENOUARD 2016-03-23 12:20:28 +01:00
commit 8271d9d4c2
3 changed files with 276 additions and 47 deletions

View file

@ -66,6 +66,10 @@
## MySQLTuner system checks ## MySQLTuner system checks
* 32-bit w/>2GB RAM check * 32-bit w/>2GB RAM check
* Check number of opened ports (warning if more than 9 ports opened)
* Check 80, 8080, 443 and 8443 ports if warning are raised if there are opened
* Check if some banned ports are not opened (option --bannedports separated by comma)
* Check if non kernel and user process except mysqld are not using more than 15% of total physical memory)
## MySQLTuner Server version checks ## MySQLTuner Server version checks
* EOL MySQL version check * EOL MySQL version check
@ -97,12 +101,18 @@
## MySQLTuner database information ## MySQLTuner database information
* Per database information * Per database information
* Tables number
* Rows number * Rows number
* Total size * Total size
* Data size * Data size
* Percentage of data size * Percentage of data size
* Index size * Index size
* Percentage of index size * Percentage of index size
* Collation number
* Check that there is only one collation for all table in a database
* Check that there is only one collation for ll table columns in a database
* Check that there is only one storage engine per user database
## MySQLTuner index information ## MySQLTuner index information
@ -238,17 +248,7 @@
* tokudb_cleaner_iterations * tokudb_cleaner_iterations
* tokudb_fanout * tokudb_fanout
## MySQLTuner MariaDB thread pool information ## MySQLTuner Thread pool information
* thread_pool_size * thread_pool_size between 16 to 36 for Innodb usage
* thread_pool_stall_limit * thread_pool_size between 4 to 8 for MyIsam usage
* thread_pool_max_threads
* thread_pool_idle_timeout
* thread_pool_oversubscribe
* threadpool_threads
* threadpool_idle_threads
* threadpool_threads / thread_pool_size
* threadpool_idle_threads / thread_pool_size

View file

@ -21,8 +21,8 @@ sub AUTOLOAD {
my $mech = WWW::Mechanize->new(); my $mech = WWW::Mechanize->new();
$mech->agent('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0'); $mech->agent('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0');
#$mech->proxy( ['http'], 'http://10.236.240.71:3128' ); #$mech->proxy( ['http'], 'http://XXX.XXX.XXX.XXX:3128' );
#$mech->proxy( ['https'], 'http://10.236.240.71:3128' ); #$mech->proxy( ['https'], 'http://XXX.XXX.XXX.XXX:3128' );
$mech->env_proxy; $mech->env_proxy;

View file

@ -1,5 +1,5 @@
#!/usr/bin/env perl #!/usr/bin/env perl
# mysqltuner.pl - Version 1.6.6 # mysqltuner.pl - Version 1.6.7
# High Performance MySQL Tuning Script # High Performance MySQL Tuning Script
# Copyright (C) 2006-2015 Major Hayden - major@mhtx.net # Copyright (C) 2006-2015 Major Hayden - major@mhtx.net
# #
@ -51,7 +51,7 @@ use Data::Dumper;
$Data::Dumper::Pair = " : "; $Data::Dumper::Pair = " : ";
# Set up a few variables for use in the script # Set up a few variables for use in the script
my $tunerversion = "1.6.6"; my $tunerversion = "1.6.7";
my ( @adjvars, @generalrec ); my ( @adjvars, @generalrec );
# Set defaults # Set defaults
@ -73,6 +73,7 @@ my %opt = (
"checkversion" => 0, "checkversion" => 0,
"buffers" => 0, "buffers" => 0,
"passwordfile" => 0, "passwordfile" => 0,
"bannedports" => '',
"outputfile" => 0, "outputfile" => 0,
"dbstat" => 0, "dbstat" => 0,
"idxstat" => 0, "idxstat" => 0,
@ -92,6 +93,7 @@ GetOptions(
'mysqlcmd=s', 'help', 'buffers', 'skippassword', 'mysqlcmd=s', 'help', 'buffers', 'skippassword',
'passwordfile=s', 'outputfile=s', 'silent', 'dbstat', 'json', 'passwordfile=s', 'outputfile=s', 'silent', 'dbstat', 'json',
'idxstat', 'noask', 'template=s', 'reportfile=s', 'cvefile=s', 'idxstat', 'noask', 'template=s', 'reportfile=s', 'cvefile=s',
'bannedports=s',
); );
if ( defined $opt{'help'} && $opt{'help'} == 1 ) { usage(); } if ( defined $opt{'help'} && $opt{'help'} == 1 ) { usage(); }
@ -134,6 +136,7 @@ sub usage {
. " --debug Print debug information\n" . " --debug Print debug information\n"
. " --dbstat Print database information\n" . " --dbstat Print database information\n"
. " --idxstat Print index information\n" . " --idxstat Print index information\n"
. " --bannedports ports banned separated by comma(,)\n"
. " --cvefile CVE File for vulnerability checks\n" . " --cvefile CVE File for vulnerability checks\n"
. " --nocolor Don't print output in color\n" . " --nocolor Don't print output in color\n"
. " --json Print result as JSON string\n" . " --json Print result as JSON string\n"
@ -158,6 +161,10 @@ $basic_password_files = "/usr/share/mysqltuner/basic_passwords.txt"
$opt{cvefile} = "/usr/share/mysqltuner/vulnerabilities.csv" $opt{cvefile} = "/usr/share/mysqltuner/vulnerabilities.csv"
unless ( defined $opt{cvefile} and -f "$opt{cvefile}"); unless ( defined $opt{cvefile} and -f "$opt{cvefile}");
$opt{cvefile} ='' unless -f "$opt{cvefile}"; $opt{cvefile} ='' unless -f "$opt{cvefile}";
$opt{cvefile} ='./vulnerabilities.csv' if -f './vulnerabilities.csv';
$opt{'bannedports'}='' unless defined($opt{'bannedports'});
my @banned_ports=split ',', $opt{'bannedports'};
# #
my $outputfile = undef; my $outputfile = undef;
@ -505,7 +512,21 @@ sub mysql_setup {
$doremote = 1; $doremote = 1;
} }
} }
# Did we already get a username without password on the command line?
if ( $opt{user} ne 0 and $opt{pass} eq 0 ) {
$mysqllogin = "-u $opt{user} " . $remotestring;
my $loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1`;
if ( $loginstatus =~ /mysqld is alive/ ) {
goodprint
"Logged in using credentials passed on the command line";
return 1;
}
else {
badprint
"Attempted to use login credentials, but they were invalid";
exit 1;
}
}
# Did we already get a username and password passed on the command line? # Did we already get a username and password passed on the command line?
if ( $opt{user} ne 0 and $opt{pass} ne 0 ) { if ( $opt{user} ne 0 and $opt{pass} ne 0 ) {
$mysqllogin = "-u $opt{user} -p\"$opt{pass}\"" . $remotestring; $mysqllogin = "-u $opt{user} -p\"$opt{pass}\"" . $remotestring;
@ -682,6 +703,15 @@ sub select_array {
my $req = shift; my $req = shift;
debugprint "PERFORM: $req "; debugprint "PERFORM: $req ";
my @result = `$mysqlcmd $mysqllogin -Bse "$req" 2>>/dev/null`; my @result = `$mysqlcmd $mysqllogin -Bse "$req" 2>>/dev/null`;
if ($? != 0) {
badprint "failed to execute: $req";
badprint "FAIL Execute SQL / return code: $?";
debugprint "CMD : $mysqlcmd";
debugprint "OPTIONS: $mysqllogin";
debugprint `$mysqlcmd $mysqllogin -Bse "$req" 2>&1`;
exit $?;
}
debugprint "select_array: return code : $?";
chomp(@result); chomp(@result);
return @result; return @result;
} }
@ -691,6 +721,15 @@ sub select_one {
my $req = shift; my $req = shift;
debugprint "PERFORM: $req "; debugprint "PERFORM: $req ";
my $result = `$mysqlcmd $mysqllogin -Bse "$req" 2>>/dev/null`; my $result = `$mysqlcmd $mysqllogin -Bse "$req" 2>>/dev/null`;
if ($? != 0) {
badprint "failed to execute: $req";
badprint "FAIL Execute SQL / return code: $?";
debugprint "CMD : $mysqlcmd";
debugprint "OPTIONS: $mysqllogin";
debugprint `$mysqlcmd $mysqllogin -Bse "$req" 2>&1`;
exit $?;
}
debugprint "select_array: return code : $?";
chomp($result); chomp($result);
return $result; return $result;
} }
@ -747,6 +786,10 @@ sub get_all_vars {
$myvar{'have_innodb'} = "NO"; $myvar{'have_innodb'} = "NO";
} }
$myvar{'have_threadpool'} = "NO";
if ( defined ( $myvar{'thread_pool_size'} ) and $myvar{'thread_pool_size'} > 0 ) {
$myvar{'have_threadpool'} = "YES";
}
# have_* for engines is deprecated and will be removed in MySQL 5.6; # have_* for engines is deprecated and will be removed in MySQL 5.6;
# check SHOW ENGINES and set corresponding old style variables. # check SHOW ENGINES and set corresponding old style variables.
# Also works around MySQL bug #59393 wrt. skip-innodb # Also works around MySQL bug #59393 wrt. skip-innodb
@ -787,14 +830,25 @@ sub get_all_vars {
} }
} }
sub get_basic_passwords { sub remove_cr {
map { s/\n$//g; } @_;
}
sub remove_empty {
grep { $_ ne '' } @_;
}
sub get_file_contents {
my $file = shift; my $file = shift;
open( FH, "< $file" ) or die "Can't open $file for read: $!"; open( FH, "< $file" ) or die "Can't open $file for read: $!";
my @lines = <FH>; my @lines = <FH>;
close FH or die "Cannot close $file: $!"; close FH or die "Cannot close $file: $!";
remove_cr \@lines;
return @lines; return @lines;
} }
sub get_basic_passwords {
return get_file_contents(shift);
}
sub cve_recommendations { sub cve_recommendations {
prettyprint prettyprint
"\n-------- CVE Security Recommendations ---------------------------------------"; "\n-------- CVE Security Recommendations ---------------------------------------";
@ -825,6 +879,110 @@ sub cve_recommendations {
} }
sub get_opened_ports {
my @opened_ports=`netstat -ltn`;
map {
s/.*:(\d+)\s.*$/$1/;
s/\D//g;
} @opened_ports;
@opened_ports = sort {$a <=> $b} grep { !/^$/ } @opened_ports;
debugprint Dumper \@opened_ports;
return @opened_ports;
}
sub is_open_port {
my $port=shift;
if ( grep { /^$port$/ } get_opened_ports ) {
return 1;
}
return 0;
}
sub get_process_memory {
my $pid=shift;
return 0 unless -f "/proc/$pid/status";
my @pdata= grep { /RSS:/ } get_file_contents "/proc/$pid/status";
map {
s/.*RSS:\s*(\d+)\s*kB\s*$/$1*1024/ge
} @pdata;
return $pdata[0];
}
sub get_other_process_memory {
my @procs=`ps -eo pid,cmd`;
map { s/.*mysqld.*//; s/.*\[.*\].*//; s/^\s+$//g; s/.*PID.*CMD.*//; s/.*systemd.*//;} @procs;
map {s/\s*?(\d+)\s*.*/$1/g;} @procs;
remove_cr @procs;
@procs=remove_empty @procs;
my $totalMemOther=0;
map {
$totalMemOther+=get_process_memory($_);
} @procs;
return $totalMemOther;
}
sub get_os_release {
return "Unknown OS release" unless -f "/etc/system-release";
my @info_release=get_file_contents "/etc/system-release";
remove_cr @info_release;
return $info_release[0];
}
sub system_recommendations {
prettyprint "\n-------- System Linux Recommendations ---------------------------------------";
my $os = `uname`;
unless ($os =~ /Linux/i) {
infoprint "Skipped due to non Linux server";
return;
}
prettyprint "Look for related Linux system recommandations";
#prettyprint '-'x78;
infoprint get_os_release;
my $omem=get_other_process_memory;
infoprint "User process except mysqld used ". hr_bytes_rnd($omem) . " RAM.";
if ( (0.15*$physical_memory) < $omem) {
badprint "Other user process except mysqld used more than 15% of total physical memory ". percentage($omem, $physical_memory). "% (".hr_bytes_rnd($omem). " / ".hr_bytes_rnd($physical_memory).")";
push( @generalrec, "Consider stopping or dedicate server for additionnal process other than mysqld." );
push( @adjvars, "DON'T APPLY SETTINGS BECAUSE THERE IS TOO MANY PROCESSES RUNNING ON THIS SERVER. OOM KILL CAN OCCURS !" );
} else {
}
#if ($omem >
#exit 0;
my @opened_ports=get_opened_ports;
infoprint "There is ". scalar @opened_ports. " listening port(s) on this server.";
if (scalar(@opened_ports) > 10) {
badprint "There is too many listening ports: ". scalar(@opened_ports). " > 10";
push( @generalrec, "Consider dedicating a server for your database installation with less services running on !" );
} else {
goodprint "There is less than 10 opened ports on this server.";
}
if ( is_open_port(80) or is_open_port(443) ) {
badprint "There is Apache like server running on 80 or 443 port.";
push( @generalrec, "Consider dedicating a server for Web server in production !" );
} else {
goodprint "No Web server runing on 80 and 444 port.";
}
if ( is_open_port(8080) or is_open_port(8443) ) {
badprint "There is Application server running on 8080 or 8443 port.";
push( @generalrec, "Consider dedicating a server for Application server in production !" );
} else {
goodprint "No Application server runing on 8080 or 8443 port.";
}
foreach my $banport (@banned_ports) {
if ( is_open_port($banport) ) {
badprint "Banned port: $banport is opened..";
push( @generalrec, "Port $banport is opened. Consider stopping program handling this port." );
} else {
goodprint "$banport is not opened.";
}
}
}
sub security_recommendations { sub security_recommendations {
prettyprint prettyprint
"\n-------- Security Recommendations -------------------------------------------"; "\n-------- Security Recommendations -------------------------------------------";
@ -838,10 +996,12 @@ sub security_recommendations {
$PASS_COLUMN_NAME='authentication_string'; $PASS_COLUMN_NAME='authentication_string';
} }
debugprint "Password column = $PASS_COLUMN_NAME"; debugprint "Password column = $PASS_COLUMN_NAME";
#exit(0);
# Looking for Anonymous users # Looking for Anonymous users
my @mysqlstatlist = select_array my @mysqlstatlist = select_array
"SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE TRIM(USER) = '' OR USER IS NULL"; "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE TRIM(USER) = '' OR USER IS NULL";
debugprint Dumper \@mysqlstatlist;
#exit 0;
if (@mysqlstatlist) { if (@mysqlstatlist) {
foreach my $line ( sort @mysqlstatlist ) { foreach my $line ( sort @mysqlstatlist ) {
chomp($line); chomp($line);
@ -1098,6 +1258,7 @@ sub check_architecture {
} }
} }
$result{'OS'}{'Architecture'} = "$arch bits"; $result{'OS'}{'Architecture'} = "$arch bits";
} }
# Start up a ton of storage engine counts/statistics # Start up a ton of storage engine counts/statistics
@ -1174,7 +1335,6 @@ sub check_storage_engines {
$result{'Databases'}{'List'} = [@dblist]; $result{'Databases'}{'List'} = [@dblist];
infoprint "Status: $engines"; infoprint "Status: $engines";
if ( mysql_version_ge( 5, 1, 5 ) ) { if ( mysql_version_ge( 5, 1, 5 ) ) {
# MySQL 5 servers can have table sizes calculated quickly from information schema # MySQL 5 servers can have table sizes calculated quickly from information schema
my @templist = select_array my @templist = select_array
"SELECT ENGINE,SUM(DATA_LENGTH+INDEX_LENGTH),COUNT(ENGINE),SUM(DATA_LENGTH),SUM(INDEX_LENGTH) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND ENGINE IS NOT NULL GROUP BY ENGINE ORDER BY ENGINE ASC;"; "SELECT ENGINE,SUM(DATA_LENGTH+INDEX_LENGTH),COUNT(ENGINE),SUM(DATA_LENGTH),SUM(INDEX_LENGTH) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND ENGINE IS NOT NULL GROUP BY ENGINE ORDER BY ENGINE ASC;";
@ -1893,7 +2053,7 @@ sub mysql_stats {
push( @generalrec, push( @generalrec,
"Upgrade MySQL to version 4+ to utilize query caching" ); "Upgrade MySQL to version 4+ to utilize query caching" );
} }
elsif (mysql_version_ge(5,6)) elsif (mysql_version_ge(5,5))
{ {
if ( $myvar{'query_cache_type'} ne "OFF" ) { if ( $myvar{'query_cache_type'} ne "OFF" ) {
badprint "Query cache should be disabled by default due to mutex contention."; badprint "Query cache should be disabled by default due to mutex contention.";
@ -2347,13 +2507,33 @@ sub mariadb_threadpool {
# AriaDB # AriaDB
unless ( defined $myvar{'have_threadpool'} unless ( defined $myvar{'have_threadpool'}
&& $myvar{'have_threadpool'} eq "YES" && $myvar{'have_threadpool'} eq "YES" )
&& defined $enginestats{'Aria'} )
{ {
infoprint "ThreadPool stat is disabled."; infoprint "ThreadPool stat is disabled.";
return; return;
} }
infoprint "ThreadPool stat is enabled."; infoprint "ThreadPool stat is enabled.";
infoprint "Thread Pool Size: ".$myvar{'thread_pool_size'}. " thread(s).";
if ($myvar{'have_innodb'} eq 'YES') {
if ($myvar{'thread_pool_size'}< 16 or $myvar{'thread_pool_size'}>36) {
badprint "thread_pool_size between 16 and 36 when using InnoDB storage engine.";
push( @generalrec, "Thread pool size for InnoDB usage (".$myvar{'thread_pool_size'}.")" );
push( @adjvars, "thread_pool_size between 16 and 36 for InnoDB usage" );
} else {
goodprint "thread_pool_size between 16 and 36 when using InnoDB storage engine.";
}
return;
}
if ($myvar{'have_isam'} eq 'YES') {
if ($myvar{'thread_pool_size'}<4 or $myvar{'thread_pool_size'}>8) {
badprint "thread_pool_size between 4 and 8 when using MyIsam storage engine.";
push( @generalrec, "Thread pool size for MyIsam usage (".$myvar{'thread_pool_size'}.")" );
push( @adjvars, "thread_pool_size between 4 and 8 for MyIsam usage" );
} else {
goodprint "thread_pool_size between 4 and 8 when using MyISAM storage engine.";
}
}
} }
# Recommendations for Performance Schema # Recommendations for Performance Schema
@ -2369,6 +2549,7 @@ sub mysqsl_pfs {
infoprint "Performance schema is enabled."; infoprint "Performance schema is enabled.";
} }
# Recommendations for Ariadb # Recommendations for Ariadb
sub mariadb_ariadb { sub mariadb_ariadb {
prettyprint prettyprint
@ -2702,9 +2883,11 @@ sub mysql_databases {
infoprint "There is " . scalar(@dblist) . " Database(s)."; infoprint "There is " . scalar(@dblist) . " Database(s).";
my @totaldbinfo = split /\s/, my @totaldbinfo = split /\s/,
select_one( select_one(
"SELECT SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH) , SUM(DATA_LENGTH+INDEX_LENGTH) FROM information_schema.TABLES;" "SELECT SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH) , SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(TABLE_NAME),COUNT(DISTINCT(TABLE_COLLATION)),COUNT(DISTINCT(ENGINE)) FROM information_schema.TABLES;"
); );
infoprint "All Databases:"; infoprint "All Databases:";
infoprint " +-- TABLE : "
. ( $totaldbinfo[4] eq 'NULL' ? 0 : $totaldbinfo[4] ) . "";
infoprint " +-- ROWS : " infoprint " +-- ROWS : "
. ( $totaldbinfo[0] eq 'NULL' ? 0 : $totaldbinfo[0] ) . ""; . ( $totaldbinfo[0] eq 'NULL' ? 0 : $totaldbinfo[0] ) . "";
infoprint " +-- DATA : " infoprint " +-- DATA : "
@ -2714,9 +2897,11 @@ sub mysql_databases {
. hr_bytes( $totaldbinfo[2] ) . "(" . hr_bytes( $totaldbinfo[2] ) . "("
. percentage( $totaldbinfo[2], $totaldbinfo[3] ) . "%)"; . percentage( $totaldbinfo[2], $totaldbinfo[3] ) . "%)";
infoprint " +-- SIZE : " . hr_bytes( $totaldbinfo[3] ) . ""; infoprint " +-- SIZE : " . hr_bytes( $totaldbinfo[3] ) . "";
infoprint " +-- COLLA : "
. ( $totaldbinfo[5] eq 'NULL' ? 0 : $totaldbinfo[5] ) . " (". (join ", ", select_array ("SELECT DISTINCT(TABLE_COLLATION) FROM information_schema.TABLES;")) .")";
infoprint " +-- ENGIN : "
. ( $totaldbinfo[6] eq 'NULL' ? 0 : $totaldbinfo[6] ) . " (". (join ", ", select_array ("SELECT DISTINCT(ENGINE) FROM information_schema.TABLES;")) .")";
badprint "Index size is larger than data size \n"
if $totaldbinfo[1] < $totaldbinfo[2];
$result{'Databases'}{'All databases'}{'Rows'} = $result{'Databases'}{'All databases'}{'Rows'} =
( $totaldbinfo[0] eq 'NULL' ? 0 : $totaldbinfo[0] ); ( $totaldbinfo[0] eq 'NULL' ? 0 : $totaldbinfo[0] );
@ -2727,12 +2912,12 @@ sub mysql_databases {
$result{'Databases'}{'All databases'}{'Index Pct'} = $result{'Databases'}{'All databases'}{'Index Pct'} =
percentage( $totaldbinfo[2], $totaldbinfo[3] ) . "%"; percentage( $totaldbinfo[2], $totaldbinfo[3] ) . "%";
$result{'Databases'}{'All databases'}{'Total Size'} = $totaldbinfo[3]; $result{'Databases'}{'All databases'}{'Total Size'} = $totaldbinfo[3];
print "\n";
foreach (@dblist) { foreach (@dblist) {
chomp($_); chomp($_);
if ( $_ eq "information_schema" if ( $_ eq "information_schema"
or $_ eq "performance_schema" or $_ eq "performance_schema"
or $_ eq "mysql" # or $_ eq "mysql"
or $_ eq "" ) or $_ eq "" )
{ {
next; next;
@ -2740,10 +2925,15 @@ sub mysql_databases {
my @dbinfo = split /\s/, my @dbinfo = split /\s/,
select_one( select_one(
"SELECT TABLE_SCHEMA, SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH) , SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(DISTINCT ENGINE) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' GROUP BY TABLE_SCHEMA ORDER BY TABLE_SCHEMA" "SELECT TABLE_SCHEMA, SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH) , SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(DISTINCT ENGINE),COUNT(TABLE_NAME),COUNT(DISTINCT(TABLE_COLLATION)),COUNT(DISTINCT(ENGINE)) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' GROUP BY TABLE_SCHEMA ORDER BY TABLE_SCHEMA"
); );
next unless defined $dbinfo[0]; next unless defined $dbinfo[0];
infoprint "Database: " . $dbinfo[0] . ""; infoprint "Database: " . $dbinfo[0] . "";
infoprint " +-- TABLE: "
. ( !defined( $dbinfo[6] ) or $dbinfo[6] eq 'NULL' ? 0 : $dbinfo[6] )
. "";
infoprint " +-- COLL : "
. ( $dbinfo[7] eq 'NULL' ? 0 : $dbinfo[7] ) . " (". (join ", ", select_array ("SELECT DISTINCT(TABLE_COLLATION) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_';")) .")";
infoprint " +-- ROWS : " infoprint " +-- ROWS : "
. ( !defined( $dbinfo[1] ) or $dbinfo[1] eq 'NULL' ? 0 : $dbinfo[1] ) . ( !defined( $dbinfo[1] ) or $dbinfo[1] eq 'NULL' ? 0 : $dbinfo[1] )
. ""; . "";
@ -2754,11 +2944,15 @@ sub mysql_databases {
. hr_bytes( $dbinfo[3] ) . "(" . hr_bytes( $dbinfo[3] ) . "("
. percentage( $dbinfo[3], $dbinfo[4] ) . "%)"; . percentage( $dbinfo[3], $dbinfo[4] ) . "%)";
infoprint " +-- TOTAL: " . hr_bytes( $dbinfo[4] ) . ""; infoprint " +-- TOTAL: " . hr_bytes( $dbinfo[4] ) . "";
infoprint " +-- ENGIN : "
. ( $dbinfo[8] eq 'NULL' ? 0 : $dbinfo[8] ) . " (". (join ", ", select_array ("SELECT DISTINCT(ENGINE) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_'")) .")";
badprint "Index size is larger than data size for $dbinfo[0] \n" badprint "Index size is larger than data size for $dbinfo[0] \n"
if $dbinfo[2] < $dbinfo[3]; if $dbinfo[2] < $dbinfo[3];
badprint "There are " . $dbinfo[5] . " storage engines. Be careful. \n" badprint "There are " . $dbinfo[5] . " storage engines. Be careful. \n"
if $dbinfo[5] > 1; if $dbinfo[5] > 1;
$result{'Databases'}{ $dbinfo[0] }{'Rows'} = $dbinfo[1]; $result{'Databases'}{ $dbinfo[0] }{'Rows'} = $dbinfo[1];
$result{'Databases'}{ $dbinfo[0] }{'Tables'} = $dbinfo[6];
$result{'Databases'}{ $dbinfo[0] }{'Collations'} = $dbinfo[7];
$result{'Databases'}{ $dbinfo[0] }{'Data Size'} = $dbinfo[2]; $result{'Databases'}{ $dbinfo[0] }{'Data Size'} = $dbinfo[2];
$result{'Databases'}{ $dbinfo[0] }{'Data Pct'} = $result{'Databases'}{ $dbinfo[0] }{'Data Pct'} =
percentage( $dbinfo[2], $dbinfo[4] ) . "%"; percentage( $dbinfo[2], $dbinfo[4] ) . "%";
@ -2766,7 +2960,38 @@ sub mysql_databases {
$result{'Databases'}{ $dbinfo[0] }{'Index Pct'} = $result{'Databases'}{ $dbinfo[0] }{'Index Pct'} =
percentage( $dbinfo[3], $dbinfo[4] ) . "%"; percentage( $dbinfo[3], $dbinfo[4] ) . "%";
$result{'Databases'}{ $dbinfo[0] }{'Total Size'} = $dbinfo[4]; $result{'Databases'}{ $dbinfo[0] }{'Total Size'} = $dbinfo[4];
if ($dbinfo[7]>1) {
badprint $dbinfo[7]. " differents collations for database ".$dbinfo[0];
push(@generalrec, "Check all table collations are identical for all tables in ".$dbinfo[0]. " database.");
} else {
goodprint $dbinfo[7]. " collation for ".$dbinfo[0]. " database.";
} }
if ($dbinfo[8]>1) {
badprint $dbinfo[8]. " differents engines for database ".$dbinfo[0];
push(@generalrec, "Check all table engines are identical for all tables in ".$dbinfo[0]. " database.");
} else {
goodprint $dbinfo[8]. " engine for ".$dbinfo[0]. " database.";
}
my @distinct_column_charset=select_array("select DISTINCT(CHARACTER_SET_NAME) from information_schema.COLUMNS where CHARACTER_SET_NAME IS NOT NULL AND TABLE_SCHEMA ='$_'");
infoprint "Charsets for $dbinfo[0] database table column: ". join (', ', @distinct_column_charset);
if (scalar (@distinct_column_charset)>1 ) {
badprint $dbinfo[0]. " table column(s) has several charsets defined for all text like column(s).";
push(@generalrec, "Limit charset for column to one charset if possible for ".$dbinfo[0]." database.");
} else {
goodprint $dbinfo[0]. " table column(s) has same charset defined for all text like column(s).";
}
my @distinct_column_collation=select_array("select DISTINCT(COLLATION_NAME) from information_schema.COLUMNS where COLLATION_NAME IS NOT NULL AND TABLE_SCHEMA ='$_'");
infoprint "Collations for $dbinfo[0] database table column: ". join (', ', @distinct_column_collation);
if (scalar (@distinct_column_collation)>1 ) {
badprint $dbinfo[0]. " table column(s) has several collations defined for all text like column(s).";
push(@generalrec, "Limit collations for column to one collation if possible for ".$dbinfo[0]." database.");
} else {
goodprint $dbinfo[0]. " table column(s) has same collation defined for all text like column(s).";
}
}
} }
# Recommendations for Indexes metrics # Recommendations for Indexes metrics
@ -2807,7 +3032,7 @@ FROM INFORMATION_SCHEMA.STATISTICS s
, MAX(SEQ_IN_INDEX) AS max_columns , MAX(SEQ_IN_INDEX) AS max_columns
FROM INFORMATION_SCHEMA.STATISTICS FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema') WHERE TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema')
AND INDEX_TYPE <> "FULLTEXT" AND INDEX_TYPE <> 'FULLTEXT'
GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
) AS s2 ) AS s2
ON s.TABLE_SCHEMA = s2.TABLE_SCHEMA ON s.TABLE_SCHEMA = s2.TABLE_SCHEMA
@ -2832,15 +3057,17 @@ ENDSQL
infoprint " +-- NB COLS : " . $info[3] . " column(s)"; infoprint " +-- NB COLS : " . $info[3] . " column(s)";
infoprint " +-- CARDINALITY : " . $info[4] . " distinct values"; infoprint " +-- CARDINALITY : " . $info[4] . " distinct values";
infoprint " +-- NB ROWS : " . $info[5] . " rows"; infoprint " +-- NB ROWS : " . $info[5] . " rows";
infoprint " +-- SELECTIVITY : " . $info[6] . "%"; infoprint " +-- TYPE : " . $info[6] ;
infoprint " +-- SELECTIVITY : " . $info[7] . "%";
$result{'Indexes'}{ $info[1] }{'Colunm'} = $info[0]; $result{'Indexes'}{ $info[1] }{'Colunm'} = $info[0];
$result{'Indexes'}{ $info[1] }{'Sequence number'} = $info[2]; $result{'Indexes'}{ $info[1] }{'Sequence number'} = $info[2];
$result{'Indexes'}{ $info[1] }{'Number of collunm'} = $info[3]; $result{'Indexes'}{ $info[1] }{'Number of collunm'} = $info[3];
$result{'Indexes'}{ $info[1] }{'Cardianality'} = $info[4]; $result{'Indexes'}{ $info[1] }{'Cardianality'} = $info[4];
$result{'Indexes'}{ $info[1] }{'Row number'} = $info[5]; $result{'Indexes'}{ $info[1] }{'Row number'} = $info[5];
$result{'Indexes'}{ $info[1] }{'Selectivity'} = $info[6]; $result{'Indexes'}{ $info[1] }{'Index Type'} = $info[6];
if ( $info[6] < 25 ) { $result{'Indexes'}{ $info[1] }{'Selectivity'} = $info[7];
if ( $info[7] < 25 ) {
badprint "$info[1] has a low selectivity"; badprint "$info[1] has a low selectivity";
} }
} }
@ -3001,6 +3228,7 @@ get_all_vars; # Toss variables/status into hashes
get_tuning_info; # Get information about the tuning connexion get_tuning_info; # Get information about the tuning connexion
validate_mysql_version; # Check current MySQL version validate_mysql_version; # Check current MySQL version
check_architecture; # Suggest 64-bit upgrade check_architecture; # Suggest 64-bit upgrade
system_recommendations; # avoid to many service on the same host
check_storage_engines; # Show enabled storage engines check_storage_engines; # Show enabled storage engines
mysql_databases; # Show informations about databases mysql_databases; # Show informations about databases
mysql_indexes; # Show informations about indexes mysql_indexes; # Show informations about indexes
@ -3033,7 +3261,7 @@ __END__
=head1 NAME =head1 NAME
MySQLTuner 1.6.6 - MySQL High Performance Tuning Script MySQLTuner 1.6.7 - MySQL High Performance Tuning Script
=head1 IMPORTANT USAGE GUIDELINES =head1 IMPORTANT USAGE GUIDELINES
@ -3071,6 +3299,7 @@ You must provide the remote server's total memory when connecting to other serve
--debug Print debug information --debug Print debug information
--dbstat Print database information --dbstat Print database information
--idxstat Print index information --idxstat Print index information
--bannedports ports banned separated by comma(,)
--cvefile CVE File for vulnerability checks --cvefile CVE File for vulnerability checks
--nocolor Don't print output in color --nocolor Don't print output in color
--json Print result as JSON string --json Print result as JSON string