Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.fr.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
2 changes: 1 addition & 1 deletion README.it.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
2 changes: 1 addition & 1 deletion README.ru.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
Binary file added mtlogo2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
229 changes: 133 additions & 96 deletions mysqltuner.pl
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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');"
);
);
}
}
}

Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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" ) {
Copy link

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line still references the old 'wsrep_slave_FK_checks' option name. Consider updating this to use a similar dynamic approach as done for the threads variable, or verify if this option name has also changed in newer versions.

Copilot uses AI. Check for mistakes.
Expand All @@ -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') . ")";
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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'} . " / "
Expand All @@ -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."
);
}
}
Expand All @@ -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'} )
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 )
Expand All @@ -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 )
Expand Down