diff --git a/README.fr.md b/README.fr.md index d89c42668..dbf284a12 100644 --- a/README.fr.md +++ b/README.fr.md @@ -1,4 +1,4 @@ -![MySQLTuner-perl](https://github.com/major/MySQLTuner-perl/blob/master/mtlogo.png) +![MySQLTuner-perl](mtlogo2.png) [!["Buy Us A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/jmrenouard) diff --git a/README.it.md b/README.it.md index 1aef10b52..dc34c5e3e 100644 --- a/README.it.md +++ b/README.it.md @@ -1,4 +1,4 @@ -![MySQLTuner-perl](https://github.com/major/MySQLTuner-perl/blob/master/mtlogo.png) +![MySQLTuner-perl](mtlogo2.png) [!["Buy Us A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/jmrenouard) diff --git a/README.md b/README.md index 50f8735e5..60efdeab5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![MySQLTuner-perl](https://github.com/major/MySQLTuner-perl/blob/master/mtlogo.png) +![MySQLTuner-perl](mtlogo2.png) [!["Buy Us A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/jmrenouard) diff --git a/README.ru.md b/README.ru.md index 079b50184..52efda916 100644 --- a/README.ru.md +++ b/README.ru.md @@ -1,4 +1,4 @@ -![MySQLTuner-perl](https://github.com/major/MySQLTuner-perl/blob/master/mtlogo.png) +![MySQLTuner-perl](mtlogo2.png) [!["Buy Us A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/jmrenouard) diff --git a/mtlogo2.png b/mtlogo2.png new file mode 100644 index 000000000..92b4a234f Binary files /dev/null and b/mtlogo2.png differ diff --git a/mysqltuner.pl b/mysqltuner.pl index 82f126d12..0657dfaac 100755 --- a/mysqltuner.pl +++ b/mysqltuner.pl @@ -446,9 +446,9 @@ sub hr_bytes_practical_rnd { my $num = shift; return "0B" unless defined($num) and $num > 0; - my $gbs = $num / (1024**3); # convert to GB + my $gbs = $num / ( 1024**3 ); # convert to GB my $power_of_2_gb = 1; - while ($power_of_2_gb < $gbs) { + while ( $power_of_2_gb < $gbs ) { $power_of_2_gb *= 2; } @@ -2253,10 +2253,6 @@ sub security_recommendations { subheaderprint "Security Recommendations"; infoprint "$myvar{'version_comment'} - $myvar{'version'}"; - if ( mysql_version_ge(8.0) ) { - infoprint "Skipped due to unsupported feature for MySQL 8.0+"; - return; - } #exit 0; if ( $opt{skippassword} eq 1 ) { @@ -2377,15 +2373,17 @@ sub security_recommendations { } # Looking for User with user/ uppercase /capitalise user as password - @mysqlstatlist = select_array + if ( !mysql_version_ge(8) ) { + @mysqlstatlist = select_array "SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE user != '' AND (CAST($PASS_COLUMN_NAME as Binary) = PASSWORD(user) OR CAST($PASS_COLUMN_NAME as Binary) = PASSWORD(UPPER(user)) OR CAST($PASS_COLUMN_NAME as Binary) = PASSWORD(CONCAT(UPPER(LEFT(User, 1)), SUBSTRING(User, 2, LENGTH(User)))))"; - if (@mysqlstatlist) { - foreach my $line ( sort @mysqlstatlist ) { - chomp($line); - badprint "User " . $line . " has user name as password."; - push( @generalrec, + if (@mysqlstatlist) { + foreach my $line ( sort @mysqlstatlist ) { + chomp($line); + badprint "User " . $line . " has user name as password."; + push( @generalrec, "Set up a Secure Password for $line user: SET PASSWORD FOR $line = PASSWORD('secure_password');" - ); + ); + } } } @@ -2419,44 +2417,46 @@ sub security_recommendations { my $nbins = 0; my $passreq; if (@passwords) { - my $nbInterPass = 0; - foreach my $pass (@passwords) { - $nbInterPass++; - - $pass =~ s/\s//g; - $pass =~ s/\'/\\\'/g; - chomp($pass); - - # Looking for User with user/ uppercase /capitalise weak password - @mysqlstatlist = - select_array + if ( !mysql_version_ge(8) ) { + my $nbInterPass = 0; + foreach my $pass (@passwords) { + $nbInterPass++; + + $pass =~ s/\s//g; + $pass =~ s/\'/\\\'/g; + chomp($pass); + + # Looking for User with user/ uppercase /capitalise weak password + @mysqlstatlist = + select_array "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE $PASS_COLUMN_NAME = PASSWORD('" - . $pass - . "') OR $PASS_COLUMN_NAME = PASSWORD(UPPER('" - . $pass - . "')) OR $PASS_COLUMN_NAME = PASSWORD(CONCAT(UPPER(LEFT('" - . $pass - . "', 1)), SUBSTRING('" - . $pass - . "', 2, LENGTH('" - . $pass . "'))))"; - debugprint "There are " . scalar(@mysqlstatlist) . " items."; - if (@mysqlstatlist) { - foreach my $line (@mysqlstatlist) { - chomp($line); - badprint "User '" . $line - . "' is using weak password: $pass in a lower, upper or capitalize derivative version."; + . $pass + . "') OR $PASS_COLUMN_NAME = PASSWORD(UPPER('" + . $pass + . "')) OR $PASS_COLUMN_NAME = PASSWORD(CONCAT(UPPER(LEFT('" + . $pass + . "', 1)), SUBSTRING('" + . $pass + . "', 2, LENGTH('" + . $pass . "'))))"; + debugprint "There are " . scalar(@mysqlstatlist) . " items."; + if (@mysqlstatlist) { + foreach my $line (@mysqlstatlist) { + chomp($line); + badprint "User '" . $line + . "' is using weak password: $pass in a lower, upper or capitalize derivative version."; - push( @generalrec, + push( @generalrec, "Set up a Secure Password for $line user: SET PASSWORD FOR '" - . ( split /@/, $line )[0] . "'\@'" - . ( split /@/, $line )[1] - . "' = PASSWORD('secure_password');" ); - $nbins++; + . ( split /@/, $line )[0] . "'\@'" + . ( split /@/, $line )[1] + . "' = PASSWORD('secure_password');" ); + $nbins++; + } } + debugprint "$nbInterPass / " . scalar(@passwords) + if ( $nbInterPass % 1000 == 0 ); } - debugprint "$nbInterPass / " . scalar(@passwords) - if ( $nbInterPass % 1000 == 0 ); } } if ( $nbins > 0 ) { @@ -5996,9 +5996,7 @@ sub trim { sub get_wsrep_options { return () unless defined $myvar{'wsrep_provider_options'}; - my @galera_options = split /;/, $myvar{'wsrep_provider_options'}; - my $wsrep_slave_threads = $myvar{'wsrep_slave_threads'}; - push @galera_options, ' wsrep_slave_threads = ' . $wsrep_slave_threads; + my @galera_options = split /;/, $myvar{'wsrep_provider_options'}; @galera_options = remove_cr @galera_options; @galera_options = remove_empty @galera_options; @@ -6176,25 +6174,33 @@ sub mariadb_galera { . hr_bytes_rnd( get_wsrep_option('gcache.mem_size') ); infoprint "CPU cores detected : " . (cpu_cores); - infoprint "wsrep_slave_threads: " . get_wsrep_option('wsrep_slave_threads'); + my $wsrep_threads_var_name = 'wsrep_slave_threads'; + if ( defined( $myvar{'wsrep_applier_threads'} ) ) { + $wsrep_threads_var_name = 'wsrep_applier_threads'; + } + # Use 1 as a fallback if $myvar{$wsrep_threads_var_name} is undefined or zero, + # to ensure there is at least one thread for Galera replication. + my $wsrep_threads_value = $myvar{$wsrep_threads_var_name} || 1; + + infoprint "$wsrep_threads_var_name: " . $wsrep_threads_value; - if ( get_wsrep_option('wsrep_slave_threads') > ( (cpu_cores) * 4 ) - or get_wsrep_option('wsrep_slave_threads') < ( (cpu_cores) * 2 ) ) + if ( $wsrep_threads_value > ( (cpu_cores) * 4 ) + or $wsrep_threads_value < ( (cpu_cores) * 2 ) ) { badprint -"wsrep_slave_threads is not equal to 2, 3 or 4 times the number of CPU(s)"; - push @adjvars, "wsrep_slave_threads = " . ( (cpu_cores) * 4 ); +"$wsrep_threads_var_name is not equal to 2, 3 or 4 times the number of CPU(s)"; + push @adjvars, "$wsrep_threads_var_name = " . ( (cpu_cores) * 4 ); } else { goodprint -"wsrep_slave_threads is equal to 2, 3 or 4 times the number of CPU(s)"; +"$wsrep_threads_var_name is equal to 2, 3 or 4 times the number of CPU(s)"; } - if ( get_wsrep_option('wsrep_slave_threads') > 1 ) { + if ( $wsrep_threads_value > 1 ) { infoprint "wsrep parallel slave can cause frequent inconsistency crash."; push @adjvars, -"Set wsrep_slave_threads to 1 in case of HA_ERR_FOUND_DUPP_KEY crash on slave"; +"Set $wsrep_threads_var_name to 1 in case of HA_ERR_FOUND_DUPP_KEY crash on slave"; # check options for parallel slave if ( get_wsrep_option('wsrep_slave_FK_checks') eq "OFF" ) { @@ -6212,15 +6218,15 @@ sub mariadb_galera { } } - if ( get_wsrep_option('gcs.fc_limit') != $myvar{'wsrep_slave_threads'} * 5 ) - { - badprint "gcs.fc_limit should be equal to 5 * wsrep_slave_threads (=" - . ( $myvar{'wsrep_slave_threads'} * 5 ) . ")"; - push @adjvars, "gcs.fc_limit= wsrep_slave_threads * 5 (=" - . ( $myvar{'wsrep_slave_threads'} * 5 ) . ")"; + if ( get_wsrep_option('gcs.fc_limit') != $wsrep_threads_value * 5 ) { + badprint + "gcs.fc_limit should be equal to 5 * $wsrep_threads_var_name (=" + . ( $wsrep_threads_value * 5 ) . ")"; + push @adjvars, "gcs.fc_limit= $wsrep_threads_var_name * 5 (=" + . ( $wsrep_threads_value * 5 ) . ")"; } else { - goodprint "gcs.fc_limit is equal to 5 * wsrep_slave_threads ( =" + goodprint "gcs.fc_limit is equal to 5 * $wsrep_threads_var_name ( =" . get_wsrep_option('gcs.fc_limit') . ")"; } @@ -6595,39 +6601,60 @@ sub mysql_innodb { } } } - # InnoDB Log File Size / InnoDB Redo Log Capacity Recommendations - # For MySQL < 8.0.30, the recommendation is based on innodb_log_file_size and innodb_log_files_in_group. - # For MySQL >= 8.0.30, innodb_redo_log_capacity replaces the old system. - if ( mysql_version_ge( 8, 0, 30 ) && defined $myvar{'innodb_redo_log_capacity'} ) { + +# InnoDB Log File Size / InnoDB Redo Log Capacity Recommendations +# For MySQL < 8.0.30, the recommendation is based on innodb_log_file_size and innodb_log_files_in_group. +# For MySQL >= 8.0.30, innodb_redo_log_capacity replaces the old system. + if ( mysql_version_ge( 8, 0, 30 ) + && defined $myvar{'innodb_redo_log_capacity'} ) + { # New recommendation logic for MySQL >= 8.0.30 - infoprint "InnoDB Redo Log Capacity is set to " . hr_bytes($myvar{'innodb_redo_log_capacity'}); + infoprint "InnoDB Redo Log Capacity is set to " + . hr_bytes( $myvar{'innodb_redo_log_capacity'} ); my $innodb_os_log_written = $mystat{'Innodb_os_log_written'} || 0; - my $uptime = $mystat{'Uptime'} || 1; + my $uptime = $mystat{'Uptime'} || 1; - if ($uptime > 3600) { # Only make a recommendation if server has been up for at least an hour + if ( $uptime > 3600 ) + { # Only make a recommendation if server has been up for at least an hour my $hourly_rate = $innodb_os_log_written / ( $uptime / 3600 ); - my $suggested_redo_log_capacity_str = hr_bytes_practical_rnd($hourly_rate); - my $suggested_redo_log_capacity_bytes = hr_raw($suggested_redo_log_capacity_str); + my $suggested_redo_log_capacity_str = + hr_bytes_practical_rnd($hourly_rate); + my $suggested_redo_log_capacity_bytes = + hr_raw($suggested_redo_log_capacity_str); - infoprint "Hourly InnoDB log write rate: " . hr_bytes_rnd($hourly_rate) . "/hour"; + infoprint "Hourly InnoDB log write rate: " + . hr_bytes_rnd($hourly_rate) . "/hour"; - if (hr_raw($myvar{'innodb_redo_log_capacity'}) < $hourly_rate) { - badprint "Your innodb_redo_log_capacity is not large enough to hold at least 1 hour of writes."; - push( @adjvars, "innodb_redo_log_capacity (>= " . $suggested_redo_log_capacity_str . ")" ); - } else { - goodprint "Your innodb_redo_log_capacity is sized to handle more than 1 hour of writes."; + if ( hr_raw( $myvar{'innodb_redo_log_capacity'} ) < $hourly_rate ) { + badprint +"Your innodb_redo_log_capacity is not large enough to hold at least 1 hour of writes."; + push( @adjvars, + "innodb_redo_log_capacity (>= " + . $suggested_redo_log_capacity_str + . ")" ); + } + else { + goodprint +"Your innodb_redo_log_capacity is sized to handle more than 1 hour of writes."; } # Sanity check against total InnoDB data size - if ( defined $enginestats{'InnoDB'} and $enginestats{'InnoDB'} > 0 ) { + if ( defined $enginestats{'InnoDB'} and $enginestats{'InnoDB'} > 0 ) + { my $total_innodb_size = $enginestats{'InnoDB'}; - if ( $suggested_redo_log_capacity_bytes > $total_innodb_size * 0.25 ) { - infoprint "The suggested innodb_redo_log_capacity (" . $suggested_redo_log_capacity_str . ") is more than 25% of your total InnoDB data size. This might be unnecessarily large."; + if ( $suggested_redo_log_capacity_bytes > + $total_innodb_size * 0.25 ) + { + infoprint "The suggested innodb_redo_log_capacity (" + . $suggested_redo_log_capacity_str + . ") is more than 25% of your total InnoDB data size. This might be unnecessarily large."; } } - } else { - infoprint "Server uptime is less than 1 hour. Cannot make a reliable recommendation for innodb_redo_log_capacity."; + } + else { + infoprint +"Server uptime is less than 1 hour. Cannot make a reliable recommendation for innodb_redo_log_capacity."; } } else { @@ -6648,11 +6675,12 @@ sub mysql_innodb { . ") if possible, so InnoDB Redo log Capacity equals 25% of buffer pool size." ); push( @generalrec, - "Be careful, increasing innodb_redo_log_capacity means higher crash recovery mean time" +"Be careful, increasing innodb_redo_log_capacity means higher crash recovery mean time" ); } else { - badprint "Ratio InnoDB log file size / InnoDB Buffer pool size (" + badprint + "Ratio InnoDB log file size / InnoDB Buffer pool size (" . $mycalc{'innodb_log_size_pct'} . "%): " . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " . $myvar{'innodb_log_files_in_group'} . " / " @@ -6678,12 +6706,12 @@ sub mysql_innodb { . ") if possible, so InnoDB total log file size equals 25% of buffer pool size." ); push( @generalrec, - "Be careful, increasing innodb_log_file_size / innodb_log_files_in_group means higher crash recovery mean time" +"Be careful, increasing innodb_log_file_size / innodb_log_files_in_group means higher crash recovery mean time" ); } if ( mysql_version_le( 5, 6, 2 ) ) { push( @generalrec, - "For MySQL 5.6.2 and lower, total innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB." +"For MySQL 5.6.2 and lower, total innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB." ); } } @@ -6697,9 +6725,10 @@ sub mysql_innodb { } else { push( @generalrec, - "Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU" +"Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU" ); - goodprint "Ratio InnoDB log file size / InnoDB Buffer pool size: " + goodprint + "Ratio InnoDB log file size / InnoDB Buffer pool size: " . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " . $myvar{'innodb_log_files_in_group'} . "/" . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) @@ -6762,10 +6791,14 @@ sub mysql_innodb { } # InnoDB Used Buffer Pool Size vs CHUNK size - if ( $myvar{'version'} =~ /MariaDB/i and mysql_version_ge(10, 8) and $myvar{'innodb_buffer_pool_chunk_size'} == 0) { - infoprint "innodb_buffer_pool_chunk_size is set to 'autosize' (0) in MariaDB >= 10.8. Skipping chunk size checks."; + if ( $myvar{'version'} =~ /MariaDB/i + and mysql_version_ge( 10, 8 ) + and $myvar{'innodb_buffer_pool_chunk_size'} == 0 ) + { + infoprint +"innodb_buffer_pool_chunk_size is set to 'autosize' (0) in MariaDB >= 10.8. Skipping chunk size checks."; } - elsif ( !defined( $myvar{'innodb_buffer_pool_chunk_size'} ) + elsif (!defined( $myvar{'innodb_buffer_pool_chunk_size'} ) || $myvar{'innodb_buffer_pool_chunk_size'} == 0 || !defined( $myvar{'innodb_buffer_pool_size'} ) || $myvar{'innodb_buffer_pool_size'} == 0 @@ -6821,8 +6854,11 @@ sub mysql_innodb { } # InnoDB Read efficiency - if ( $mystat{'Innodb_buffer_pool_reads'} > $mystat{'Innodb_buffer_pool_read_requests'} ) { - infoprint "InnoDB Read buffer efficiency: metrics are not reliable (reads > read requests)"; + if ( $mystat{'Innodb_buffer_pool_reads'} > + $mystat{'Innodb_buffer_pool_read_requests'} ) + { + infoprint +"InnoDB Read buffer efficiency: metrics are not reliable (reads > read requests)"; } elsif ( defined $mycalc{'pct_read_efficiency'} && $mycalc{'pct_read_efficiency'} < 90 ) @@ -6847,7 +6883,8 @@ sub mysql_innodb { # InnoDB Write efficiency if ( $mystat{'Innodb_log_writes'} > $mystat{'Innodb_log_write_requests'} ) { - infoprint "InnoDB Write Log efficiency: metrics are not reliable (writes > write requests)"; + infoprint +"InnoDB Write Log efficiency: metrics are not reliable (writes > write requests)"; } elsif ( defined $mycalc{'pct_write_efficiency'} && $mycalc{'pct_write_efficiency'} < 90 )