diff --git a/mysql-test/include/grep_pattern.inc b/mysql-test/include/grep_pattern.inc index 46ca8cc77fe..9ec6880af9d 100644 --- a/mysql-test/include/grep_pattern.inc +++ b/mysql-test/include/grep_pattern.inc @@ -12,7 +12,7 @@ # # --let $grep_pattern= PERL_REGEX # --let $grep_file= FILENAME -# [--let $grep_output= print_each | print_count | boolean] +# [--let $grep_output= print_each | print_count | boolean | none] # # Parameters: # @@ -28,6 +28,8 @@ # - print_count: print just a count. # - boolean: print only whether something was found or not. # If this is not specified, print_each is used. +# - none: doesn't print anything but result is $GREP_RESULT +# 0 - no match, and non-zero as number of matches #--let $include_filename= grep_pattern.inc #--source include/begin_include_file.inc @@ -44,39 +46,62 @@ if (!$grep_output) --let _GP_GREP_PATTERN= $grep_pattern --let _GP_GREP_FILE= $grep_file +--let _GREP_RESULT_FILE=$MYSQL_TMP_DIR/grep_result + --perl use strict; my $file= $ENV{'_GP_GREP_FILE'} or die "grep_file is not set"; my $pattern= $ENV{'_GP_GREP_PATTERN'} or die "grep_pattern is not set"; open(FILE, "$file") or die("Unable to open $file: $!\n"); + my $out_file = $ENV{'_GREP_RESULT_FILE'} or die "_GREP_RESULT_FILE is not set"; my $count = 0; + my $result = ""; my $output = $ENV{'_GP_GREP_OUTPUT'}; if ($output eq 'print_each') { print "Matching lines are:\n"; } + + if ($output eq 'none') { + open(OUT_FILE, '>', $out_file) or die("Unable to open out file for writing: $!\n"); + } + + my $finished = 0; + while () { my $line = $_; - if ($line =~ /$pattern/) { + if (($line =~ /^CURRENT_TEST: /)) { + $result = ""; + $finished = 0; + $count = 0; + } + if ($finished == 0 && $line =~ /$pattern/) { if ($output eq 'print_each') { - print $line; + $result = $result . $line; } $count++; if ($output eq 'boolean') { - last; + $finished = 1; } } } + print $result; if ($count == 0 && $output eq 'print_each') { print "None\n"; } if ($output eq 'boolean') { print $count ? "Pattern found.\n" : "Pattern not found.\n"; - } - else { + } elsif ($output eq 'none') { + print OUT_FILE "--let \$GREP_RESULT=$count\n"; + close(OUT_FILE); + } else { print "Occurrences of '$pattern' in the input file: $count\n"; } close(FILE) or die "Error closing $file: $!"; EOF +if ($_GP_GREP_OUTPUT == "none") { + --source $_GREP_RESULT_FILE + --remove_file $_GREP_RESULT_FILE +} #--source include/end_include_file.inc diff --git a/mysql-test/include/have_odirect.inc b/mysql-test/include/have_odirect.inc new file mode 100644 index 00000000000..e8dfc67f9c9 --- /dev/null +++ b/mysql-test/include/have_odirect.inc @@ -0,0 +1,21 @@ +# Tests if datadir can support innodb_flush_method as O_DIRECT +# if datadir is on ramdisk, O_DIRECT is not supported. Such tests +# should be run without --mem and mtr var dir should be on regular disk + +--source include/shutdown_mysqld.inc + +--let $ERROR_LOG=$MYSQL_TMP_DIR/o_direct.log +--error 1 +--exec $MYSQLD_CMD --invalid-option --log-error-verbosity=3 --log-error=$ERROR_LOG --innodb_flush_method=O_DIRECT + +--let $grep_pattern=Failed to set O_DIRECT +--let $grep_file=$ERROR_LOG +--let $grep_output=none +--source grep_pattern.inc + +--remove_file $ERROR_LOG + +--source include/start_mysqld_no_echo.inc +if ($GREP_RESULT == "1") { + --skip "O_DIRECT not available. Requires datadir not be on ramdisk" +} diff --git a/mysql-test/include/persist_only_variables.sql b/mysql-test/include/persist_only_variables.sql index 4adea1da187..cfddc98ba7d 100644 --- a/mysql-test/include/persist_only_variables.sql +++ b/mysql-test/include/persist_only_variables.sql @@ -33,7 +33,6 @@ set @@persist_only.innodb_api_enable_mdl=0; set @@persist_only.innodb_autoinc_lock_mode=1; set @@persist_only.innodb_buffer_pool_chunk_size=134217728; set @@persist_only.innodb_buffer_pool_instances=1; -set @@persist_only.innodb_doublewrite=1; set @@persist_only.innodb_force_recovery=0; set @@persist_only.innodb_ft_cache_size=16777216; set @@persist_only.innodb_ft_max_token_size=84; diff --git a/mysql-test/include/search_pattern_in_file.inc b/mysql-test/include/search_pattern_in_file.inc index 9f0c19b2bc0..2f4d914cbe3 100644 --- a/mysql-test/include/search_pattern_in_file.inc +++ b/mysql-test/include/search_pattern_in_file.inc @@ -13,8 +13,8 @@ # In case of # - SEARCH_FILE and/or SEARCH_PATTERN is not set # - SEARCH_FILE cannot be opened -# - SEARCH_FILE does not contain SEARCH_PATTERN -# the test will abort immediate. +# - SEARCH_FILE does not contain SEARCH_PATTERN and PRINT_SEARCH_RESULT +# is not set the test will abort immediate. # MTR will report something like # .... # worker[1] Using MTR_BUILD_THREAD 300, with reserved ports 13000..13009 @@ -33,6 +33,9 @@ # - saving '' to '' # main.1st [ pass ] 2 # +# - PRINT_SEARCH_RESULT is set - the result of the search will +# get printed. +# # Typical use case (check invalid server startup options): # let $error_log= $MYSQLTEST_VARDIR/log/my_restart.err; # --error 0,1 @@ -41,7 +44,7 @@ # # Stop the server # let $restart_file= $MYSQLTEST_VARDIR/tmp/mysqld.1.expect; # --exec echo "wait" > $restart_file -# --shutdown_server 10 +# --shutdown_server # --source include/wait_until_disconnected.inc # # --error 1 @@ -51,16 +54,45 @@ # --source include/search_pattern_in_file.inc # # Created: 2011-11-11 mleich +# Modified: 2014-04-26 hvadodar +# Modified: 2017-11-10 rgolebiowski # perl; + use strict; - my $search_file= $ENV{'SEARCH_FILE'} or die "SEARCH_FILE not set"; + die "SEARCH_FILE not set" unless $ENV{'SEARCH_FILE'}; + my $print_search_result= $ENV{PRINT_SEARCH_RESULT} or 0; + my $abort_on= $ENV{ABORT_ON} or "NOT_FOUND"; + #my $abort_on_found= $ENV{ABORT_ON_FOUND} or 0; + my @search_files= glob($ENV{'SEARCH_FILE'}); my $search_pattern= $ENV{'SEARCH_PATTERN'} or die "SEARCH_PATTERN not set"; - open(FILE, "$search_file") or die("Unable to open '$search_file': $!\n"); - read(FILE, my $file_content, 100000, 0); - close(FILE); - if ( not $file_content =~ m{$search_pattern} ) { - die("# ERROR: The file '$search_file' does not contain the expected pattern $search_pattern\n->$file_content<-\n"); + my $file_content= ""; + my $found= 0; + foreach my $search_file (@search_files) { + open(FILE, "$search_file") or die("Unable to open '$search_file': $!\n"); + while ( read(FILE, $file_content, 50000, 0) ) { + if ( $file_content =~ m{$search_pattern} ) { + $found= 1; + last; + } + } + close(FILE); + } + + if ( $print_search_result == 0 ) { + if ( $abort_on eq "NOT_FOUND" and $found == 0 ) { + die ("# ERROR: None of the files do not contain the expected pattern $search_pattern\n"); + } + if ( $abort_on eq "FOUND" and $found ) { + die ("# ERROR: Some of the files contain pattern that we expected not to find $search_pattern\n"); + } + } else { + my $res=$found ? "FOUND" : "NOT FOUND"; + $ENV{SEARCH_FILE} =~ s{^.*?([^/\\]+)$}{$1}; + print "$res /$search_pattern/ in $ENV{SEARCH_FILE}\n"; } + #elsif ( $found == 0 and $abort_on_found eq "Disabled" ) { + #die("# ERROR: None of the files do not contain the expected pattern $search_pattern\n"); + #} EOF diff --git a/mysql-test/r/read_only_persisted_plugin_variables.result b/mysql-test/r/read_only_persisted_plugin_variables.result index ac3d70cc8b3..fe1e64eb40e 100644 --- a/mysql-test/r/read_only_persisted_plugin_variables.result +++ b/mysql-test/r/read_only_persisted_plugin_variables.result @@ -28,7 +28,6 @@ set @@persist_only.innodb_api_enable_mdl=0; set @@persist_only.innodb_autoinc_lock_mode=1; set @@persist_only.innodb_buffer_pool_chunk_size=134217728; set @@persist_only.innodb_buffer_pool_instances=1; -set @@persist_only.innodb_doublewrite=1; set @@persist_only.innodb_force_recovery=0; set @@persist_only.innodb_ft_cache_size=16777216; set @@persist_only.innodb_ft_max_token_size=84; @@ -112,7 +111,7 @@ set @@persist_only.report_user=NULL; # Must return 100 rows. SELECT count(*) from performance_schema.persisted_variables; count(*) -94 +93 # Restart server CALL mtr.add_suppression("Plugin audit_log reported *"); # Both queries must return 101 rows. @@ -133,7 +132,6 @@ innodb_api_enable_mdl 0 innodb_autoinc_lock_mode 1 innodb_buffer_pool_chunk_size 134217728 innodb_buffer_pool_instances 1 -innodb_doublewrite 1 innodb_force_recovery 0 innodb_ft_cache_size 16777216 innodb_ft_max_token_size 84 @@ -227,7 +225,6 @@ innodb_api_enable_mdl innodb_autoinc_lock_mode innodb_buffer_pool_chunk_size innodb_buffer_pool_instances -innodb_doublewrite innodb_force_recovery innodb_ft_cache_size innodb_ft_max_token_size diff --git a/mysql-test/suite/innodb/include/corrupt_page.inc b/mysql-test/suite/innodb/include/corrupt_page.inc new file mode 100644 index 00000000000..471a6e5d92d --- /dev/null +++ b/mysql-test/suite/innodb/include/corrupt_page.inc @@ -0,0 +1,23 @@ +# Requires three parameters +# 1. IBD_FILE :- the file to corrupt a page in it +# 2. INNODB_PAGE_SIZE :- page_size of IBD +# 3. PAGE_NUM :- the page to corrupt +# 4. ALL_ZEROES :- write the entire page as all-zeros (optional parameter) +# (innodb doesn't treat all-zero as corrupted page) +perl; +use IO::Handle; +my $file = $ENV{'IBD_FILE'} or die; +my $page_size = $ENV{'INNODB_PAGE_SIZE'} or die; +my $page_num = $ENV{'PAGE_NUM'} or die; +my $all_zeroes = $ENV{'ALL_ZEROES'}; +open(FILE, "+<", $file) or die; +FILE->autoflush(1); +binmode FILE; +seek(FILE, $page_size * $page_num, SEEK_SET); +if ($all_zeroes) { + print FILE chr(0) x $page_size; +} else { + print FILE chr(0) x ($page_size/2); +} +close FILE; +EOF diff --git a/mysql-test/suite/innodb/include/doublewrite_on_to_reduced.inc b/mysql-test/suite/innodb/include/doublewrite_on_to_reduced.inc new file mode 100644 index 00000000000..2bb03882da3 --- /dev/null +++ b/mysql-test/suite/innodb/include/doublewrite_on_to_reduced.inc @@ -0,0 +1,528 @@ +--let BACKUP=$MYSQL_TMP_DIR/datadir_backup +--let ORIG=$MYSQL_TMP_DIR/datadir_orig +if ($O_DIRECT) { +--let $restart_parameters = "restart: --datadir=$BACKUP/data --innodb-flush-method=O_DIRECT" +} + +if (!$O_DIRECT) { +--let $restart_parameters = "restart: --datadir=$BACKUP/data" +} + +--let $ERROR_LOG=$MYSQL_TMP_DIR/error.log + +--disable_query_log +call mtr.add_suppression("Database page [0-9]+:1 contained only zeroes."); +call mtr.add_suppression("Header page consists of zero bytes"); +call mtr.add_suppression("Checksum mismatch in datafile"); +call mtr.add_suppression("but the innodb_page_size start-up parameter is"); +call mtr.add_suppression("Database page corruption"); +call mtr.add_suppression("Wrong server version"); +--enable_query_log + +--let INNODB_PAGE_SIZE=`select @@innodb_page_size` +--let MYSQLD_DATADIR=`select @@datadir` + +--let $ARGS=--innodb_doublewrite=reduced --log-error=$ERROR_LOG --datadir=$BACKUP/data + +SHOW VARIABLES LIKE 'innodb_doublewrite'; +SHOW VARIABLES LIKE 'innodb_fil_make_page_dirty_debug'; +SHOW VARIABLES LIKE 'innodb_saved_page_number_debug'; + +--echo # Wait for purge to complete +--source include/wait_innodb_all_purged.inc + +create table t1 (f1 int primary key, f2 blob) engine=innodb STATS_PERSISTENT=0; + +START TRANSACTION; +INSERT INTO t1 VALUES(1, repeat('#',12)); +INSERT INTO t1 VALUES(2, repeat('+',12)); +INSERT INTO t1 VALUES(3, repeat('/',12)); +INSERT INTO t1 VALUES(4, repeat('-',12)); +INSERT INTO t1 VALUES(5, repeat('.',12)); +COMMIT WORK; + +# Slow shutdown and restart to make sure ibuf merge is finished +SET GLOBAL innodb_fast_shutdown = 0; +--source include/shutdown_mysqld.inc + +--echo # Take backup of original datadir +--mkdir $BACKUP +--mkdir $ORIG +--force-cpdir $MYSQLD_DATADIR $BACKUP +--force-cpdir $MYSQLD_DATADIR $ORIG + +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if first page of user +--echo # tablespace is full of zeroes. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +--echo # Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; + +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACEs +WHERE name = 'test/t1' INTO @space_id; + +SET GLOBAL innodb_checkpoint_disabled=1; +SET GLOBAL innodb_limit_optimistic_insert_debug=2; + +SELECT @@innodb_doublewrite; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; + +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); +INSERT INTO t1 SELECT f1+64,f2 FROM t1; + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; + +--source include/expect_crash.inc +# Set the page dirty and force a flush to disk +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; + +--echo # Make the first page (page_no=0) of the user tablespace +--echo # full of zeroes. +--let IBD_FILE=$BACKUP/data/test/t1.ibd +--let PAGE_NUM="0" +--let ALL_ZEROES=1 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./test/t1.ibd space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 1 + +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if first page of user +--echo # tablespace is corrupted. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SELECT space from INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; + +--echo # Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; + +SET GLOBAL innodb_checkpoint_disabled=1; + +SELECT @@innodb_doublewrite; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; + +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; + +--source include/expect_crash.inc +# Set the page dirty and force a flush to disk +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; + +--echo # Corrupt the first page (page_no=0) of the user tablespace. +--let IBD_FILE=$BACKUP/data/test/t1.ibd +--let PAGE_NUM="0" +--let ALL_ZEROES=0 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./test/t1.ibd space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 2 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if 2nd page of user +--echo # tablespace is full of zeroes. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; + +--echo # Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; + +SET GLOBAL innodb_checkpoint_disabled=1; + +SELECT @@innodb_doublewrite; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; + +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; + +--echo # Make the 2nd page (page_no=1) of the tablespace all zeroes. +--let IBD_FILE=$BACKUP/data/test/t1.ibd +--let PAGE_NUM=1 +--let ALL_ZEROES=1 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./test/t1.ibd space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 3 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if 2nd page of user +--echo # tablespace is corrupted. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' into @space_id; + +#--echo # Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; + +SET GLOBAL innodb_checkpoint_disabled=1; + +SELECT @@innodb_doublewrite; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; + +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); + +--echo # Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--source include/expect_crash.inc +# Set the page dirty and force a flush to disk +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; + +--echo # Corrupt the 2nd page (page_no=1) of the user tablespace. +--let IBD_FILE=$BACKUP/data/test/t1.ibd +--let PAGE_NUM=1 +--let ALL_ZEROES=0 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./test/t1.ibd space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 4 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if first page of +--echo # system tablespace is full of zeroes. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SET GLOBAL innodb_master_thread_disabled_debug=1; + +SELECT @@innodb_doublewrite; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; + +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; + +--echo # Make the first page (page_no=0) of the system tablespace +--echo # all zeroes. +--let IBD_FILE=$BACKUP/data/ibdata1 +--let PAGE_NUM="0" +--let ALL_ZEROES=1 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./ibdata1 space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 5 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if first page of +--echo # system tablespace is corrupted. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SET GLOBAL innodb_master_thread_disabled_debug=1; + +SELECT @@innodb_doublewrite; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; + +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; + +--echo # Corrupt the first page (page_no=0) of the system tablespace. +--let IBD_FILE=$BACKUP/data/ibdata1 +--let PAGE_NUM="0" +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./ibdata1 space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 6 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if 2nd page of +--echo # system tablespace is full of zeroes. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SET GLOBAL innodb_master_thread_disabled_debug=1; + +SELECT @@innodb_doublewrite; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; + +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; + +--echo # Make the 2nd page (page_no=1) of the system tablespace +--echo # all zeroes. +--let IBD_FILE=$BACKUP/data/ibdata1 +--let PAGE_NUM=1 +--let ALL_ZEROES=1 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./ibdata1 space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 7 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if 2nd page of +--echo # system tablespace is corrupted. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SET GLOBAL innodb_master_thread_disabled_debug=1; + +SELECT @@innodb_doublewrite; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; + +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; + +--echo # Make the 2nd page (page_no=1) of the system tablespace +--echo # all zeroes. +--let IBD_FILE=$BACKUP/data/ibdata1 +--let PAGE_NUM=1 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./ibdata1 space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data + +--let $restart_parameters = +--source include/start_mysqld.inc + +--echo # Test End - 8 +--echo # --------------------------------------------------------------- + +DROP TABLE t1; +--force-rmdir $BACKUP +--force-rmdir $ORIG diff --git a/mysql-test/suite/innodb/include/doublewrite_reduced.inc b/mysql-test/suite/innodb/include/doublewrite_reduced.inc new file mode 100644 index 00000000000..101da353726 --- /dev/null +++ b/mysql-test/suite/innodb/include/doublewrite_reduced.inc @@ -0,0 +1,441 @@ +--let BACKUP=$MYSQL_TMP_DIR/datadir_backup +--let ORIG=$MYSQL_TMP_DIR/datadir_orig +if ($O_DIRECT) { +--let $restart_parameters = "restart: --innodb_doublewrite=reduced --datadir=$BACKUP/data --innodb-flush-method=O_DIRECT" +} + +if (!$O_DIRECT) { +--let $restart_parameters = "restart: --innodb_doublewrite=reduced --datadir=$BACKUP/data" +} + +--let $ERROR_LOG=$MYSQL_TMP_DIR/error.log + +--disable_query_log +call mtr.add_suppression("Database page [0-9]+:1 contained only zeroes."); +call mtr.add_suppression("Header page consists of zero bytes"); +call mtr.add_suppression("Checksum mismatch in datafile"); +call mtr.add_suppression("but the innodb_page_size start-up parameter is"); +call mtr.add_suppression("Database page corruption"); +call mtr.add_suppression("Wrong server version"); +--enable_query_log + +--let INNODB_PAGE_SIZE=`select @@innodb_page_size` +--let MYSQLD_DATADIR=`select @@datadir` + +--let $ARGS=--innodb_doublewrite=reduced --log-error=$ERROR_LOG --datadir=$BACKUP/data + +SHOW VARIABLES LIKE 'innodb_doublewrite'; +SHOW VARIABLES LIKE 'innodb_fil_make_page_dirty_debug'; +SHOW VARIABLES LIKE 'innodb_saved_page_number_debug'; + +--echo # Wait for purge to complete +--source include/wait_innodb_all_purged.inc + +create table t1 (f1 int primary key, f2 blob) engine=innodb STATS_PERSISTENT=0; + +START TRANSACTION; +INSERT INTO t1 VALUES(1, repeat('#',12)); +INSERT INTO t1 VALUES(2, repeat('+',12)); +INSERT INTO t1 VALUES(3, repeat('/',12)); +INSERT INTO t1 VALUES(4, repeat('-',12)); +INSERT INTO t1 VALUES(5, repeat('.',12)); +COMMIT WORK; + +# Slow shutdown and restart to make sure ibuf merge is finished +SET GLOBAL innodb_fast_shutdown = 0; +--source include/shutdown_mysqld.inc + +--echo # Take backup of original datadir +--mkdir $BACKUP +--mkdir $ORIG +--force-cpdir $MYSQLD_DATADIR $BACKUP +--force-cpdir $MYSQLD_DATADIR $ORIG +--remove_files_wildcard $ORIG/data/ *.bdblwr +--remove_files_wildcard $ORIG/data/ *.dblwr + +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if first page of user +--echo # tablespace is full of zeroes. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +--echo # Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; + +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACEs +WHERE name = 'test/t1' INTO @space_id; + +SET GLOBAL innodb_checkpoint_disabled=1; +SET GLOBAL innodb_limit_optimistic_insert_debug=2; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; + +--source include/expect_crash.inc +# Set the page dirty and force a flush to disk +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; + +--echo # Make the first page (page_no=0) of the user tablespace +--echo # full of zeroes. +--let IBD_FILE=$BACKUP/data/test/t1.ibd +--let PAGE_NUM="0" +--let ALL_ZEROES=1 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./test/t1.ibd space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 1 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if first page of user +--echo # tablespace is corrupted. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SELECT space from INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; + +--echo # Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; + +SET GLOBAL innodb_checkpoint_disabled=1; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; + +--source include/expect_crash.inc +# Set the page dirty and force a flush to disk +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; + +--echo # Corrupt the first page (page_no=0) of the user tablespace. +--let IBD_FILE=$BACKUP/data/test/t1.ibd +--let PAGE_NUM="0" +--let ALL_ZEROES=0 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./test/t1.ibd space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 2 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if 2nd page of user +--echo # tablespace is full of zeroes. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; + +--echo # Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; + +SET GLOBAL innodb_checkpoint_disabled=1; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; + +--echo # Make the 2nd page (page_no=1) of the tablespace all zeroes. +--let IBD_FILE=$BACKUP/data/test/t1.ibd +--let PAGE_NUM=1 +--let ALL_ZEROES=1 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./test/t1.ibd space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 3 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if 2nd page of user +--echo # tablespace is corrupted. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' into @space_id; + +#--echo # Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; + +SET GLOBAL innodb_checkpoint_disabled=1; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); + +--echo # Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--source include/expect_crash.inc +# Set the page dirty and force a flush to disk +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; + +--echo # Corrupt the 2nd page (page_no=1) of the user tablespace. +--let IBD_FILE=$BACKUP/data/test/t1.ibd +--let PAGE_NUM=1 +--let ALL_ZEROES=0 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./test/t1.ibd space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 4 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if first page of +--echo # system tablespace is full of zeroes. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SET GLOBAL innodb_master_thread_disabled_debug=1; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; + +--echo # Make the first page (page_no=0) of the system tablespace +--echo # all zeroes. +--let IBD_FILE=$BACKUP/data/ibdata1 +--let PAGE_NUM="0" +--let ALL_ZEROES=1 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./ibdata1 space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 5 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if first page of +--echo # system tablespace is corrupted. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SET GLOBAL innodb_master_thread_disabled_debug=1; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; + +--echo # Corrupt the first page (page_no=0) of the system tablespace. +--let IBD_FILE=$BACKUP/data/ibdata1 +--let PAGE_NUM="0" +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./ibdata1 space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 6 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if 2nd page of +--echo # system tablespace is full of zeroes. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SET GLOBAL innodb_master_thread_disabled_debug=1; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; + +--echo # Make the 2nd page (page_no=1) of the system tablespace +--echo # all zeroes. +--let IBD_FILE=$BACKUP/data/ibdata1 +--let PAGE_NUM=1 +--let ALL_ZEROES=1 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./ibdata1 space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data +--replace_result $BACKUP BACKUP " --innodb-flush-method=O_DIRECT" "" +--source include/start_mysqld.inc + +--echo # Test End - 7 +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if 2nd page of +--echo # system tablespace is corrupted. + +SHOW VARIABLES LIKE 'innodb_doublewrite'; + +SET GLOBAL innodb_master_thread_disabled_debug=1; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; + +--echo # Make the 2nd page (page_no=1) of the system tablespace +--echo # all zeroes. +--let IBD_FILE=$BACKUP/data/ibdata1 +--let PAGE_NUM=1 +--source corrupt_page.inc + +--error 1 +--exec $MYSQLD_CMD $ARGS + +--let ABORT_ON=NOT_FOUND +--let SEARCH_PATTERN=Database page corruption of tablespace ./ibdata1 space_id: \d+ page_num: \d+. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode +--let SEARCH_FILE= $ERROR_LOG +--source include/search_pattern_in_file.inc +--remove_file $ERROR_LOG + +--echo # restoring original datadir +--force-rmdir $BACKUP/data +--force-cpdir $ORIG/data $BACKUP/data + +--let $restart_parameters = +--source include/start_mysqld.inc + +--echo # Test End - 8 +--echo # --------------------------------------------------------------- + +DROP TABLE t1; +--force-rmdir $BACKUP +--force-rmdir $ORIG diff --git a/mysql-test/suite/innodb/include/doublewrite_reduced_to_on.inc b/mysql-test/suite/innodb/include/doublewrite_reduced_to_on.inc new file mode 100644 index 00000000000..6bae578ac1b --- /dev/null +++ b/mysql-test/suite/innodb/include/doublewrite_reduced_to_on.inc @@ -0,0 +1,352 @@ +# Slow shutdown and restart to make sure ibuf merge is finished +SET GLOBAL innodb_fast_shutdown = 0; +--source include/restart_mysqld.inc + +--let $restart_parameters = "restart: --innodb_doublewrite=reduced" + +--disable_query_log +call mtr.add_suppression("Database page [0-9]+:1 contained only zeroes."); +call mtr.add_suppression("Header page consists of zero bytes"); +call mtr.add_suppression("Checksum mismatch in datafile"); +call mtr.add_suppression("but the innodb_page_size start-up parameter is"); +call mtr.add_suppression("Database page corruption"); +call mtr.add_suppression("Wrong server version"); +--enable_query_log + +let INNODB_PAGE_SIZE=`select @@innodb_page_size`; +let MYSQLD_DATADIR=`select @@datadir`; + +SHOW VARIABLES LIKE 'innodb_doublewrite'; +SHOW VARIABLES LIKE 'innodb_fil_make_page_dirty_debug'; +SHOW VARIABLES LIKE 'innodb_saved_page_number_debug'; + +create table t1 (f1 int primary key, f2 blob) engine=innodb; + +START TRANSACTION; +INSERT INTO t1 VALUES(1, repeat('#',12)); +INSERT INTO t1 VALUES(2, repeat('+',12)); +INSERT INTO t1 VALUES(3, repeat('/',12)); +INSERT INTO t1 VALUES(4, repeat('-',12)); +INSERT INTO t1 VALUES(5, repeat('.',12)); +COMMIT WORK; + +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if first page of user +--echo # tablespace is full of zeroes. + +--echo # Wait for purge to complete +--source include/wait_innodb_all_purged.inc + +--echo # Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; + +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACEs +WHERE name = 'test/t1' INTO @space_id; + +SET GLOBAL innodb_checkpoint_disabled=1; + +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; + +INSERT INTO t1 VALUES(7, repeat('.',12)); + +SELECT @@innodb_doublewrite; +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; + +--echo # Make the first page (page_no=0) of the user tablespace +--echo # full of zeroes. +--let IBD_FILE=$MYSQLD_DATADIR/test/t1.ibd +--let PAGE_NUM="0" +--let ALL_ZEROES=1 +--source corrupt_page.inc + +--source include/start_mysqld.inc + +CHECK TABLE t1; +SELECT f1, f2 FROM t1; + +--echo # Test End +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if first page of user +--echo # tablespace is corrupted. + +SELECT space from INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; + +--echo # Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; + +SET GLOBAL innodb_checkpoint_disabled=1; + +SELECT @@innodb_doublewrite; +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; + +--echo # Corrupt the first page (page_no=0) of the user tablespace. +--let IBD_FILE=$MYSQLD_DATADIR/test/t1.ibd +--let PAGE_NUM="0" +--source corrupt_page.inc + +--source include/start_mysqld.inc + +CHECK TABLE t1; +SELECT f1, f2 FROM t1; + +--echo # Test End +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if 2nd page of user +--echo # tablespace is full of zeroes. + +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; + +--echo # Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; + +SET GLOBAL innodb_checkpoint_disabled=1; + +SELECT @@innodb_doublewrite; +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; + +--echo # Make the 2nd page (page_no=1) of the tablespace all zeroes. +--let IBD_FILE=$MYSQLD_DATADIR/test/t1.ibd +--let PAGE_NUM=1 +--let ALL_ZEROES=1 +--source corrupt_page.inc + +--source include/start_mysqld.inc + +CHECK TABLE t1; +SELECT f1, f2 FROM t1; + +--echo # Test End +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if 2nd page of user +--echo # tablespace is corrupted. + +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' into @space_id; + +--echo # Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; + +SET GLOBAL innodb_checkpoint_disabled=1; + +SELECT @@innodb_doublewrite; +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; + +--echo # Corrupt the 2nd page (page_no=1) of the user tablespace. +--let IBD_FILE=$MYSQLD_DATADIR/test/t1.ibd +--let PAGE_NUM=1 +--source corrupt_page.inc + +--source include/start_mysqld.inc + +CHECK TABLE t1; +SELECT f1, f2 FROM t1; + +--echo # Test End +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if first page of +--echo # system tablespace is full of zeroes. + +SET GLOBAL innodb_master_thread_disabled_debug=1; + +SELECT @@innodb_doublewrite; +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; + +--echo # Make the first page (page_no=0) of the system tablespace +--echo # all zeroes. +--let IBD_FILE=$MYSQLD_DATADIR/ibdata1 +--let PAGE_NUM="0" +--let ALL_ZEROES=1 +--source corrupt_page.inc + +--source include/start_mysqld.inc + +CHECK TABLE t1; +SELECT f1, f2 FROM t1; + +--echo # Test End +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if first page of +--echo # system tablespace is corrupted. + +SET GLOBAL innodb_master_thread_disabled_debug=1; + +SELECT @@innodb_doublewrite; +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; + +--echo # Corrupt the first page (page_no=0) of the system tablespace. +--let IBD_FILE=$MYSQLD_DATADIR/ibdata1 +--let PAGE_NUM="0" +--source corrupt_page.inc + +--source include/start_mysqld.inc + +CHECK TABLE t1; +SELECT f1, f2 FROM t1; + +--echo # Test End +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if 2nd page of +--echo # system tablespace is full of zeroes. + +SET GLOBAL innodb_master_thread_disabled_debug=1; + +SELECT @@innodb_doublewrite; +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; + +--echo # Make the 2nd page (page_no=1) of the system tablespace +--echo # all zeroes. +--let IBD_FILE=$MYSQLD_DATADIR/ibdata1 +--let PAGE_NUM=1 +--let ALL_ZEROES=1 +--source corrupt_page.inc + +--source include/start_mysqld.inc + +CHECK TABLE t1; +SELECT f1, f2 FROM t1; + +--echo # Test End +--echo # --------------------------------------------------------------- +--echo # Test Begin: Test if recovery works if 2nd page of +--echo # system tablespace is corrupted. + +SET GLOBAL innodb_master_thread_disabled_debug=1; + +SELECT @@innodb_doublewrite; +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; + +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); + +SET GLOBAL innodb_checkpoint_disabled = 1; + +--echo # Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; + +# Set the page dirty and force a flush to disk +--source include/expect_crash.inc +--error CR_SERVER_LOST +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; + +--echo # Make the 2nd page (page_no=1) of the system tablespace +--echo # all zeroes. +--let IBD_FILE=$MYSQLD_DATADIR/ibdata1 +--let PAGE_NUM=1 +--source corrupt_page.inc + +--let $restart_parameters= +--source include/start_mysqld.inc + +CHECK TABLE t1; +SELECT f1, f2 FROM t1; + +--echo # Test End +--echo # --------------------------------------------------------------- + +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/r/doublewrite_on_to_reduced.result b/mysql-test/suite/innodb/r/doublewrite_on_to_reduced.result new file mode 100644 index 00000000000..9c38e7c07fd --- /dev/null +++ b/mysql-test/suite/innodb/r/doublewrite_on_to_reduced.result @@ -0,0 +1,296 @@ +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite ON +SHOW VARIABLES LIKE 'innodb_fil_make_page_dirty_debug'; +Variable_name Value +innodb_fil_make_page_dirty_debug 4294967295 +SHOW VARIABLES LIKE 'innodb_saved_page_number_debug'; +Variable_name Value +innodb_saved_page_number_debug 0 +# Wait for purge to complete +create table t1 (f1 int primary key, f2 blob) engine=innodb STATS_PERSISTENT=0; +START TRANSACTION; +INSERT INTO t1 VALUES(1, repeat('#',12)); +INSERT INTO t1 VALUES(2, repeat('+',12)); +INSERT INTO t1 VALUES(3, repeat('/',12)); +INSERT INTO t1 VALUES(4, repeat('-',12)); +INSERT INTO t1 VALUES(5, repeat('.',12)); +COMMIT WORK; +SET GLOBAL innodb_fast_shutdown = 0; +# Take backup of original datadir +# restart: --datadir=BACKUP/data +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of user +# tablespace is full of zeroes. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite ON +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACEs +WHERE name = 'test/t1' INTO @space_id; +SET GLOBAL innodb_checkpoint_disabled=1; +SET GLOBAL innodb_limit_optimistic_insert_debug=2; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); +INSERT INTO t1 SELECT f1+64,f2 FROM t1; +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Make the first page (page_no=0) of the user tablespace +# full of zeroes. +# restoring original datadir +# restart: --datadir=BACKUP/data +# Test End - 1 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of user +# tablespace is corrupted. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite ON +SELECT space from INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SET GLOBAL innodb_checkpoint_disabled=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Corrupt the first page (page_no=0) of the user tablespace. +# restoring original datadir +# restart: --datadir=BACKUP/data +# Test End - 2 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of user +# tablespace is full of zeroes. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite ON +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SET GLOBAL innodb_checkpoint_disabled=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Make the 2nd page (page_no=1) of the tablespace all zeroes. +# restoring original datadir +# restart: --datadir=BACKUP/data +# Test End - 3 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of user +# tablespace is corrupted. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite ON +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' into @space_id; +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SET GLOBAL innodb_checkpoint_disabled=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); +# Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_checkpoint_disabled = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Corrupt the 2nd page (page_no=1) of the user tablespace. +# restoring original datadir +# restart: --datadir=BACKUP/data +# Test End - 4 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of +# system tablespace is full of zeroes. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite ON +SET GLOBAL innodb_master_thread_disabled_debug=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Make the first page (page_no=0) of the system tablespace +# all zeroes. +# restoring original datadir +# restart: --datadir=BACKUP/data +# Test End - 5 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of +# system tablespace is corrupted. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite ON +SET GLOBAL innodb_master_thread_disabled_debug=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Corrupt the first page (page_no=0) of the system tablespace. +# restoring original datadir +# restart: --datadir=BACKUP/data +# Test End - 6 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of +# system tablespace is full of zeroes. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite ON +SET GLOBAL innodb_master_thread_disabled_debug=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Make the 2nd page (page_no=1) of the system tablespace +# all zeroes. +# restoring original datadir +# restart: --datadir=BACKUP/data +# Test End - 7 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of +# system tablespace is corrupted. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite ON +SET GLOBAL innodb_master_thread_disabled_debug=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +INSERT INTO t1 SELECT f1+16,f2 FROM t1; +INSERT INTO t1 SELECT f1+32,f2 FROM t1; +COMMIT; +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +BEGIN; +INSERT INTO t1 VALUES (0, repeat('%', 12)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Make the 2nd page (page_no=1) of the system tablespace +# all zeroes. +# restoring original datadir +# restart +# Test End - 8 +# --------------------------------------------------------------- +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/r/doublewrite_reduced.result b/mysql-test/suite/innodb/r/doublewrite_reduced.result new file mode 100644 index 00000000000..5a1d4bfde0b --- /dev/null +++ b/mysql-test/suite/innodb/r/doublewrite_reduced.result @@ -0,0 +1,192 @@ +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite ON +SHOW VARIABLES LIKE 'innodb_fil_make_page_dirty_debug'; +Variable_name Value +innodb_fil_make_page_dirty_debug 4294967295 +SHOW VARIABLES LIKE 'innodb_saved_page_number_debug'; +Variable_name Value +innodb_saved_page_number_debug 0 +# Wait for purge to complete +create table t1 (f1 int primary key, f2 blob) engine=innodb STATS_PERSISTENT=0; +START TRANSACTION; +INSERT INTO t1 VALUES(1, repeat('#',12)); +INSERT INTO t1 VALUES(2, repeat('+',12)); +INSERT INTO t1 VALUES(3, repeat('/',12)); +INSERT INTO t1 VALUES(4, repeat('-',12)); +INSERT INTO t1 VALUES(5, repeat('.',12)); +COMMIT WORK; +SET GLOBAL innodb_fast_shutdown = 0; +# Take backup of original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of user +# tablespace is full of zeroes. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACEs +WHERE name = 'test/t1' INTO @space_id; +SET GLOBAL innodb_checkpoint_disabled=1; +SET GLOBAL innodb_limit_optimistic_insert_debug=2; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Make the first page (page_no=0) of the user tablespace +# full of zeroes. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 1 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of user +# tablespace is corrupted. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SELECT space from INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SET GLOBAL innodb_checkpoint_disabled=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Corrupt the first page (page_no=0) of the user tablespace. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 2 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of user +# tablespace is full of zeroes. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SET GLOBAL innodb_checkpoint_disabled=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Make the 2nd page (page_no=1) of the tablespace all zeroes. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 3 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of user +# tablespace is corrupted. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' into @space_id; +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SET GLOBAL innodb_checkpoint_disabled=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +# Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_checkpoint_disabled = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Corrupt the 2nd page (page_no=1) of the user tablespace. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 4 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of +# system tablespace is full of zeroes. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SET GLOBAL innodb_master_thread_disabled_debug=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Make the first page (page_no=0) of the system tablespace +# all zeroes. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 5 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of +# system tablespace is corrupted. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SET GLOBAL innodb_master_thread_disabled_debug=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Corrupt the first page (page_no=0) of the system tablespace. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 6 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of +# system tablespace is full of zeroes. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SET GLOBAL innodb_master_thread_disabled_debug=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Make the 2nd page (page_no=1) of the system tablespace +# all zeroes. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 7 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of +# system tablespace is corrupted. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SET GLOBAL innodb_master_thread_disabled_debug=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Make the 2nd page (page_no=1) of the system tablespace +# all zeroes. +# restoring original datadir +# restart +# Test End - 8 +# --------------------------------------------------------------- +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/r/doublewrite_reduced_odirect.result b/mysql-test/suite/innodb/r/doublewrite_reduced_odirect.result new file mode 100644 index 00000000000..0885eec0018 --- /dev/null +++ b/mysql-test/suite/innodb/r/doublewrite_reduced_odirect.result @@ -0,0 +1,195 @@ +########################################### +# Test with O_DIRECT enabled +########################################### +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite ON +SHOW VARIABLES LIKE 'innodb_fil_make_page_dirty_debug'; +Variable_name Value +innodb_fil_make_page_dirty_debug 4294967295 +SHOW VARIABLES LIKE 'innodb_saved_page_number_debug'; +Variable_name Value +innodb_saved_page_number_debug 0 +# Wait for purge to complete +create table t1 (f1 int primary key, f2 blob) engine=innodb STATS_PERSISTENT=0; +START TRANSACTION; +INSERT INTO t1 VALUES(1, repeat('#',12)); +INSERT INTO t1 VALUES(2, repeat('+',12)); +INSERT INTO t1 VALUES(3, repeat('/',12)); +INSERT INTO t1 VALUES(4, repeat('-',12)); +INSERT INTO t1 VALUES(5, repeat('.',12)); +COMMIT WORK; +SET GLOBAL innodb_fast_shutdown = 0; +# Take backup of original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of user +# tablespace is full of zeroes. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACEs +WHERE name = 'test/t1' INTO @space_id; +SET GLOBAL innodb_checkpoint_disabled=1; +SET GLOBAL innodb_limit_optimistic_insert_debug=2; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +INSERT INTO t1 SELECT f1+8,f2 FROM t1; +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Make the first page (page_no=0) of the user tablespace +# full of zeroes. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 1 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of user +# tablespace is corrupted. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SELECT space from INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SET GLOBAL innodb_checkpoint_disabled=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Corrupt the first page (page_no=0) of the user tablespace. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 2 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of user +# tablespace is full of zeroes. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SET GLOBAL innodb_checkpoint_disabled=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Make the 2nd page (page_no=1) of the tablespace all zeroes. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 3 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of user +# tablespace is corrupted. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' into @space_id; +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SET GLOBAL innodb_checkpoint_disabled=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +# Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_checkpoint_disabled = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Corrupt the 2nd page (page_no=1) of the user tablespace. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 4 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of +# system tablespace is full of zeroes. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SET GLOBAL innodb_master_thread_disabled_debug=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Make the first page (page_no=0) of the system tablespace +# all zeroes. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 5 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of +# system tablespace is corrupted. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SET GLOBAL innodb_master_thread_disabled_debug=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Corrupt the first page (page_no=0) of the system tablespace. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 6 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of +# system tablespace is full of zeroes. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SET GLOBAL innodb_master_thread_disabled_debug=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Make the 2nd page (page_no=1) of the system tablespace +# all zeroes. +# restoring original datadir +# restart: --innodb_doublewrite=reduced --datadir=BACKUP/data +# Test End - 7 +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of +# system tablespace is corrupted. +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite REDUCED +SET GLOBAL innodb_master_thread_disabled_debug=1; +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Make the 2nd page (page_no=1) of the system tablespace +# all zeroes. +# restoring original datadir +# restart +# Test End - 8 +# --------------------------------------------------------------- +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/r/doublewrite_reduced_to_on.result b/mysql-test/suite/innodb/r/doublewrite_reduced_to_on.result new file mode 100644 index 00000000000..b3707a6062e --- /dev/null +++ b/mysql-test/suite/innodb/r/doublewrite_reduced_to_on.result @@ -0,0 +1,307 @@ +SET GLOBAL innodb_fast_shutdown = 0; +# restart +SHOW VARIABLES LIKE 'innodb_doublewrite'; +Variable_name Value +innodb_doublewrite ON +SHOW VARIABLES LIKE 'innodb_fil_make_page_dirty_debug'; +Variable_name Value +innodb_fil_make_page_dirty_debug 4294967295 +SHOW VARIABLES LIKE 'innodb_saved_page_number_debug'; +Variable_name Value +innodb_saved_page_number_debug 0 +create table t1 (f1 int primary key, f2 blob) engine=innodb; +START TRANSACTION; +INSERT INTO t1 VALUES(1, repeat('#',12)); +INSERT INTO t1 VALUES(2, repeat('+',12)); +INSERT INTO t1 VALUES(3, repeat('/',12)); +INSERT INTO t1 VALUES(4, repeat('-',12)); +INSERT INTO t1 VALUES(5, repeat('.',12)); +COMMIT WORK; +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of user +# tablespace is full of zeroes. +# Wait for purge to complete +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACEs +WHERE name = 'test/t1' INTO @space_id; +SET GLOBAL innodb_checkpoint_disabled=1; +SET GLOBAL innodb_doublewrite=REDUCED; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +INSERT INTO t1 VALUES(7, repeat('.',12)); +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Make the first page (page_no=0) of the user tablespace +# full of zeroes. +# restart: --innodb_doublewrite=reduced +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +SELECT f1, f2 FROM t1; +f1 f2 +1 ############ +2 ++++++++++++ +3 //////////// +4 ------------ +5 ............ +7 ............ +# Test End +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of user +# tablespace is corrupted. +SELECT space from INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SET GLOBAL innodb_checkpoint_disabled=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 12)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Corrupt the first page (page_no=0) of the user tablespace. +# restart: --innodb_doublewrite=reduced +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +SELECT f1, f2 FROM t1; +f1 f2 +1 ############ +2 ++++++++++++ +3 //////////// +4 ------------ +5 ............ +7 ............ +# Test End +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of user +# tablespace is full of zeroes. +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' INTO @space_id; +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SET GLOBAL innodb_checkpoint_disabled=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Make the 2nd page (page_no=1) of the tablespace all zeroes. +# restart: --innodb_doublewrite=reduced +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +SELECT f1, f2 FROM t1; +f1 f2 +1 ############ +2 ++++++++++++ +3 //////////// +4 ------------ +5 ............ +7 ............ +# Test End +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of user +# tablespace is corrupted. +SELECT space FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE name = 'test/t1' into @space_id; +# Ensure that dirty pages of table t1 is flushed. +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; +SET GLOBAL innodb_checkpoint_disabled=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the 2nd page dirty for table t1 +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = @space_id; +ERROR HY000: Lost connection to MySQL server during query +# Corrupt the 2nd page (page_no=1) of the user tablespace. +# restart: --innodb_doublewrite=reduced +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +SELECT f1, f2 FROM t1; +f1 f2 +1 ############ +2 ++++++++++++ +3 //////////// +4 ------------ +5 ............ +7 ............ +# Test End +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of +# system tablespace is full of zeroes. +SET GLOBAL innodb_master_thread_disabled_debug=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Make the first page (page_no=0) of the system tablespace +# all zeroes. +# restart: --innodb_doublewrite=reduced +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +SELECT f1, f2 FROM t1; +f1 f2 +1 ############ +2 ++++++++++++ +3 //////////// +4 ------------ +5 ............ +7 ............ +# Test End +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if first page of +# system tablespace is corrupted. +SET GLOBAL innodb_master_thread_disabled_debug=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the first page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 0; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Corrupt the first page (page_no=0) of the system tablespace. +# restart: --innodb_doublewrite=reduced +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +SELECT f1, f2 FROM t1; +f1 f2 +1 ############ +2 ++++++++++++ +3 //////////// +4 ------------ +5 ............ +7 ............ +# Test End +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of +# system tablespace is full of zeroes. +SET GLOBAL innodb_master_thread_disabled_debug=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Make the 2nd page (page_no=1) of the system tablespace +# all zeroes. +# restart: --innodb_doublewrite=reduced +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +SELECT f1, f2 FROM t1; +f1 f2 +1 ############ +2 ++++++++++++ +3 //////////// +4 ------------ +5 ............ +7 ............ +# Test End +# --------------------------------------------------------------- +# Test Begin: Test if recovery works if 2nd page of +# system tablespace is corrupted. +SET GLOBAL innodb_master_thread_disabled_debug=1; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +REDUCED +SET GLOBAL innodb_doublewrite=ON; +SELECT @@innodb_doublewrite; +@@innodb_doublewrite +ON +BEGIN; +INSERT INTO t1 VALUES (6, repeat('%', 400)); +SET GLOBAL innodb_checkpoint_disabled = 1; +# Make the second page dirty for system tablespace +SET GLOBAL innodb_saved_page_number_debug = 1; +SET GLOBAL innodb_fil_make_page_dirty_debug = 0; +ERROR HY000: Lost connection to MySQL server during query +# Make the 2nd page (page_no=1) of the system tablespace +# all zeroes. +# restart +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +SELECT f1, f2 FROM t1; +f1 f2 +1 ############ +2 ++++++++++++ +3 //////////// +4 ------------ +5 ............ +7 ............ +# Test End +# --------------------------------------------------------------- +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/alter_kill-master.opt b/mysql-test/suite/innodb/t/alter_kill-master.opt index c5bac3bdeb4..01003e979a3 100644 --- a/mysql-test/suite/innodb/t/alter_kill-master.opt +++ b/mysql-test/suite/innodb/t/alter_kill-master.opt @@ -1,4 +1,4 @@ ---innodb-doublewrite=false +--innodb-doublewrite=OFF --innodb_buffer_pool_load_at_startup=OFF --skip-log-bin --skip-log-slave-updates diff --git a/mysql-test/suite/innodb/t/doublewrite_on_to_reduced.test b/mysql-test/suite/innodb/t/doublewrite_on_to_reduced.test new file mode 100644 index 00000000000..74bab480f67 --- /dev/null +++ b/mysql-test/suite/innodb/t/doublewrite_on_to_reduced.test @@ -0,0 +1,6 @@ +--source include/not_valgrind.inc +--source include/not_asan.inc +--source include/have_debug.inc + +--let $O_DIRECT=0 +--source ../include/doublewrite_on_to_reduced.inc diff --git a/mysql-test/suite/innodb/t/doublewrite_reduced.test b/mysql-test/suite/innodb/t/doublewrite_reduced.test new file mode 100644 index 00000000000..d1733d44592 --- /dev/null +++ b/mysql-test/suite/innodb/t/doublewrite_reduced.test @@ -0,0 +1,6 @@ +--source include/not_valgrind.inc +--source include/not_asan.inc +--source include/have_debug.inc + +--let $O_DIRECT=0 +--source ../include/doublewrite_reduced.inc diff --git a/mysql-test/suite/innodb/t/doublewrite_reduced_odirect.test b/mysql-test/suite/innodb/t/doublewrite_reduced_odirect.test new file mode 100644 index 00000000000..713b6200f35 --- /dev/null +++ b/mysql-test/suite/innodb/t/doublewrite_reduced_odirect.test @@ -0,0 +1,11 @@ +--source include/not_valgrind.inc +--source include/not_asan.inc +--source include/have_debug.inc +--source include/have_odirect.inc + +--echo ########################################### +--echo # Test with O_DIRECT enabled +--echo ########################################### + +--let $O_DIRECT=1 +--source ../include/doublewrite_reduced.inc diff --git a/mysql-test/suite/innodb/t/doublewrite_reduced_to_on.test b/mysql-test/suite/innodb/t/doublewrite_reduced_to_on.test new file mode 100644 index 00000000000..2121f3ee55d --- /dev/null +++ b/mysql-test/suite/innodb/t/doublewrite_reduced_to_on.test @@ -0,0 +1,5 @@ +--source include/not_valgrind.inc +--source include/not_asan.inc +--source include/have_debug.inc + +--source ../include/doublewrite_reduced_to_on.inc diff --git a/mysql-test/suite/sys_vars/r/innodb_doublewrite_basic.result b/mysql-test/suite/sys_vars/r/innodb_doublewrite_basic.result index 37d1b707c92..6a98f30b25a 100644 --- a/mysql-test/suite/sys_vars/r/innodb_doublewrite_basic.result +++ b/mysql-test/suite/sys_vars/r/innodb_doublewrite_basic.result @@ -3,17 +3,16 @@ COUNT(@@GLOBAL.innodb_doublewrite) 1 1 Expected SET @@GLOBAL.innodb_doublewrite=off; -ERROR HY000: Variable 'innodb_doublewrite' is a read only variable +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to OFF if doublewrite is enabled. Please shutdown and change value to OFF SET @@GLOBAL.innodb_doublewrite=on; -ERROR HY000: Variable 'innodb_doublewrite' is a read only variable SELECT COUNT(@@GLOBAL.innodb_doublewrite); COUNT(@@GLOBAL.innodb_doublewrite) 1 1 Expected -SELECT IF(@@GLOBAL.innodb_doublewrite, "ON", "OFF") = VARIABLE_VALUE +SELECT @@GLOBAL.innodb_doublewrite = VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME='innodb_doublewrite'; -IF(@@GLOBAL.innodb_doublewrite, "ON", "OFF") = VARIABLE_VALUE +@@GLOBAL.innodb_doublewrite = VARIABLE_VALUE 1 1 Expected SELECT COUNT(@@GLOBAL.innodb_doublewrite); @@ -44,3 +43,317 @@ SELECT COUNT(@@GLOBAL.innodb_doublewrite); COUNT(@@GLOBAL.innodb_doublewrite) 1 1 Expected +"##############################################" +# restart: --innodb-doublewrite=ON +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=0; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to OFF if doublewrite is enabled. Please shutdown and change value to OFF +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=3; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to FALSE if doublewrite is enabled. Please shutdown and change value to FALSE +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +TRUE +"##############################################" +# restart: --innodb-doublewrite=TRUE +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +TRUE +SET @@GLOBAL.innodb_doublewrite=0; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to OFF if doublewrite is enabled. Please shutdown and change value to OFF +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +TRUE +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +TRUE +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=3; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to FALSE if doublewrite is enabled. Please shutdown and change value to FALSE +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +TRUE +"##############################################" +# restart: --innodb-doublewrite=off +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +OFF +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +OFF +SET @@GLOBAL.innodb_doublewrite=1; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to ON if doublewrite is disabled. Please shutdown and change value to ON +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +OFF +SET @@GLOBAL.innodb_doublewrite=2; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to REDUCED if doublewrite is disabled. Please shutdown and change value to REDUCED +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +OFF +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +OFF +SET @@GLOBAL.innodb_doublewrite=4; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to TRUE if doublewrite is disabled. Please shutdown and change value to TRUE +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +OFF +"##############################################" +# restart: --innodb-doublewrite=false +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +FALSE +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +FALSE +SET @@GLOBAL.innodb_doublewrite=1; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to ON if doublewrite is disabled. Please shutdown and change value to ON +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +FALSE +SET @@GLOBAL.innodb_doublewrite=2; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to REDUCED if doublewrite is disabled. Please shutdown and change value to REDUCED +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +FALSE +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +FALSE +SET @@GLOBAL.innodb_doublewrite=4; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to TRUE if doublewrite is disabled. Please shutdown and change value to TRUE +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +FALSE +"##############################################" +# restart: --innodb-doublewrite=reduced +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=off; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to OFF if doublewrite is enabled. Please shutdown and change value to OFF +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=false; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to OFF if doublewrite is enabled. Please shutdown and change value to OFF +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=on; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=true; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=reduced; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +"##############################################" +# restart: --innodb-doublewrite +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=off; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to OFF if doublewrite is enabled. Please shutdown and change value to OFF +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=false; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to OFF if doublewrite is enabled. Please shutdown and change value to OFF +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=on; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=true; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=reduced; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +"##############################################" +# restart: --innodb-doublewrite=0 +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +OFF +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +OFF +SET @@GLOBAL.innodb_doublewrite=1; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to ON if doublewrite is disabled. Please shutdown and change value to ON +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +OFF +SET @@GLOBAL.innodb_doublewrite=2; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to REDUCED if doublewrite is disabled. Please shutdown and change value to REDUCED +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +OFF +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +OFF +SET @@GLOBAL.innodb_doublewrite=4; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to TRUE if doublewrite is disabled. Please shutdown and change value to TRUE +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +OFF +"##############################################" +# restart: --innodb-doublewrite=1 +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=0; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to OFF if doublewrite is enabled. Please shutdown and change value to OFF +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=3; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to FALSE if doublewrite is enabled. Please shutdown and change value to FALSE +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +TRUE +"##############################################" +# restart: --innodb-doublewrite=3 +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +FALSE +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +FALSE +SET @@GLOBAL.innodb_doublewrite=1; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to ON if doublewrite is disabled. Please shutdown and change value to ON +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +FALSE +SET @@GLOBAL.innodb_doublewrite=2; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to REDUCED if doublewrite is disabled. Please shutdown and change value to REDUCED +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +FALSE +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +FALSE +SET @@GLOBAL.innodb_doublewrite=4; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to TRUE if doublewrite is disabled. Please shutdown and change value to TRUE +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +FALSE +"##############################################" +# restart: --innodb-doublewrite=4 +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +TRUE +SET @@GLOBAL.innodb_doublewrite=0; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to OFF if doublewrite is enabled. Please shutdown and change value to OFF +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +TRUE +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +TRUE +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=3; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to FALSE if doublewrite is enabled. Please shutdown and change value to FALSE +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +TRUE +"##############################################" +# restart: --innodb-doublewrite=2 +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=0; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to OFF if doublewrite is enabled. Please shutdown and change value to OFF +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +ON +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=3; +ERROR HY000: Incorrect arguments to InnoDB: cannot change doublewrite mode to FALSE if doublewrite is enabled. Please shutdown and change value to FALSE +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +REDUCED +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; +@@global.innodb_doublewrite +TRUE +# restart +SET PERSIST innodb_doublewrite=ON; +ERROR HY000: Variable 'innodb_doublewrite' is a non persistent variable +SET PERSIST innodb_doublewrite=OFF; +ERROR HY000: Variable 'innodb_doublewrite' is a non persistent variable +SET PERSIST innodb_doublewrite=REDUCED; +ERROR HY000: Variable 'innodb_doublewrite' is a non persistent variable +SET PERSIST_ONLY innodb_doublewrite=ON; +ERROR HY000: Variable 'innodb_doublewrite' is a non persistent variable +SET PERSIST_ONLY innodb_doublewrite=OFF; +ERROR HY000: Variable 'innodb_doublewrite' is a non persistent variable +SET PERSIST_ONLY innodb_doublewrite=REDUCED; +ERROR HY000: Variable 'innodb_doublewrite' is a non persistent variable diff --git a/mysql-test/suite/sys_vars/t/innodb_doublewrite_basic.test b/mysql-test/suite/sys_vars/t/innodb_doublewrite_basic.test index b215af530bc..3ab03410c36 100644 --- a/mysql-test/suite/sys_vars/t/innodb_doublewrite_basic.test +++ b/mysql-test/suite/sys_vars/t/innodb_doublewrite_basic.test @@ -1,9 +1,8 @@ SELECT COUNT(@@GLOBAL.innodb_doublewrite); --echo 1 Expected ---Error ER_INCORRECT_GLOBAL_LOCAL_VAR +--Error ER_WRONG_ARGUMENTS SET @@GLOBAL.innodb_doublewrite=off; ---Error ER_INCORRECT_GLOBAL_LOCAL_VAR SET @@GLOBAL.innodb_doublewrite=on; SELECT COUNT(@@GLOBAL.innodb_doublewrite); @@ -11,7 +10,7 @@ SELECT COUNT(@@GLOBAL.innodb_doublewrite); --disable_warnings -SELECT IF(@@GLOBAL.innodb_doublewrite, "ON", "OFF") = VARIABLE_VALUE +SELECT @@GLOBAL.innodb_doublewrite = VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME='innodb_doublewrite'; --enable_warnings @@ -30,7 +29,6 @@ WHERE VARIABLE_NAME='innodb_doublewrite'; SELECT @@innodb_doublewrite = @@GLOBAL.innodb_doublewrite; --echo 1 Expected - SELECT COUNT(@@innodb_doublewrite); --echo 1 Expected @@ -45,3 +43,267 @@ SELECT COUNT(@@SESSION.innodb_doublewrite); SELECT COUNT(@@GLOBAL.innodb_doublewrite); --echo 1 Expected +--echo "##############################################" +--let $restart_parameters = "restart: --innodb-doublewrite=ON" +--source include/restart_mysqld.inc +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; + +--echo "##############################################" +--let $restart_parameters = "restart: --innodb-doublewrite=TRUE" +--source include/restart_mysqld.inc +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; + +--echo "##############################################" +--let $restart_parameters = "restart: --innodb-doublewrite=off" +--source include/restart_mysqld.inc +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; + +--echo "##############################################" +--let $restart_parameters = "restart: --innodb-doublewrite=false" +--source include/restart_mysqld.inc +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; + +--echo "##############################################" +--let $restart_parameters = "restart: --innodb-doublewrite=reduced" +--source include/restart_mysqld.inc +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=off; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=false; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=on; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=true; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=reduced; +SELECT @@global.innodb_doublewrite; + + +--echo "##############################################" +--let $restart_parameters = "restart: --innodb-doublewrite" +--source include/restart_mysqld.inc +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=off; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=false; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=on; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=true; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=reduced; +SELECT @@global.innodb_doublewrite; + + +--echo "##############################################" +--let $restart_parameters = "restart: --innodb-doublewrite=0" +--source include/restart_mysqld.inc +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; + +--echo "##############################################" +--let $restart_parameters = "restart: --innodb-doublewrite=1" +--source include/restart_mysqld.inc +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; + +--echo "##############################################" +--let $restart_parameters = "restart: --innodb-doublewrite=3" +--source include/restart_mysqld.inc +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; + +--echo "##############################################" +--let $restart_parameters = "restart: --innodb-doublewrite=4" +--source include/restart_mysqld.inc +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; + +--echo "##############################################" +--let $restart_parameters = "restart: --innodb-doublewrite=2" +--source include/restart_mysqld.inc +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=0; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=1; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=2; +SELECT @@global.innodb_doublewrite; + +--Error ER_WRONG_ARGUMENTS +SET @@GLOBAL.innodb_doublewrite=3; +SELECT @@global.innodb_doublewrite; + +SET @@GLOBAL.innodb_doublewrite=4; +SELECT @@global.innodb_doublewrite; + +--let $restart_parameters = +--source include/restart_mysqld.inc + +--Error ER_INCORRECT_GLOBAL_LOCAL_VAR +SET PERSIST innodb_doublewrite=ON; +--Error ER_INCORRECT_GLOBAL_LOCAL_VAR +SET PERSIST innodb_doublewrite=OFF; +--Error ER_INCORRECT_GLOBAL_LOCAL_VAR +SET PERSIST innodb_doublewrite=REDUCED; + +--Error ER_INCORRECT_GLOBAL_LOCAL_VAR +SET PERSIST_ONLY innodb_doublewrite=ON; +--Error ER_INCORRECT_GLOBAL_LOCAL_VAR +SET PERSIST_ONLY innodb_doublewrite=OFF; +--Error ER_INCORRECT_GLOBAL_LOCAL_VAR +SET PERSIST_ONLY innodb_doublewrite=REDUCED; diff --git a/share/messages_to_clients.txt b/share/messages_to_clients.txt index 877f562b476..635fd356b1d 100644 --- a/share/messages_to_clients.txt +++ b/share/messages_to_clients.txt @@ -9695,6 +9695,12 @@ ER_LOG_ALTER_DB ER_RAFT_UNEXPECTED_EVENT_INSIDE_TRX eng "`%s` found in the middle of a trx or with end_log_pos = 0, this is not expected in raft mode" +ER_REDUCED_DBLWR_FILE_CORRUPTED + eng "Cannot recover from reduced doublewrite buffer as page %u from batch doublewrite file is corrupted." + +ER_REDUCED_DBLWR_PAGE_FOUND + eng "Database page corruption of tablespace %s space_id: %u page_num: %u. Cannot recover it from the doublewrite buffer because it was written in reduced-doublewrite mode." + # # End of 8.0 FB MySQL error messages. # (Please read comments from the header of this section before adding error diff --git a/sql/sql_plugin_var.cc b/sql/sql_plugin_var.cc index 0396acde21a..e93c0d19de2 100644 --- a/sql/sql_plugin_var.cc +++ b/sql/sql_plugin_var.cc @@ -418,12 +418,14 @@ bool sys_var_pluginvar::global_update(THD *thd, set_var *var) { } if ((plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR && - plugin_var->flags & PLUGIN_VAR_MEMALLOC) + plugin_var->flags & PLUGIN_VAR_MEMALLOC) { rc = plugin_var_memalloc_global_update(thd, plugin_var, static_cast(tgt), *static_cast(src)); - else + } else { plugin_var->update(thd, plugin_var, tgt, src); + return (thd->is_error() ? 1 : 0); + } return rc; } diff --git a/storage/innobase/buf/buf0dblwr.cc b/storage/innobase/buf/buf0dblwr.cc index 7ba8414b4bc..86d6a0e5c60 100644 --- a/storage/innobase/buf/buf0dblwr.cc +++ b/storage/innobase/buf/buf0dblwr.cc @@ -31,6 +31,7 @@ Atomic writes handling. */ #include "buf0buf.h" #include "buf0checksum.h" +#include "os0enc.h" #include "os0thread-create.h" #include "page0zip.h" #include "srv0srv.h" @@ -78,7 +79,43 @@ ulong batch_size{}; ulong n_pages{64}; -bool enabled{true}; +ulong enabled{ON}; + +bool is_reduced_inited = false; + +bool is_enabled() { return (is_enabled_low(enabled)); } + +bool is_enabled_low(ulong mode) { + return (mode == ON || mode == REDUCED || mode == TRUEE); +} + +bool is_disabled() { return (is_disabled_low(enabled)); } + +bool is_disabled_low(ulong mode) { return (mode == OFF || mode == FALSEE); } + +const char *to_string(ulong mode) { + switch (mode) { + case OFF: + return ("OFF"); + case ON: + return ("ON"); + case TRUEE: + return ("TRUE"); + case FALSEE: + return ("FALSE"); + case REDUCED: + return ("REDUCED"); + default: + ut_ad(0); + return (""); + } + ut_ad(0); + return (""); +} + +bool is_reduced() { return (is_reduced_low(enabled)); } + +bool is_reduced_low(ulong mode) { return (mode == REDUCED); } /** Legacy dblwr buffer first segment page number. */ static page_no_t LEGACY_PAGE1; @@ -139,13 +176,27 @@ struct Page { Page &operator=(const Page &) = delete; }; +/** A record from reduced doublewrite buffer. */ +struct Page_entry { + Page_entry(space_id_t space_id, page_no_t page_num, lsn_t lsn) + : m_space_id(space_id), m_page_num(page_num), m_lsn(lsn) {} + + /** Tablespace id */ + space_id_t m_space_id; + /** Tablespace page number */ + page_no_t m_page_num; + /** Page LSN */ + lsn_t m_lsn; +}; + /** Pages recovered from the doublewrite buffer */ class Pages { public: using Buffers = std::vector>; + using Page_entries = std::vector; /** Default constructor */ - Pages() : m_pages() {} + Pages() : m_pages(), m_page_entries() {} /** Destructor */ ~Pages() noexcept { @@ -154,6 +205,7 @@ class Pages { } m_pages.clear(); + m_page_entries.clear(); } /** Add a page frame to the doublewrite recovery buffer. @@ -162,24 +214,89 @@ class Pages { @param[in] n_bytes Size in bytes */ void add(page_no_t page_no, const byte *page, uint32_t n_bytes) noexcept; + /** Add a page entry from reduced doublewrite buffer to vector + @param[in] pg_entry Reduced doublewrite buffer entry */ + void add_entry(Page_entry &pg_entry) { m_page_entries.push_back(pg_entry); } + /** Find a doublewrite copy of a page. @param[in] page_id Page number to lookup @return page frame @retval nullptr if no page was found */ const byte *find(const page_id_t &page_id) const noexcept; + /** @return true if page is recovered from the regular doublewrite buffer + @param[in] page_id Page number to lookup */ + bool is_recovered(const page_id_t &page_id) const noexcept; + + /** Recover a page from the doublewrite buffer. + @param[in] dblwr_page_no Page number if the doublewrite buffer + @param[in] space Tablespace the page belongs to + @param[in] page_no Page number in the tablespace + @param[in] page Data to write to + @return true if page was restored to the tablespace */ + bool dblwr_recover_page(page_no_t dblwr_page_no, fil_space_t *space, + page_no_t page_no, byte *page) noexcept; + + /** Checks if page in tablespace is corrupted or an all-zero page + @param[in] space Tablespace object + @param[in] page_id Page number to check for corruption + @return tuple<0> - true if corrupted + tuple<1> - true if the page is all zero page */ + std::tuple is_actual_page_corrupted(fil_space_t *space, + page_id_t &page_id); + + /** Check if page was logged in reduced doublewrite buffer mode, if so also + return the page LSN. Note if there are multiple entries of same page, we + return the max_LSN of all entries. + @param[in] page_id Page number to lookup + @retval tuple<0> - true if the page is found in reduced doublewrite + buffer nullptr if no page was found + tuple<1> - if tuple<0> is true (ie page + found in reduced dblwr mode), then return the + max page LSN */ + std::tuple find_entry(const page_id_t &page_id) const noexcept { + bool found = false; + lsn_t max_lsn = 0; + // There can be multiple entries with different LSNs, we are interested in + // the entry with max_lsn + for (auto &pe : m_page_entries) { + if (page_id.space() == pe.m_space_id && + page_id.page_no() == pe.m_page_num) { + if (pe.m_lsn > max_lsn) { + found = true; + max_lsn = pe.m_lsn; + } + } + } + + return (std::tuple(found, max_lsn)); + } + /** Recover double write buffer pages @param[in] space Tablespace pages to recover, if set - to nullptr then try and recovery all. */ - void recover(fil_space_t *space) noexcept; + to nullptr then try and recovery all. + @return DB_SUCCESS on success, false on failure */ + dberr_t recover(fil_space_t *space) noexcept; /** Check if some pages could be restored because of missing tablespace IDs */ void check_missing_tablespaces() const noexcept; + private: + /** Check if page is logged in reduced doublewrite buffer. We cannot recover + page because the entire page is not logged only an entry of + space_id, page_id, LSN is logged. So we abort the server. It is expected that + the user restores from backup + @param[in] space Tablespace pages to check in reduced + dblwr, if set to nullptr then try and recovery all. + @return DB_SUCCESS on success, others on failure */ + dberr_t reduced_recover(fil_space_t *space) noexcept; + private: /** Recovered doublewrite buffer page frames */ Buffers m_pages; + /** Page entry (space_id,page_id,LSN) from reduced doublewrite buffer */ + Page_entries m_page_entries; // Disable copying Pages(const Pages &) = delete; @@ -253,7 +370,7 @@ class Double_write { Double_write(uint16_t id, uint32_t n_pages) noexcept; /** Destructor */ - ~Double_write() noexcept; + virtual ~Double_write() noexcept; /** @return instance ID */ uint16_t id() const noexcept MY_ATTRIBUTE((warn_unused_result)) { @@ -269,9 +386,21 @@ class Double_write { @param[in] buf_pool_index Buffer pool instance number. @param[in] flush_type LRU or Flush list write. @return instance that will handle the flush to disk. */ - static Double_write *instance( - buf_flush_t flush_type, - uint32_t buf_pool_index) noexcept MY_ATTRIBUTE((warn_unused_result)) { + static Double_write *instance(buf_flush_t flush_type, + uint32_t buf_pool_index) noexcept + MY_ATTRIBUTE((warn_unused_result)) { + return (instance_low(flush_type, buf_pool_index, is_reduced())); + } + + /** @return the double write instance to use for flushing. + @param[in] buf_pool_index Buffer pool instance number. + @param[in] flush_type LRU or Flush list write. + @param[in] is_reduced true if REDUCED mode is on + @return instance that will handle the flush to disk. */ + static Double_write *instance_low(buf_flush_t flush_type, + uint32_t buf_pool_index, + bool is_reduced) noexcept + MY_ATTRIBUTE((warn_unused_result)) { ut_a(buf_pool_index < srv_buf_pool_instances); auto midpoint = s_instances->size() / 2; @@ -281,7 +410,7 @@ class Double_write { i += midpoint; } - return s_instances->at(i); + return (is_reduced ? s_r_instances->at(i) : s_instances->at(i)); } /** Wait for any pending batch to complete. @@ -332,7 +461,7 @@ class Double_write { double write file, sync the file if required and then write to the data files. @param[in] flush_type LRU or FLUSH request. */ - void write_pages(buf_flush_t flush_type) noexcept; + virtual void write_pages(buf_flush_t flush_type) noexcept; /** Force a flush of the page queue. @param[in] flush_type FLUSH LIST or LRU LIST flush. */ @@ -401,6 +530,13 @@ class Double_write { static dberr_t create_batch_segments( uint32_t segments_per_file) noexcept MY_ATTRIBUTE((warn_unused_result)); + /** Create the Reduced batch write segments. These segments are mapped + to separate file which has extension .bdblwr + @param[in] segments_per_file Number of configured segments per file. + @return DB_SUCCESS or error code. */ + static dberr_t create_reduced_batch_segments() noexcept + MY_ATTRIBUTE((warn_unused_result)); + /** Create the single page flush segments. @param[in] segments_per_file Number of configured segments per file. @return DB_SUCCESS or error code. */ @@ -475,9 +611,14 @@ class Double_write { if (s_instances == nullptr) { return; } - auto dblwr = instance(flush_type, buf_pool_index); + auto dblwr = instance_low(flush_type, buf_pool_index, false); dblwr->force_flush(flush_type); + + if (is_reduced_inited) { + auto dblwr = instance_low(flush_type, buf_pool_index, true); + dblwr->force_flush(flush_type); + } } /** Load the doublewrite buffer pages from an external file. @@ -488,11 +629,25 @@ class Double_write { static dberr_t load(dblwr::File &file, recv::Pages *pages) noexcept MY_ATTRIBUTE((warn_unused_result)); + /** Load the reduced doublewrite buffer page entries from an reduced batch + double write buffer file (.bdblwr) + @param[in,out] file File handle + @param[in,out] pages For storing the doublewrite pages + read from the file + @return DB_SUCCESS or error code */ + static dberr_t load_reduced_batch(dblwr::File &file, recv::Pages *pages) noexcept + MY_ATTRIBUTE((warn_unused_result)); + /** Write zeros to the file if it is "empty" - @param[in] file File instance. + @param[in] file File instance. @param[in] n_pages Size in physical pages. + @param[in] phy_size Physical page size in DBLWR file + (For reduced DBLW file, it is not + UNIV_PAGE_SIZE, it is hardcoded to 8K + page size) @return DB_SUCCESS or error code */ - static dberr_t init_file(dblwr::File &file, uint32_t n_pages) noexcept + static dberr_t init_file(dblwr::File &file, uint32_t n_pages, + uint32_t phy_size=univ_page_size.physical()) noexcept MY_ATTRIBUTE((warn_unused_result)); /** Reset the size in bytes to the configured size. @@ -500,6 +655,14 @@ class Double_write { @param[in] truncate Truncate the file to configured size if true. */ static void reset_file(dblwr::File &file, bool truncate) noexcept; + /** Reset the size in bytes to the configured size. + @param[in,out] file File to reset + @param[in] pages_per_file Number of pages to be created + in doublewrite file + @param[in] phy_size physical page size */ + static void reduced_reset_file(dblwr::File &file, + uint32_t pages_per_file, uint32_t phy_size) noexcept; + /** Reset the size in bytes to the configured size of all files. */ static void reset_files() noexcept { for (auto &file : Double_write::s_files) { @@ -512,6 +675,10 @@ class Double_write { @return DB_SUCCESS or error code */ static dberr_t create_v2() noexcept MY_ATTRIBUTE((warn_unused_result)); + /** Create the data structures for reduced doublewrite buffer + @return DB_SUCCESS or error code */ + static dberr_t create_reduced() noexcept MY_ATTRIBUTE((warn_unused_result)); + #ifndef _WIN32 /** @return true if we need to fsync to disk */ static bool is_fsync_required() noexcept MY_ATTRIBUTE((warn_unused_result)) { @@ -578,7 +745,7 @@ class Double_write { header page. */ static byte *get(mtr_t *mtr) noexcept MY_ATTRIBUTE((warn_unused_result)); - private: + protected: using Segments = mpmc_bq; using Instances = std::vector; using Batch_segments = mpmc_bq; @@ -610,16 +777,43 @@ class Double_write { /** File segments to use for single page writes. */ static Segments *s_single_segments; + /** File segments to use for LRU batched writes in reduced dblwr mode */ + static Batch_segments *s_r_LRU_batch_segments; + + /** File segments to use for flush list batched writes in reduced dblwr mode + */ + static Batch_segments *s_r_flush_list_batch_segments; + /** For indexing batch segments by ID. */ static std::vector s_segments; + /** Utility function to free batch segments + @param[in] segments batch segment to free */ + static void free_segments(Batch_segments *&segments) noexcept; + + /** Last used batch_id for regular batch segments. Any id greater + than this belongs to reduced double write */ + static uint32_t s_regular_last_batch_id; + + /** @return true if batch belonged to reduced dblwr. When returning + a batch segment to lock-free queue, we should know which lock-free + queue(Batch_segments) to return to + @param[in] batch_id Batch segment id */ + static bool is_reduced_batch_id(uint32_t batch_id); + public: /** Files to use for atomic writes. */ static std::vector s_files; + /** Reduced batch doublewrite files to use for atomic writes. */ + static std::vector s_r_files; + /** The global instances */ static Instances *s_instances; + /** The global Reduced Doublewrite instances */ + static Instances *s_r_instances; + // Disable copying Double_write(const Double_write &) = delete; Double_write(const Double_write &&) = delete; @@ -636,8 +830,21 @@ class Segment { @param[in] n_pages Number of pages in the segment. */ Segment(dblwr::File &file, page_no_t start, uint32_t n_pages) : m_file(file), - m_start(start * univ_page_size.physical()), - m_end(m_start + (n_pages * univ_page_size.physical())) {} + m_phy_size(univ_page_size.physical()), + m_start(start * m_phy_size), + m_end(m_start + (n_pages * m_phy_size)) {} + + /** Constructor. + @param[in] file File that owns the segment. + @param[in] phy_size Size of an entry in segment + @param[in] start Offset (page number) of segment in the file. + @param[in] n_pages Number of pages in the segment. */ + Segment(dblwr::File &file, uint32_t phy_size, page_no_t start, + uint32_t n_pages) + : m_file(file), + m_phy_size(phy_size), + m_start(start * m_phy_size), + m_end(m_start + (n_pages * m_phy_size)) {} /** Destructor. */ virtual ~Segment() {} @@ -662,6 +869,9 @@ class Segment { /** File that owns the segment. */ dblwr::File &m_file; + /** Physical page size of each entry/Segment */ + uint32_t m_phy_size{}; + /** Physical offset in the file for the segment. */ os_offset_t m_start{}; @@ -689,6 +899,18 @@ class Batch_segment : public Segment { reset(); } + /** Constructor. + @param[in] id Segment ID. + @param[in] file File that owns the segment. + @param[in] phy_size physical size of each segment entry + @param[in] start Offset (page number) of segment in the file. + @param[in] n_pages Number of pages in the segment. */ + Batch_segment(uint16_t id, dblwr::File &file, uint32_t phy_size, + page_no_t start, uint32_t n_pages) + : Segment(file, phy_size, start, n_pages), m_id(id) { + reset(); + } + /** Destructor. */ virtual ~Batch_segment() noexcept { ut_a(m_written.load(std::memory_order_relaxed) == 0); @@ -702,6 +924,11 @@ class Batch_segment : public Segment { @param[in] buffer Buffer to write. */ void write(const Buffer &buffer) noexcept; + /** Write a batch to the segment. + @param[in] buffer Buffer to write + @param[in] len amount of data to write */ + void write(const byte *buf, uint32_t len) noexcept; + /** Called on page write completion. @return if batch ended. */ bool write_complete() noexcept MY_ATTRIBUTE((warn_unused_result)) { @@ -756,14 +983,178 @@ class Batch_segment : public Segment { std::atomic_int m_written{}; }; +/** Reduced doublewrite implementation. Uses separate +.bdblwr files and can coexist with regular doublewrite buffer +implemenation */ +class Reduced_double_write : public Double_write { + public: + /** Constructor + @param[in] id Instance ID + @param[in] n_pages Number of pages handled by this instance. */ + Reduced_double_write(uint16_t id, uint32_t n_pages) + : Double_write(id, n_pages), m_buf(nullptr), m_page(nullptr) {} + + /** Destructor */ + ~Reduced_double_write() { + if (m_buf != nullptr) { + ut_free(m_buf); + } + } + + /** Process the requests in the flush queue, write the space_id, page_id, LSN + to the reduced double write file (.bdblwr), sync the file if required and + then write to the data files. + @param[in] flush_type LRU or FLUSH request. */ + void write_pages(buf_flush_t flush_type) noexcept { + ut_ad(mutex_own(&m_mutex)); + ut_a(!m_buffer.empty()); + + allocate(); + + uint16_t data_len{}; + byte *ptr = m_page + REDUCED_HEADER_SIZE; + byte *ptr_orig MY_ATTRIBUTE((unused)) = ptr; + + for (uint32_t i = 0; i < m_buf_pages.size(); ++i) { + const auto bpage = m_buf_pages.m_pages[i]; + + mach_write_to_4(ptr, bpage->id.space()); + ptr += 4; + + mach_write_to_4(ptr, bpage->id.page_no()); + ptr += 4; + + page_t *frame = bpage->zip.data; + + if (!frame) { + frame = ((buf_block_t *)bpage)->frame; + } + + lsn_t frame_lsn = mach_read_from_8(frame + FIL_PAGE_LSN); + + mach_write_to_8(ptr, frame_lsn); + ptr += 8; + + data_len += REDUCED_ENTRY_SIZE; + } + + ut_ad(ptr - ptr_orig == data_len); + ut_ad(data_len < REDUCED_DATA_SIZE); + + // Calculate checksum for the data written + uint32_t checksum = calculate_checksum(data_len); + + Batch_segment *batch_segment{}; + + auto segments = flush_type == BUF_FLUSH_LRU ? s_r_LRU_batch_segments + : s_r_flush_list_batch_segments; + + while (!segments->dequeue(batch_segment)) { + os_thread_yield(); + } + + // Create Page header + create_header(batch_segment->id(), checksum, data_len, flush_type); + + ut_ad(data_len / REDUCED_ENTRY_SIZE == m_buf_pages.size()); + + batch_segment->start(this); + + batch_segment->write(m_page, REDUCED_BATCH_PAGE_SIZE); + + m_buffer.clear(); + clear(); + +#ifndef _WIN32 + if (is_fsync_required()) { + batch_segment->flush(); + } +#endif /* !_WIN32 */ + + batch_segment->set_batch_size(m_buf_pages.size()); + + for (uint32_t i = 0; i < m_buf_pages.size(); ++i) { + const auto bpage = m_buf_pages.m_pages[i]; + + ut_d(auto page_id = bpage->id); + + bpage->set_dblwr_batch_id(batch_segment->id()); + + auto err = write_to_datafile(bpage, false); + ut_a(err == DB_SUCCESS || err == DB_TABLESPACE_DELETED); + +#ifdef UNIV_DEBUG + if (dblwr::Force_crash.equals_to(page_id)) { + DBUG_SUICIDE(); + } +#endif /* UNIV_DEBUG */ + } + + srv_stats.dblwr_writes.inc(); + + m_buf_pages.clear(); + + os_aio_simulated_wake_handler_threads(); + } + + private: + /** Allocate a temporary buffer for writing page entries */ + void allocate() { + ut_ad(mutex_own(&m_mutex)); + + if (m_buf != nullptr) { + return; + } + + m_buf = static_cast(ut_zalloc_nokey(2 * REDUCED_BATCH_PAGE_SIZE)); + + /* Align the memory for file i/o if we might have O_DIRECT set */ + m_page = static_cast(ut_align(m_buf, REDUCED_BATCH_PAGE_SIZE)); + } + + /** Clear the temporary buffer used for writing reduced dblwr page */ + void clear() { memset(m_page, 0, REDUCED_BATCH_PAGE_SIZE); } + + /** Create Reduced dblwr page header + @param[in] batch_id Batch_id of the Reduced dblwr segment + @param[in] checksum Checksum of the page + @param[in] data_len Length of data in page + @param[in] flush_type LRU of FLUSH_LIST type*/ + void create_header(uint32_t batch_id, uint32_t checksum, uint16_t data_len, + buf_flush_t flush_type) { + mach_write_to_4(m_page + RB_OFF_BATCH_ID, batch_id); + mach_write_to_4(m_page + RB_OFF_CHECKSUM, checksum); + mach_write_to_2(m_page + RB_OFF_DATA_LEN, data_len); + m_page[RB_OFF_BATCH_TYPE] = flush_type; + } + + /** Calculate checksum for the Reduced dblwr page + @param[in] data_len amount of data in page + @return checksum calcuated */ + uint32_t calculate_checksum(uint16_t data_len) { + return (ut_crc32(m_page + REDUCED_HEADER_SIZE, data_len)); + } + + private: + /** Un-aligned temporary buffer */ + byte *m_buf; + /** aligned temporary buffer. Created from m_buf */ + byte *m_page; +}; + uint32_t Double_write::s_n_instances{}; std::vector Double_write::s_files; +std::vector Double_write::s_r_files; Double_write::Segments *Double_write::s_single_segments{}; Double_write::Batch_segments *Double_write::s_LRU_batch_segments{}; Double_write::Batch_segments *Double_write::s_flush_list_batch_segments{}; std::vector Double_write::s_segments{}; - Double_write::Instances *Double_write::s_instances{}; +uint32_t Double_write::s_regular_last_batch_id{}; + +Double_write::Batch_segments *Double_write::s_r_LRU_batch_segments{}; +Double_write::Batch_segments *Double_write::s_r_flush_list_batch_segments{}; +Double_write::Instances *Double_write::s_r_instances{}; Double_write::Double_write(uint16_t id, uint32_t n_pages) noexcept : m_id(id), m_buffer(n_pages), m_buf_pages(n_pages) { @@ -833,6 +1224,10 @@ void Batch_segment::write(const Buffer &buffer) noexcept { Segment::write(buffer.begin(), buffer.size()); } +void Batch_segment::write(const byte *buf, uint32_t len) noexcept { + Segment::write(buf, len); +} + dberr_t Double_write::create_v2() noexcept { ut_a(!s_files.empty()); ut_a(s_instances == nullptr); @@ -867,6 +1262,51 @@ dberr_t Double_write::create_v2() noexcept { return err; } +dberr_t Double_write::create_reduced() noexcept { + ut_a(!s_files.empty()); + ut_a(s_r_instances == nullptr); + + s_r_instances = UT_NEW_NOKEY(Instances{}); + + if (s_r_instances == nullptr) { + return DB_OUT_OF_MEMORY; + } + + dberr_t err{DB_SUCCESS}; + + for (uint32_t i = 0; i < s_n_instances; ++i) { + auto ptr = UT_NEW_NOKEY(Reduced_double_write(i, dblwr::n_pages)); + + if (ptr == nullptr) { + err = DB_OUT_OF_MEMORY; + break; + } + + s_r_instances->push_back(ptr); + } + + if (err != DB_SUCCESS) { + for (auto &dblwr : *s_r_instances) { + UT_DELETE(dblwr); + } + UT_DELETE(s_r_instances); + s_r_instances = nullptr; + } + + return err; +} + +void Double_write::free_segments(Batch_segments *&segments) noexcept { + if (segments != nullptr) { + Batch_segment *s{}; + while (segments->dequeue(s)) { + UT_DELETE(s); + } + UT_DELETE(segments); + segments = nullptr; + } +} + void Double_write::shutdown() noexcept { if (s_instances == nullptr) { return; @@ -876,31 +1316,31 @@ void Double_write::shutdown() noexcept { UT_DELETE(dblwr); } + if (s_r_instances != nullptr) { + for (auto dblwr : *s_r_instances) { + UT_DELETE(dblwr); + } + } + for (auto &file : s_files) { if (file.m_pfs.m_file != OS_FILE_CLOSED) { os_file_close(file.m_pfs); } } - s_files.clear(); - - if (s_LRU_batch_segments != nullptr) { - Batch_segment *s{}; - while (s_LRU_batch_segments->dequeue(s)) { - UT_DELETE(s); + for (auto &file : s_r_files) { + if (file.m_pfs.m_file != OS_FILE_CLOSED) { + os_file_close(file.m_pfs); } - UT_DELETE(s_LRU_batch_segments); - s_LRU_batch_segments = nullptr; } - if (s_flush_list_batch_segments != nullptr) { - Batch_segment *s{}; - while (s_flush_list_batch_segments->dequeue(s)) { - UT_DELETE(s); - } - UT_DELETE(s_flush_list_batch_segments); - s_flush_list_batch_segments = nullptr; - } + s_files.clear(); + s_r_files.clear(); + + free_segments(s_LRU_batch_segments); + free_segments(s_flush_list_batch_segments); + free_segments(s_r_LRU_batch_segments); + free_segments(s_r_flush_list_batch_segments); if (s_single_segments != nullptr) { Segment *s{}; @@ -912,7 +1352,9 @@ void Double_write::shutdown() noexcept { } UT_DELETE(s_instances); + UT_DELETE(s_r_instances); s_instances = nullptr; + s_r_instances = nullptr; } void Double_write::check_page_lsn(const page_t *page) noexcept { @@ -1138,16 +1580,38 @@ void Double_write::reset_file(dblwr::File &file, bool truncate) noexcept { } } -dberr_t Double_write::init_file(dblwr::File &file, uint32_t n_pages) noexcept { +void Double_write::reduced_reset_file(dblwr::File &file, + uint32_t pages_per_file, + uint32_t phy_size) noexcept { + auto cur_size = os_file_get_size(file.m_pfs); + auto new_size = pages_per_file * phy_size; + auto pfs_file = file.m_pfs; + + if (new_size > cur_size) { + auto err = + os_file_write_zeros(pfs_file, file.m_name.c_str(), phy_size, cur_size, + new_size - cur_size, srv_read_only_mode); + + if (err != DB_SUCCESS) { + ib::fatal(ER_IB_MSG_DBLWR_1321, file.m_name.c_str()); + } + + ib::info(ER_IB_MSG_DBLWR_1307) + << file.m_name << " size increased to " << new_size << " bytes " + << "from " << cur_size << " bytes"; + } +} + +dberr_t Double_write::init_file(dblwr::File &file, uint32_t n_pages, + uint32_t phy_size) noexcept { auto pfs_file = file.m_pfs; auto size = os_file_get_size(pfs_file); ut_ad(dblwr::File::s_n_pages > 0); if (size == 0) { - auto err = os_file_write_zeros( - pfs_file, file.m_name.c_str(), univ_page_size.physical(), 0, - n_pages * univ_page_size.physical(), srv_read_only_mode); + auto err = os_file_write_zeros(pfs_file, file.m_name.c_str(), phy_size, 0, + n_pages * phy_size, srv_read_only_mode); if (err != DB_SUCCESS) { return err; @@ -1378,64 +1842,235 @@ dberr_t Double_write::load(dblwr::File &file, recv::Pages *pages) noexcept { return DB_SUCCESS; } -void Double_write::write_pages(buf_flush_t flush_type) noexcept { - ut_ad(mutex_own(&m_mutex)); - ut_a(!m_buffer.empty()); +/** Reduced doublewrite file deserializer. Used during crash recovery. */ +class Reduced_batch_deserializer { + public: + /** Constructor + @param[in] buf Buffer to hold the Reduced dblwr pages + @param[in] n_pages Number of reduced dblwr pages */ + explicit Reduced_batch_deserializer(Buffer *buf, uint32_t n_pages) + : m_buf(buf), m_n_pages(n_pages) {} + + /** Deserialize page and call Functor f for each page_entry found + from reduced dblwr page + @param[in] f Functor to process page entry from dblwr page + @return DB_SUCCESS on sucess, others of checksum or parsing failures */ + template + dberr_t deserialize(F &f) { + auto page = m_buf->begin(); + for (uint32_t i = 0; i < m_n_pages; ++i) { + if (is_zeroes(page)) { + page += REDUCED_BATCH_PAGE_SIZE; + continue; + } - Batch_segment *batch_segment{}; + dberr_t err = parse_page(page, f); + if (err != DB_SUCCESS) { + ib::error(ER_REDUCED_DBLWR_FILE_CORRUPTED, i); + return (err); + } - auto segments = flush_type == BUF_FLUSH_LRU ? s_LRU_batch_segments - : s_flush_list_batch_segments; + page += REDUCED_BATCH_PAGE_SIZE; + } - while (!segments->dequeue(batch_segment)) { - os_thread_yield(); + return (DB_SUCCESS); } - batch_segment->start(this); + private: + /** Parse reduced dblwr batch page header + @param[in] page Page to parse + @param[in] data_len length of data in page + @return DB_SUCCESS on success, others on failure */ + dberr_t parse_header(const byte *page, uint16_t *data_len) noexcept { + // uint32_t batch_id = mach_read_from_4(page + RB_OFF_BATCH_ID); + uint32_t checksum = mach_read_from_4(page + RB_OFF_CHECKSUM); + *data_len = mach_read_from_2(page + RB_OFF_DATA_LEN); + // buf_flush_t flush_type = + // static_cast(page[RB_OFF_BATCH_TYPE]); + + if (*data_len == 0) { + return (DB_CORRUPTION); + } - batch_segment->write(m_buffer); + if (*data_len % REDUCED_ENTRY_SIZE != 0) { + return (DB_CORRUPTION); + } - m_buffer.clear(); + uint32_t calc_checksum = ut_crc32(page + REDUCED_HEADER_SIZE, *data_len); -#ifndef _WIN32 - if (is_fsync_required()) { - batch_segment->flush(); + if (checksum != calc_checksum) { + return (DB_CORRUPTION); + } + return (DB_SUCCESS); } -#endif /* !_WIN32 */ - - batch_segment->set_batch_size(m_buf_pages.size()); - for (uint32_t i = 0; i < m_buf_pages.size(); ++i) { - const auto bpage = m_buf_pages.m_pages[i]; + /* Utility function to parse page + @param[in] page reduced dblwr batch page + @param[in] f Callback function that process page entries + @return DB_SUCCESS on success */ + template + dberr_t parse_page(const byte *page, F &f) noexcept { + uint16_t data_len{}; - ut_d(auto page_id = bpage->id); + dberr_t err = parse_header(page, &data_len); - bpage->set_dblwr_batch_id(batch_segment->id()); + if (err != DB_SUCCESS) { + return (err); + } - auto err = write_to_datafile(bpage, false); - ut_a(err == DB_SUCCESS); + parse_page_data(page, data_len, f); -#ifdef UNIV_DEBUG - if (dblwr::Force_crash.equals_to(page_id)) { - DBUG_SUICIDE(); - } -#endif /* UNIV_DEBUG */ + return (DB_SUCCESS); } - srv_stats.dblwr_writes.inc(); + /** Utility function to parse page data + @param[in] page reduced dblwr batch page + @param[in] data_len length of data in page + @param[in] f Callback function that process page entries */ + template + void parse_page_data(const byte *page, uint16_t data_len, F &f) noexcept { + const byte *page_data = page + REDUCED_HEADER_SIZE; + const byte *page_start MY_ATTRIBUTE((unused)) = page + REDUCED_HEADER_SIZE; + const uint32_t expected_entries = data_len / REDUCED_ENTRY_SIZE; - m_buf_pages.clear(); + for (uint32_t entry = 1; entry <= expected_entries; ++entry) { + space_id_t space_id = mach_read_from_4(page_data); + page_data += 4; - os_aio_simulated_wake_handler_threads(); -} + page_no_t page_num = mach_read_from_4(page_data); + page_data += 4; -dberr_t Double_write::create_batch_segments( - uint32_t segments_per_file) noexcept { - const uint32_t n_segments = segments_per_file * s_files.size(); + lsn_t lsn = mach_read_from_8(page_data); + page_data += 8; - const auto n = std::max(ulint{2}, ut_2_power_up((n_segments + 1))); + Page_entry pe(space_id, page_num, lsn); + f(pe); + } - ut_a(s_LRU_batch_segments == nullptr); + ut_ad(page_data - page_start == (expected_entries * REDUCED_ENTRY_SIZE)); + } + + /** @return true if dblwr page is an all-zero page + @param[in] page dblwr page in batch file (.bdblwr) */ + bool is_zeroes(const byte *page) { + for (ulint i = 0; i < REDUCED_BATCH_PAGE_SIZE; i++) { + if (page[i] != 0) { + return (false); + } + } + return (true); + } + + private: + /** Temporary buffer to hold Reduced dblwr pages */ + Buffer *m_buf; + /** Number of reduced dblwr pages */ + uint32_t m_n_pages; +}; + +dberr_t Double_write::load_reduced_batch(dblwr::File &file, + recv::Pages *pages) noexcept { + os_offset_t size = os_file_get_size(file.m_pfs); + + if (srv_read_only_mode) { + ib::info() << "Skipping doublewrite buffer processing due to " + "InnoDB running in read only mode"; + return (DB_SUCCESS); + } + + if (size == 0) { + /* Double write buffer is empty. */ + ib::info(ER_IB_MSG_DBLWR_1285, file.m_name.c_str()); + + return DB_SUCCESS; + } + + if ((size % REDUCED_BATCH_PAGE_SIZE) != 0) { + ib::warn(ER_IB_MSG_DBLWR_1319, file.m_name.c_str(), (ulint)size, + (ulint)REDUCED_BATCH_PAGE_SIZE); + } + + const uint32_t n_pages = size / REDUCED_BATCH_PAGE_SIZE; + Buffer buffer(n_pages, REDUCED_BATCH_PAGE_SIZE); + IORequest read_request(IORequest::READ); + + read_request.disable_compression(); + + auto err = os_file_read(read_request, file.m_name.c_str(), file.m_pfs, + buffer.begin(), 0, buffer.capacity()); + + if (err != DB_SUCCESS) { + ib::error(ER_IB_MSG_DBLWR_1301, ut_strerr(err)); + + return err; + } + + auto page_entry_processor = [&](Page_entry &pe) { pages->add_entry(pe); }; + + Reduced_batch_deserializer rbd(&buffer, n_pages); + err = rbd.deserialize(page_entry_processor); + + return (err); +} + +void Double_write::write_pages(buf_flush_t flush_type) noexcept { + ut_ad(mutex_own(&m_mutex)); + ut_a(!m_buffer.empty()); + + Batch_segment *batch_segment{}; + + auto segments = flush_type == BUF_FLUSH_LRU ? s_LRU_batch_segments + : s_flush_list_batch_segments; + + while (!segments->dequeue(batch_segment)) { + os_thread_yield(); + } + + batch_segment->start(this); + + batch_segment->write(m_buffer); + + m_buffer.clear(); + +#ifndef _WIN32 + if (is_fsync_required()) { + batch_segment->flush(); + } +#endif /* !_WIN32 */ + + batch_segment->set_batch_size(m_buf_pages.size()); + + for (uint32_t i = 0; i < m_buf_pages.size(); ++i) { + const auto bpage = m_buf_pages.m_pages[i]; + + ut_d(auto page_id = bpage->id); + + bpage->set_dblwr_batch_id(batch_segment->id()); + + auto err = write_to_datafile(bpage, false); + ut_a(err == DB_SUCCESS); + +#ifdef UNIV_DEBUG + if (dblwr::Force_crash.equals_to(page_id)) { + DBUG_SUICIDE(); + } +#endif /* UNIV_DEBUG */ + } + + srv_stats.dblwr_writes.inc(); + + m_buf_pages.clear(); + + os_aio_simulated_wake_handler_threads(); +} + +dberr_t Double_write::create_batch_segments( + uint32_t segments_per_file) noexcept { + const uint32_t n_segments = segments_per_file * s_files.size(); + + const auto n = std::max(ulint{2}, ut_2_power_up((n_segments + 1))); + + ut_a(s_LRU_batch_segments == nullptr); s_LRU_batch_segments = UT_NEW_NOKEY(Batch_segments(n)); @@ -1475,7 +2110,58 @@ dberr_t Double_write::create_batch_segments( auto success = segments->enqueue(s); ut_a(success); s_segments.push_back(s); + s_regular_last_batch_id = id; + } + } + + return DB_SUCCESS; +} + +dberr_t Double_write::create_reduced_batch_segments() noexcept { + const auto n = + std::max(ulint{2}, ut_2_power_up(Double_write::s_n_instances / 2)); + + ut_a(s_r_LRU_batch_segments == nullptr); + + s_r_LRU_batch_segments = UT_NEW_NOKEY(Batch_segments(n)); + + if (s_r_LRU_batch_segments == nullptr) { + return DB_OUT_OF_MEMORY; + } + + ut_a(s_r_flush_list_batch_segments == nullptr); + + s_r_flush_list_batch_segments = UT_NEW_NOKEY(Batch_segments(n)); + + if (s_r_flush_list_batch_segments == nullptr) { + return DB_OUT_OF_MEMORY; + } + + const uint32_t total_pages = Double_write::s_n_instances; + + // Batch_ids for new segments should start after old batch ids + uint16_t id = Double_write::s_segments.size(); + + // Reduced Batch file + auto &file = Double_write::s_r_files[0]; + for (uint32_t i = 0; i < total_pages; ++i, ++id) { + auto s = UT_NEW_NOKEY(Batch_segment(id, file, 8192, i, 1)); + + if (s == nullptr) { + return DB_OUT_OF_MEMORY; + } + + Batch_segments *segments{}; + + if (i < total_pages / 2) { + segments = s_r_LRU_batch_segments; + } else { + segments = s_r_flush_list_batch_segments; } + + auto success = segments->enqueue(s); + ut_a(success); + s_segments.push_back(s); } return DB_SUCCESS; @@ -1529,7 +2215,7 @@ dberr_t dblwr::write(buf_flush_t flush_type, buf_page_t *bpage, dberr_t err; if (srv_read_only_mode || fsp_is_system_temporary(bpage->id.space()) || - !dblwr::enabled || Double_write::s_instances == nullptr) { + !dblwr::is_enabled() || Double_write::s_instances == nullptr) { /* Disable use of double-write buffer for temporary tablespace. Temporary tablespaces are never recovered, therefore we don't care about torn writes. */ @@ -1564,6 +2250,11 @@ dberr_t dblwr::write(buf_flush_t flush_type, buf_page_t *bpage, return err; } +bool Double_write::is_reduced_batch_id(uint32_t batch_id) { + ut_ad(s_regular_last_batch_id != 0); + return (batch_id > s_regular_last_batch_id); +} + void Double_write::write_complete(buf_page_t *bpage, buf_flush_t flush_type) noexcept { if (s_instances == nullptr) { @@ -1588,9 +2279,17 @@ void Double_write::write_complete(buf_page_t *bpage, batch_segment->reset(); - auto segments = (flush_type == BUF_FLUSH_LRU) - ? Double_write::s_LRU_batch_segments - : Double_write::s_flush_list_batch_segments; + Batch_segments *segments{nullptr}; + + if (is_reduced_batch_id(batch_id)) { + segments = (flush_type == BUF_FLUSH_LRU) + ? Double_write::s_r_LRU_batch_segments + : Double_write::s_r_flush_list_batch_segments; + } else { + segments = (flush_type == BUF_FLUSH_LRU) + ? Double_write::s_LRU_batch_segments + : Double_write::s_flush_list_batch_segments; + } fil_flush_file_spaces(FIL_TYPE_TABLESPACE, FLUSH_FROM_DOUBLEWRITE); @@ -1611,9 +2310,9 @@ void dblwr::write_complete(buf_page_t *bpage, buf_flush_t flush_type) noexcept { Double_write::write_complete(bpage, flush_type); } -void dblwr::recv::recover(recv::Pages *pages, fil_space_t *space) noexcept { +dberr_t dblwr::recv::recover(recv::Pages *pages, fil_space_t *space) noexcept { #ifndef UNIV_HOTBACKUP - pages->recover(space); + return (pages->recover(space)); #endif /* UNIV_HOTBACKUP */ } @@ -1622,9 +2321,11 @@ void dblwr::recv::recover(recv::Pages *pages, fil_space_t *space) noexcept { @param[in] id Instance ID. @param[out] file File handle. @param[in] file_type The file type. +@param[in] file_extension .dblwr/.bdblwr @return DB_SUCCESS if all went well. */ static dberr_t dblwr_file_open(const std::string &dir_name, int id, - dblwr::File &file, ulint file_type) noexcept { + dblwr::File &file, ulint file_type, + ib_file_suffix extension = DWR) noexcept { bool exists; os_file_type_t type; std::string dir(dir_name); @@ -1664,7 +2365,7 @@ static dberr_t dblwr_file_open(const std::string &dir_name, int id, file.m_name += std::to_string(srv_page_size) + "_" + std::to_string(id); - file.m_name += dot_ext[DWR]; + file.m_name += dot_ext[extension]; success = os_file_status(file.m_name.c_str(), &exists, &type); @@ -1711,6 +2412,46 @@ static dberr_t dblwr_file_open(const std::string &dir_name, int id, return DB_SUCCESS; } +dberr_t dblwr::reduced_open(bool create_new_db) noexcept { + ut_a(Double_write::s_r_files.empty()); + + // one for batch segments + Double_write::s_r_files.resize(1); + + dberr_t err{DB_SUCCESS}; + + /* Create the batch file */ + auto &file = Double_write::s_r_files[0]; + + uint32_t pages_per_file = Double_write::s_n_instances; + ib_file_suffix extension{BWR}; + + err = dblwr_file_open(dblwr::dir, 0, file, OS_DBLWR_FILE, extension); + + if (err != DB_SUCCESS) { + return (err); + } + + const uint32_t phy_size = REDUCED_BATCH_PAGE_SIZE; + + err = Double_write::init_file(file, pages_per_file, phy_size); + + if (err != DB_SUCCESS) { + return (err); + } + + auto file_size = os_file_get_size(file.m_pfs); + + if (file_size == 0 || (file_size % phy_size)) { + ib::warn(ER_IB_MSG_DBLWR_1322, file.m_name.c_str(), (ulint)file_size, + (ulint)phy_size); + } + + Double_write::reduced_reset_file(file, pages_per_file, phy_size); + + return (DB_SUCCESS); +} + dberr_t dblwr::open(bool create_new_db) noexcept { ut_a(!dblwr::dir.empty()); ut_a(Double_write::s_files.empty()); @@ -1801,9 +2542,44 @@ dberr_t dblwr::open(bool create_new_db) noexcept { Double_write::shutdown(); } + if (err != DB_SUCCESS) { + return (err); + } + + if (!dblwr::is_reduced()) { + return (DB_SUCCESS); + } + + if (err == DB_SUCCESS) { + err = dblwr::enable_reduced(create_new_db); + } + return err; } +dberr_t dblwr::enable_reduced(bool create_new_db) noexcept { + if (is_reduced_inited) { + return (DB_SUCCESS); + } + + dberr_t err = dblwr::reduced_open(create_new_db); + + /* Create the segments that for LRU and FLUSH list batches writes */ + if (err == DB_SUCCESS) { + err = Double_write::create_reduced_batch_segments(); + } + + if (err == DB_SUCCESS) { + err = Double_write::create_reduced(); + } + + if (err == DB_SUCCESS) { + is_reduced_inited = true; + } + + return (err); +} + void dblwr::close() noexcept { Double_write::shutdown(); } void dblwr::set() { @@ -1849,14 +2625,63 @@ bool dblwr::v1::is_inside(page_no_t page_no) noexcept { return false; } +std::tuple recv::Pages::is_actual_page_corrupted( + fil_space_t *space, page_id_t &page_id) { + auto result = std::make_tuple(false, false); + + if (page_id.page_no() >= space->size) { + /* Do not report the warning if the tablespace is going to be truncated. */ + if (!undo::is_active(space->id)) { + ib::warn(ER_IB_MSG_DBLWR_1313) + << "Page# " << page_id.page_no() + << " stored in the doublewrite file is" + " not within data file space bounds " + << space->size << " bytes: page : " << page_id; + } + return (result); + } + + Buffer buffer{1}; + const page_size_t page_size(space->flags); + + /* We want to ensure that for partial reads the + unread portion of the page is NUL. */ + memset(buffer.begin(), 0x0, page_size.physical()); + + IORequest request; + + request.dblwr(); + + /* Read in the page from the data file to compare. */ + auto err = fil_io(request, true, page_id, page_size, 0, page_size.physical(), + buffer.begin(), nullptr); + + if (err != DB_SUCCESS) { + ib::warn(ER_IB_MSG_DBLWR_1314) + << "Double write fle recovery: " << page_id << " read failed with " + << "error: " << ut_strerr(err); + } + + /* Is the page read from the data file corrupt? */ + BlockReporter data_file_page(true, buffer.begin(), page_size, + fsp_is_checksum_disabled(space->id)); + + bool is_all_zero = buf_page_is_zeroes(buffer.begin(), page_size); + + std::get<0>(result) = data_file_page.is_corrupted(); + std::get<1>(result) = is_all_zero; + return (result); +} + /** Recover a page from the doublewrite buffer. @param[in] dblwr_page_no Page number if the doublewrite buffer @param[in] space Tablespace the page belongs to @param[in] page_no Page number in the tablespace @param[in] page Data to write to @return true if page was restored to the tablespace */ -static bool dblwr_recover_page(page_no_t dblwr_page_no, fil_space_t *space, - page_no_t page_no, const byte *page) noexcept { +bool recv::Pages::dblwr_recover_page(page_no_t dblwr_page_no, + fil_space_t *space, page_no_t page_no, + byte *page) noexcept { /* For cloned database double write pages should be ignored. However, given the control flow, we read the pages in anyway but don't recover from the pages we read in. */ @@ -1938,6 +2763,20 @@ static bool dblwr_recover_page(page_no_t dblwr_page_no, fil_space_t *space, } } + bool found = false; + lsn_t reduced_lsn = LSN_MAX; + std::tie(found, reduced_lsn) = find_entry(page_id); + lsn_t dblwr_lsn = mach_read_from_8(page + FIL_PAGE_LSN); + + /* If we find a newer version of page that is in reduced dblwr, we + shouldn't restore the old/stale page from regular dblwr. We should + abort */ + if (found && reduced_lsn != LSN_MAX && reduced_lsn > dblwr_lsn) { + ib::error(ER_REDUCED_DBLWR_PAGE_FOUND, space->files.front().name, + page_id.space(), page_id.page_no()); + return (false); + } + /* Recovered data file pages are written out as uncompressed. */ IORequest write_request(IORequest::WRITE); @@ -1962,16 +2801,24 @@ void dblwr::force_flush(buf_flush_t flush_type, uint32_t buf_pool_index) noexcept { Double_write::force_flush(flush_type, buf_pool_index); } + +void dblwr::force_flush_all() noexcept { + for (ulint i = 0; i < srv_buf_pool_instances; i++) { + force_flush(BUF_FLUSH_LRU, i); + force_flush(BUF_FLUSH_LIST, i); + } +} + #endif /* !UNIV_HOTBACKUP */ -void recv::Pages::recover(fil_space_t *space) noexcept { +dberr_t recv::Pages::recover(fil_space_t *space) noexcept { #ifndef UNIV_HOTBACKUP /* For cloned database double write pages should be ignored. However, given the control flow, we read the pages in anyway but don't recover from the pages we read in. */ - if (!dblwr::enabled || recv_sys->is_cloned_db) { - return; + if (!dblwr::is_enabled() || recv_sys->is_cloned_db) { + return (DB_SUCCESS); } auto recover_all = (space == nullptr); @@ -2004,12 +2851,77 @@ void recv::Pages::recover(fil_space_t *space) noexcept { dblwr_recover_page(page->m_no, space, page_no, page->m_buffer.begin()); } + dberr_t err = reduced_recover(space); + if (err != DB_SUCCESS) { + return (err); + } + fil_flush_file_spaces(FIL_TYPE_TABLESPACE, FLUSH_FROM_DOUBLEWRITE); + return (DB_SUCCESS); #endif /* !UNIV_HOTBACKUP */ } +dberr_t recv::Pages::reduced_recover(fil_space_t *space) noexcept { + auto recover_all = (space == nullptr); + + for (const auto &entry : m_page_entries) { + auto space_id = entry.m_space_id; + page_id_t page_id(entry.m_space_id, entry.m_page_num); + + if (recover_all) { + space = fil_space_get(space_id); + + if (space == nullptr) { + /* Maybe we have dropped the tablespace + and this page once belonged to it: do nothing. */ + continue; + } + + } else if (space->id != space_id) { + continue; + } + + fil_space_open_if_needed(space); + + bool is_corrupted = false; + bool is_all_zero = false; + std::tie(is_corrupted, is_all_zero) = + is_actual_page_corrupted(space, page_id); + + if (is_corrupted) { + const byte *page = find(page_id); + if (page != nullptr) { + if (!is_recovered(page_id)) { + ib::error(ER_REDUCED_DBLWR_PAGE_FOUND, space->files.front().name, + page_id.space(), page_id.page_no()); + return (DB_CORRUPTION); + } + } else { + ib::error(ER_REDUCED_DBLWR_PAGE_FOUND, space->files.front().name, + page_id.space(), page_id.page_no()); + return (DB_CORRUPTION); + } + } + + if (is_all_zero) { + // is there a dblwr reduced entry with non-zero LSN? + bool found = false; + lsn_t reduced_lsn = LSN_MAX; + std::tie(found, reduced_lsn) = find_entry(page_id); + + if (!is_recovered(page_id) && found && reduced_lsn != LSN_MAX && + reduced_lsn != 0) { + ib::error(ER_REDUCED_DBLWR_PAGE_FOUND, space->files.front().name, + page_id.space(), page_id.page_no()); + return (DB_CORRUPTION); + } + } + } + return (DB_SUCCESS); +} + const byte *recv::Pages::find(const page_id_t &page_id) const noexcept { - if (!dblwr::enabled) { + if (!dblwr::is_enabled()) { return nullptr; } using Matches = std::vector>; @@ -2045,9 +2957,22 @@ const byte *recv::Pages::find(const page_id_t &page_id) const noexcept { return page; } +bool recv::Pages::is_recovered(const page_id_t &page_id) const noexcept { + for (const auto &page : m_pages) { + auto &buffer = page->m_buffer; + + if (page_get_space_id(buffer.begin()) == page_id.space() && + page_get_page_no(buffer.begin()) == page_id.page_no() && + page->m_recovered) { + return (true); + } + } + return (false); +} + void recv::Pages::add(page_no_t page_no, const byte *page, uint32_t n_bytes) noexcept { - if (!dblwr::enabled) { + if (!dblwr::is_enabled()) { return; } /* Make a copy of the page contents. */ @@ -2060,7 +2985,7 @@ void recv::Pages::check_missing_tablespaces() const noexcept { /* For cloned database double write pages should be ignored. However, given the control flow, we read the pages in anyway but don't recover from the pages we read in. */ - if (!dblwr::enabled) { + if (!dblwr::is_enabled()) { return; } @@ -2101,7 +3026,7 @@ void recv::Pages::check_missing_tablespaces() const noexcept { dberr_t dblwr::recv::load(recv::Pages *pages) noexcept { #ifndef UNIV_HOTBACKUP /* For cloned database double write pages should be ignored. */ - if (!dblwr::enabled) { + if (!dblwr::is_enabled()) { return DB_SUCCESS; } @@ -2210,11 +3135,117 @@ dberr_t dblwr::recv::load(recv::Pages *pages) noexcept { return DB_SUCCESS; } +dberr_t dblwr::recv::reduced_load(recv::Pages *pages) noexcept { +#ifndef UNIV_HOTBACKUP + /* For cloned database double write pages should be ignored. */ + if (!dblwr::is_enabled()) { + return DB_SUCCESS; + } + + ut_ad(!dblwr::dir.empty()); + + /* The number of buffer pool instances can change. Therefore we must: + 1. Scan the doublewrite directory for all *.dblwr files and load + their contents. + 2. Reset the file sizes after recovery is complete. */ + + auto real_path_dir = Fil_path::get_real_path(dblwr::dir); + + /* Walk the sub-tree of dblwr::dir. */ + + std::vector dblwr_files; + + Dir_Walker::walk(real_path_dir, false, [&](const std::string &path) { + ut_a(path.length() > real_path_dir.length()); + + if (Fil_path::get_file_type(path) != OS_FILE_TYPE_FILE) { + return; + } + + /* Make the filename relative to the directory that was scanned. */ + + auto file = path.substr(real_path_dir.length(), path.length()); + + if (file.size() <= strlen(dot_ext[BWR])) { + return; + } + + if (Fil_path::has_suffix(BWR, file.c_str())) { + dblwr_files.push_back(file); + } + }); + + /* We have to use all the dblwr files for recovery. */ + + std::string rexp{"#ib_([0-9]+)_([0-9]+)\\"}; + + rexp.append(dot_ext[BWR]); + + const std::regex regex{rexp}; + + std::vector ids; + + for (auto &file : dblwr_files) { + std::smatch match; + + if (std::regex_match(file, match, regex) && match.size() == 3) { + /* Check if the page size matches. */ + int page_size = std::stoi(match[1].str()); + + if (page_size == (int)srv_page_size) { + int id = std::stoi(match[2].str()); + ids.push_back(id); + } else { + ib::info(ER_IB_MSG_DBLWR_1310) + << "Ignoring " << file << " - page size doesn't match"; + } + } else { + ib::warn(ER_IB_MSG_DBLWR_1311) + << file << " not in double write buffer file name format!"; + } + } + + if (ids.size() == 0) { + // We are starting on older version that doesn't have reduced dblwr file + return (DB_SUCCESS); + } + + // There should be always only one Batch DBLWR file + ut_ad(ids.size() == 1); + + dblwr::File file; + + /* Open the file for reading. */ + auto err = dblwr_file_open(dblwr::dir, 0, file, OS_DATA_FILE, BWR); + + if (err == DB_NOT_FOUND) { + return (DB_SUCCESS); + } else if (err != DB_SUCCESS) { + return err; + } + + err = Double_write::load_reduced_batch(file, pages); + + os_file_close(file.m_pfs); + + if (err != DB_SUCCESS) { + return err; + } + +#endif /* UNIV_HOTBACKUP */ + return DB_SUCCESS; +} + const byte *dblwr::recv::find(const recv::Pages *pages, const page_id_t &page_id) noexcept { return pages->find(page_id); } +std::tuple dblwr::recv::find_entry( + const recv::Pages *pages, const page_id_t &page_id) noexcept { + return pages->find_entry(page_id); +} + void dblwr::recv::create(recv::Pages *&pages) noexcept { ut_a(pages == nullptr); pages = UT_NEW_NOKEY(recv::Pages{}); diff --git a/storage/innobase/buf/buf0flu.cc b/storage/innobase/buf/buf0flu.cc index 435fbe29b8c..15cbf4d9dc4 100644 --- a/storage/innobase/buf/buf0flu.cc +++ b/storage/innobase/buf/buf0flu.cc @@ -903,7 +903,7 @@ void buf_flush_write_complete(buf_page_t *bpage) { mutex_exit(&buf_pool->flush_state_mutex); - if (!fsp_is_system_temporary(bpage->id.space()) && dblwr::enabled) { + if (!fsp_is_system_temporary(bpage->id.space()) && dblwr::is_enabled()) { dblwr::write_complete(bpage, flush_type); } } @@ -1339,7 +1339,7 @@ ibool buf_flush_page(buf_pool_t *buf_pool, buf_page_t *bpage, if (flush_type == BUF_FLUSH_LIST && is_uncompressed && !rw_lock_sx_lock_nowait(rw_lock, BUF_IO_WRITE)) { - if (!fsp_is_system_temporary(bpage->id.space()) && dblwr::enabled) { + if (!fsp_is_system_temporary(bpage->id.space()) && dblwr::is_enabled()) { dblwr::force_flush(flush_type, buf_pool_index(buf_pool)); } else { buf_flush_sync_datafiles(); @@ -2002,7 +2002,7 @@ static void buf_flush_end(buf_pool_t *buf_pool, buf_flush_t flush_type) { mutex_exit(&buf_pool->flush_state_mutex); if (!srv_read_only_mode) { - if (dblwr::enabled) { + if (dblwr::is_enabled()) { dblwr::force_flush(flush_type, buf_pool_index(buf_pool)); } else { buf_flush_sync_datafiles(); diff --git a/storage/innobase/fil/fil0fil.cc b/storage/innobase/fil/fil0fil.cc index c8251a2af6b..1ca5eafc1bf 100644 --- a/storage/innobase/fil/fil0fil.cc +++ b/storage/innobase/fil/fil0fil.cc @@ -280,7 +280,8 @@ Fil_path MySQL_datadir_path; Fil_path MySQL_undo_path; /** Common InnoDB file extentions */ -const char *dot_ext[] = {"", ".ibd", ".cfg", ".cfp", ".ibt", ".ibu", ".dblwr"}; +const char *dot_ext[] = {"", ".ibd", ".cfg", ".cfp", + ".ibt", ".ibu", ".dblwr", ".bdblwr"}; /** The number of fsyncs done to the log */ ulint fil_n_log_flushes = 0; @@ -309,7 +310,10 @@ enum fil_load_status { /** The tablespace file ID in the first page doesn't match expected value. */ - FIL_LOAD_MISMATCH + FIL_LOAD_MISMATCH, + + /** Doublewrite buffer corruption */ + FIL_LOAD_DBWLR_CORRUPTION }; /** File operations for tablespace */ @@ -1404,8 +1408,8 @@ class Fil_system { /** Open a tablespace that has a redo log record to apply. @param[in] space_id Tablespace ID - @return true if the open was successful */ - bool open_for_recovery(space_id_t space_id) + @return DB_SUCCESS if the open was successful */ + dberr_t open_for_recovery(space_id_t space_id) MY_ATTRIBUTE((warn_unused_result)); /** This function should be called after recovery has completed. @@ -4423,7 +4427,7 @@ bool fil_replace_tablespace(space_id_t old_space_id, space_id_t new_space_id, page_no_t n_pages = SRV_UNDO_TABLESPACE_SIZE_IN_PAGES; #if !defined(NO_FALLOCATE) && defined(UNIV_LINUX) bool atomic_write = false; - if (!dblwr::enabled) { + if (!dblwr::is_enabled()) { atomic_write = fil_fusionio_enable_atomic_write(fh); } #else @@ -5492,7 +5496,7 @@ dberr_t fil_ibd_open(bool validate, fil_type_t purpose, space_id_t space_id, #if !defined(NO_FALLOCATE) && defined(UNIV_LINUX) const bool atomic_write = - !dblwr::enabled && fil_fusionio_enable_atomic_write(df.handle()); + !dblwr::is_enabled() && fil_fusionio_enable_atomic_write(df.handle()); #else const bool atomic_write = false; #endif /* !NO_FALLOCATE && UNIV_LINUX */ @@ -5751,7 +5755,13 @@ fil_load_status Fil_shard::ibd_open_for_recovery(space_id_t space_id, Assign a tablespace name based on the tablespace type. */ dberr_t err = df.validate_for_recovery(space_id); - ut_a(err == DB_SUCCESS || err == DB_INVALID_ENCRYPTION_META); + ut_a(err == DB_SUCCESS || err == DB_INVALID_ENCRYPTION_META || + err == DB_CORRUPTION); + + if (err == DB_CORRUPTION) { + return (FIL_LOAD_DBWLR_CORRUPTION); + } + if (err == DB_INVALID_ENCRYPTION_META) { bool success = fil_system->erase(space_id); ut_a(success); @@ -9658,11 +9668,11 @@ bool fil_tablespace_lookup_for_recovery(space_id_t space_id) { /** Open a tablespace that has a redo/DDL log record to apply. @param[in] space_id Tablespace ID @return true if the open was successful */ -bool Fil_system::open_for_recovery(space_id_t space_id) { +dberr_t Fil_system::open_for_recovery(space_id_t space_id) { ut_ad(recv_recovery_is_on() || Log_DDL::is_in_recovery()); if (!lookup_for_recovery(space_id)) { - return (false); + return (DB_FAIL); } const auto result = get_scanned_files(space_id); @@ -9677,6 +9687,12 @@ bool Fil_system::open_for_recovery(space_id_t space_id) { auto status = ibd_open_for_recovery(space_id, path, space); + dberr_t err = DB_SUCCESS; + + if (status == FIL_LOAD_DBWLR_CORRUPTION) { + return (DB_CORRUPTION); + } + if (status == FIL_LOAD_OK) { /* For encrypted tablespace, set key and iv. */ if (FSP_FLAGS_GET_ENCRYPTION(space->flags) && recv_sys->keys != nullptr) { @@ -9684,23 +9700,23 @@ bool Fil_system::open_for_recovery(space_id_t space_id) { } if (!recv_sys->dblwr->empty()) { - recv_sys->dblwr->recover(space); + err = recv_sys->dblwr->recover(space); } else { ib::info(ER_IB_MSG_DBLWR_1317) << "DBLWR recovery skipped for " << space->name << " ID: " << space->id; } - return (true); + return (err); } - return (false); + return (DB_FAIL); } /** Open a tablespace that has a redo log record to apply. @param[in] space_id Tablespace ID -@return true if the open was successful */ -bool fil_tablespace_open_for_recovery(space_id_t space_id) { +@return DB_SUCCESS if the open was successful */ +dberr_t fil_tablespace_open_for_recovery(space_id_t space_id) { return (fil_system->open_for_recovery(space_id)); } @@ -10048,11 +10064,9 @@ byte *fil_tablespace_redo_create(byte *ptr, const byte *end, /* It's possible that the tablespace file was renamed later. */ if (result.second->front().compare(abs_name) == 0) { - bool success; + dberr_t success = fil_tablespace_open_for_recovery(page_id.space()); - success = fil_tablespace_open_for_recovery(page_id.space()); - - if (!success) { + if (success != DB_SUCCESS) { ib::info(ER_IB_MSG_356) << "Create '" << abs_name << "' failed!"; } } diff --git a/storage/innobase/fsp/fsp0file.cc b/storage/innobase/fsp/fsp0file.cc index c0c13cbcf42..b77a67218e0 100644 --- a/storage/innobase/fsp/fsp0file.cc +++ b/storage/innobase/fsp/fsp0file.cc @@ -507,12 +507,12 @@ dberr_t Datafile::validate_for_recovery(space_id_t space_id) { } err = restore_from_doublewrite(0); + /* Free the previously read first page and then re-validate. */ + free_first_page(); if (err != DB_SUCCESS) { return (err); } - /* Free the previously read first page and then re-validate. */ - free_first_page(); err = validate_first_page(space_id, nullptr, false); } @@ -879,16 +879,32 @@ dberr_t Datafile::restore_from_doublewrite(page_no_t restore_page_no) { /* Find if double write buffer contains page_no of given space id. */ const byte *page = recv_sys->dblwr->find(page_id); + bool found = false; + lsn_t reduced_lsn = LSN_MAX; + std::tie(found, reduced_lsn) = recv_sys->dblwr->find_entry(page_id); + if (page == nullptr) { /* If the first page of the given user tablespace is not there in the doublewrite buffer, then the recovery is going to fail now. Hence this is treated as an error. */ - ib::error(ER_IB_MSG_412) - << "Corrupted page " << page_id_t(m_space_id, restore_page_no) - << " of datafile '" << m_filepath - << "' could not be found in the doublewrite buffer."; + if (found && reduced_lsn != LSN_MAX && reduced_lsn != 0) { + ib::error(ER_REDUCED_DBLWR_PAGE_FOUND, m_filepath, page_id.space(), + page_id.page_no()); + } else { + ib::error(ER_IB_MSG_412) + << "Corrupted page " << page_id_t(m_space_id, restore_page_no) + << " of datafile '" << m_filepath + << "' could not be found in the doublewrite buffer."; + } + return (DB_CORRUPTION); + } + + const lsn_t dblwr_lsn = mach_read_from_8(page + FIL_PAGE_LSN); + if (found && reduced_lsn != LSN_MAX && reduced_lsn > dblwr_lsn) { + ib::error(ER_REDUCED_DBLWR_PAGE_FOUND, m_filepath, page_id.space(), + page_id.page_no()); return (DB_CORRUPTION); } diff --git a/storage/innobase/fsp/fsp0space.cc b/storage/innobase/fsp/fsp0space.cc index 16a48c16b97..84e5f13edd9 100644 --- a/storage/innobase/fsp/fsp0space.cc +++ b/storage/innobase/fsp/fsp0space.cc @@ -111,7 +111,7 @@ dberr_t Tablespace::open_or_create(bool is_temp) { bool atomic_write; #if !defined(NO_FALLOCATE) && defined(UNIV_LINUX) - if (!dblwr::enabled) { + if (!dblwr::is_enabled()) { atomic_write = fil_fusionio_enable_atomic_write(it->m_handle); } else { atomic_write = false; diff --git a/storage/innobase/fsp/fsp0sysspace.cc b/storage/innobase/fsp/fsp0sysspace.cc index b48e0e8ee01..88425d5bc55 100644 --- a/storage/innobase/fsp/fsp0sysspace.cc +++ b/storage/innobase/fsp/fsp0sysspace.cc @@ -536,6 +536,12 @@ dberr_t SysTablespace::read_lsn_and_check_flags(lsn_t *flushed_lsn) { return (err); } + err = recv_sys->dblwr->reduced_load(); + + if (err != DB_SUCCESS) { + return (err); + } + /* Check the contents of the first page of the first datafile. */ for (int retry = 0; retry < 2; ++retry) { err = it->validate_first_page(it->m_space_id, flushed_lsn, false); @@ -848,11 +854,11 @@ dberr_t SysTablespace::open_or_create(bool is_temp, bool create_new_db, the tablespace should be on the same medium. */ if (fil_fusionio_enable_atomic_write(it->m_handle)) { - if (dblwr::enabled) { + if (dblwr::is_enabled()) { ib::info(ER_IB_MSG_456) << "FusionIO atomic IO enabled," " disabling the double write buffer"; - dblwr::enabled = false; + dblwr::enabled = dblwr::OFF; } it->m_atomic_write = true; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 434863ec93e..158b9e750b2 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -422,6 +422,18 @@ static TYPELIB innodb_default_row_format_typelib = { "innodb_default_row_format_typelib", innodb_default_row_format_names, nullptr}; +/** Possible values for system variable "innodb_doublewrite". +@note: If you change order or add new values, please update dblwr::mode_t in +include/buf0dblwr.h */ +static const char *innodb_doublewrite_names[] = {"OFF", "ON", "REDUCED", + "FALSE", "TRUE", NullS}; + +/** Used to define an enumerate type of the system variable +innodb_default_row_format. */ +static TYPELIB innodb_doublewrite_typelib = { + array_elements(innodb_doublewrite_names) - 1, "innodb_doublewrite_typelib", + innodb_doublewrite_names, nullptr}; + #else /* !UNIV_HOTBACKUP */ /** Returns the name of the checksum algorithm corresponding to the @@ -766,6 +778,91 @@ static PSI_file_info all_innodb_files[] = { #endif /* UNIV_PFS_IO */ #endif /* HAVE_PSI_INTERFACE */ +/** Plugin update function to handle valdiation and then switch the +innodb_doublewrite mode +@param[in] thd thread handle +@param[in] var pointer to system variable +@param[in] var_ptr where the formal string goes +@param[in] save immediate result from check function */ +static void doublewrite_update(THD *thd, SYS_VAR *var, void *var_ptr, + const void *save) { + ulong new_value = *static_cast(save); + + if (dblwr::is_enabled() && dblwr::is_disabled_low(new_value)) { + char msg[FN_REFLEN]; + snprintf(msg, sizeof(msg), + "InnoDB: cannot change doublewrite mode to %s if" + " doublewrite is enabled. Please shutdown and" + " change value to %s", + dblwr::to_string(new_value), dblwr::to_string(new_value)); + /* + push_warning_printf(thd, Sql_condition::SL_WARNING, HA_ERR_UNSUPPORTED, + "InnoDB: cannot change doublewrite mode to OFF if" + " doublewrite is enabled. Please shutdown and" + " change value to OFF"); + */ + my_error(ER_WRONG_ARGUMENTS, MYF(0), msg); + return; + } + + if (dblwr::is_disabled() && dblwr::is_enabled_low(new_value)) { + char msg[FN_REFLEN]; + snprintf(msg, sizeof(msg), + "InnoDB: cannot change doublewrite mode to %s if" + " doublewrite is disabled. Please shutdown and" + " change value to %s", + dblwr::to_string(new_value), dblwr::to_string(new_value)); + + my_error(ER_WRONG_ARGUMENTS, MYF(0), msg); + + return; + } + + // ON and TRUE are same + if ((dblwr::enabled == dblwr::TRUEE || dblwr::enabled == dblwr::ON) && + (new_value == dblwr::TRUEE || new_value == dblwr::ON)) { + return; + } + + // OFF and FALSE are same + if ((dblwr::enabled == dblwr::FALSEE || dblwr::enabled == dblwr::OFF) && + (new_value == dblwr::FALSEE || new_value == dblwr::OFF)) { + return; + } + + if (new_value == dblwr::enabled) { + // Old value and new value same. Do nothing. + return; + } + + // Handle ON to REDUCED + // 1. Check if REDUCED setup is already initalized. If not intialize REDUCED + // files and structures + // 2. Flush the partially filled dblwr buffers + // + if (dblwr::is_reduced_low(new_value)) { + dberr_t err = dblwr::enable_reduced(false); + if (err != DB_SUCCESS) { + char msg[FN_REFLEN]; + snprintf(msg, sizeof(msg), + "InnoDB: cannot change doublewrite mode to %s." + " Please check if doublewrite directory is writable" + " Error code: %d", + dblwr::to_string(new_value), err); + my_error(ER_WRONG_ARGUMENTS, MYF(0), msg); + return; + } + } + + // Handle REDUCED to ON + // 1. Flush partially filled reduced dblwr buffers + + dblwr::force_flush_all(); + + *static_cast(var_ptr) = + *static_cast(save); +} + /** Set up InnoDB API callback function array */ /* Generates array elements which look like: @@ -4569,7 +4666,7 @@ static int innodb_init_params() { /* There is no write except to intrinsic table and so turn-off doublewrite mechanism completely. */ - dblwr::enabled = false; + dblwr::enabled = dblwr::OFF; } #ifdef LINUX_NATIVE_AIO @@ -12247,7 +12344,7 @@ static bool innobase_ddse_dict_init( DBUG_ASSERT(tables && tables->is_empty()); DBUG_ASSERT(tablespaces && tablespaces->is_empty()); - if (dblwr::enabled) { + if (dblwr::is_enabled()) { if (innobase_doublewrite_dir != nullptr && *innobase_doublewrite_dir != 0) { dblwr::dir.assign(innobase_doublewrite_dir); switch (dblwr::dir.front()) { @@ -21317,11 +21414,11 @@ static MYSQL_SYSVAR_ULONG(page_hash_locks, srv_n_page_hash_locks, #endif /* defined UNIV_DEBUG || defined UNIV_PERF_DEBUG */ // clang-format off -static MYSQL_SYSVAR_BOOL( - doublewrite, dblwr::enabled, PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, +static MYSQL_SYSVAR_ENUM( + doublewrite, dblwr::enabled, PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_NOPERSIST, "Enable InnoDB doublewrite buffer (enabled by default)." " Disable with --skip-innodb-doublewrite.", - nullptr, nullptr, TRUE); + nullptr, doublewrite_update, dblwr::ON, &innodb_doublewrite_typelib); static MYSQL_SYSVAR_STR( doublewrite_dir, innobase_doublewrite_dir, PLUGIN_VAR_READONLY, diff --git a/storage/innobase/include/buf0dblwr.h b/storage/innobase/include/buf0dblwr.h index 94de4ce13db..c483d6ce6cc 100644 --- a/storage/innobase/include/buf0dblwr.h +++ b/storage/innobase/include/buf0dblwr.h @@ -61,17 +61,33 @@ struct Buffer { /** Constructor @param[in] n_pages Number of pages to create */ explicit Buffer(size_t n_pages) noexcept - : m_n_bytes(n_pages * univ_page_size.physical()) { + : m_phy_size(univ_page_size.physical()), m_n_bytes(n_pages * m_phy_size) { ut_a(n_pages > 0); - auto n_bytes = m_n_bytes + univ_page_size.physical(); + auto n_bytes = m_n_bytes + m_phy_size; m_ptr_unaligned = static_cast(ut_zalloc_nokey(n_bytes)); - m_ptr = static_cast(ut_align(m_ptr_unaligned, UNIV_PAGE_SIZE)); + m_ptr = static_cast(ut_align(m_ptr_unaligned, m_phy_size)); - ut_a(ptrdiff_t(m_ptr - m_ptr_unaligned) <= - (ssize_t)univ_page_size.physical()); + ut_a(ptrdiff_t(m_ptr - m_ptr_unaligned) <= (ssize_t)m_phy_size); + + m_next = m_ptr; + } + + /** Constructor + @param[in] n_pages Number of pages to create */ + explicit Buffer(size_t n_pages, uint32_t phy_size) noexcept + : m_phy_size(phy_size), m_n_bytes(n_pages * m_phy_size) { + ut_a(n_pages > 0); + + auto n_bytes = m_n_bytes + m_phy_size; + + m_ptr_unaligned = static_cast(ut_zalloc_nokey(n_bytes)); + + m_ptr = static_cast(ut_align(m_ptr_unaligned, m_phy_size)); + + ut_a(ptrdiff_t(m_ptr - m_ptr_unaligned) <= (ssize_t)m_phy_size); m_next = m_ptr; } @@ -89,12 +105,12 @@ struct Buffer { bool append(const void *ptr, size_t n_bytes) noexcept { ut_a(m_next >= m_ptr && m_next <= m_ptr + m_n_bytes); - if (m_next + univ_page_size.physical() > m_ptr + m_n_bytes) { + if (m_next + m_phy_size > m_ptr + m_n_bytes) { return false; } memcpy(m_next, ptr, n_bytes); - m_next += univ_page_size.physical(); + m_next += m_phy_size; return true; } @@ -120,6 +136,8 @@ struct Buffer { /** Empty the buffer. */ void clear() noexcept { m_next = m_ptr; } + uint32_t m_phy_size; + /** Write buffer used in writing to the doublewrite buffer, aligned to an address divisible by UNIV_PAGE_SIZE (which is required by Windows AIO) */ @@ -164,6 +182,19 @@ extern page_id_t Force_crash; @return DB_SUCCESS or error code */ dberr_t open(bool create_new_db) noexcept MY_ATTRIBUTE((warn_unused_result)); +/** Enable the doublewrite reduced mode by creating the necessary dblwr files +and in-memory structures +@param[in] create_new_db true if db is bootstrapped +@return DB_SUCCESS or error code */ +dberr_t enable_reduced(bool create_new_db) noexcept + MY_ATTRIBUTE((warn_unused_result)); + +/** Check and open the reduced doublewrite files if necessary +@param[in] create_new_db true if db is bootstrapped +@return DB_SUCCESS or error code */ +dberr_t reduced_open(bool create_new_db) noexcept + MY_ATTRIBUTE((warn_unused_result)); + /** Shutdown the background thread and destroy the instance */ void close() noexcept; @@ -172,6 +203,10 @@ void close() noexcept; @param[in] buf_pool_index Buffer pool instance for which called. */ void force_flush(buf_flush_t flush_type, uint32_t buf_pool_index) noexcept; +/** Force a write of all pages in all dblwr segments (reduced or regular) +This is used only when switching the doublewrite mode dynamically */ +void force_flush_all() noexcept; + /** Writes a page to the doublewrite buffer on disk, syncs it, then writes the page to the datafile. @param[in] flush_type Flush type @@ -216,8 +251,70 @@ namespace dblwr { /** Number of pages per doublewrite thread/segment */ extern ulong n_pages; -/** true if enabled. */ -extern bool enabled; +const uint32_t REDUCED_BATCH_PAGE_SIZE = 8192; + +// 20-BYTE HEADER +const uint32_t RB_BATCH_ID_SIZE = 4; +const uint32_t RB_CHECKSUM_SIZE = 4; +const uint32_t RB_DATA_LEN_SIZE = 2; +const uint32_t RB_BATCH_TYPE_SIZE = 1; +const uint32_t RB_UNUSED_BYTES_SIZE = 9; + +// Header Offsets +constexpr const uint32_t RB_OFF_BATCH_ID = 0; +constexpr const uint32_t RB_OFF_CHECKSUM = RB_OFF_BATCH_ID + RB_BATCH_ID_SIZE; +constexpr const uint32_t RB_OFF_DATA_LEN = RB_OFF_CHECKSUM + RB_CHECKSUM_SIZE; +constexpr const uint32_t RB_OFF_BATCH_TYPE = RB_OFF_DATA_LEN + RB_DATA_LEN_SIZE; + +constexpr const uint32_t REDUCED_HEADER_SIZE = + RB_BATCH_ID_SIZE /* BATCH_ID */ + + RB_CHECKSUM_SIZE /* CHECKSUM */ + + RB_DATA_LEN_SIZE /* DATA_LEN */ + + RB_BATCH_TYPE_SIZE /* BATCH_TYPE */ + + 9 /* UNUSED BYTES */; + +constexpr const uint32_t REDUCED_ENTRY_SIZE = + sizeof(space_id_t) + sizeof(page_no_t) + sizeof(lsn_t); + +constexpr const uint32_t REDUCED_DATA_SIZE = + REDUCED_BATCH_PAGE_SIZE - REDUCED_HEADER_SIZE; + +constexpr const uint32_t REDUCED_MAX_ENTRIES = + REDUCED_DATA_SIZE / REDUCED_ENTRY_SIZE; + +/* intentional difference in FALSE and TRUE spellings. InnoDB +#defines them to 0 & 1. So we cannot use as is. +@note: If you change order or add new values, please update +innodb_doublewrite_names enum in handler/ha_innodb.cc */ +enum mode_t { OFF, ON, REDUCED, FALSEE, TRUEE }; + +/** 1 if enabled. */ +extern ulong enabled; + +/** true if reduced mode is inited */ +extern bool is_reduced_inited; + +/** @return true if dblwr mode is ON, TRUE or REDUCED */ +bool is_enabled(); +/** @return true if dblwr mode is ON, TRUE or REDUCED +@param[in] mode dblwr ENUM */ +bool is_enabled_low(ulong mode); + +/** @return true if dblwr mode is REDUCED */ +bool is_reduced(); +/** @return true if dblwr mode is REDUCED +@param[in] mode dblwr ENUM */ +bool is_reduced_low(ulong mode); + +/** @return true if dblwr mode is OFF, FALSE */ +bool is_disabled(); +/** @return true if dblwr mode is OFF, FALSE +@param[in] mode dblwr ENUM */ +bool is_disabled_low(ulong mode); + +/** @return string version of dblwr numeric values +@param[in] mode dblwr ENUM */ +const char *to_string(ulong mode); /** Number of files to use for the double write buffer. It must be <= than the number of buffer pool instances. */ @@ -243,11 +340,18 @@ void create(Pages *&pages) noexcept; @return DB_SUCCESS or error code */ dberr_t load(Pages *pages) noexcept MY_ATTRIBUTE((warn_unused_result)); +/** Load the doublewrite buffer pages. +@param[in,out] pages For storing the doublewrite pages read + from the double write buffer +@return DB_SUCCESS or error code */ +dberr_t reduced_load(Pages *pages) noexcept MY_ATTRIBUTE((warn_unused_result)); + /** Restore pages from the double write buffer to the tablespace. @param[in,out] pages Pages from the doublewrite buffer @param[in] space Tablespace pages to restore, if set - to nullptr then try and restore all. */ -void recover(Pages *pages, fil_space_t *space) noexcept; + to nullptr then try and restore all. +@return DB_SUCCESS on success, others on failure */ +dberr_t recover(Pages *pages, fil_space_t *space) noexcept; /** Find a doublewrite copy of a page. @param[in] pages Pages read from the doublewrite buffer @@ -258,6 +362,15 @@ const byte *find( const Pages *pages, const page_id_t &page_id) noexcept MY_ATTRIBUTE((warn_unused_result)); +/** Find a doublewrite copy of a page. +@param[in] pages Pages read from the doublewrite buffer +@param[in] page_id Page number to lookup +@return page frame +@retval NULL if no page was found */ +std::tuple find_entry(const Pages *pages, + const page_id_t &page_id) noexcept + MY_ATTRIBUTE((warn_unused_result)); + /** Check if some pages from the double write buffer could not be restored because of the missing tablespace IDs. @param[in] pages Pages to check */ @@ -285,12 +398,18 @@ class DBLWR { return (dblwr::recv::load(m_pages)); } + /** Load the doublewrite buffer pages. Doesn't create the doublewrite + @return DB_SUCCESS or error code */ + dberr_t reduced_load() noexcept MY_ATTRIBUTE((warn_unused_result)) { + return (dblwr::recv::reduced_load(m_pages)); + } + /** Restore pages from the double write buffer to the tablespace. @param[in] space Tablespace pages to restore, if set to nullptr then try and restore all. */ - void recover(fil_space_t *space = nullptr) noexcept { - dblwr::recv::recover(m_pages, space); + dberr_t recover(fil_space_t *space = nullptr) noexcept { + return (dblwr::recv::recover(m_pages, space)); } // clang-format off @@ -302,6 +421,12 @@ class DBLWR { MY_ATTRIBUTE((warn_unused_result)) { return (dblwr::recv::find(m_pages, page_id)); } + + std::tuplefind_entry(const page_id_t &page_id) noexcept + MY_ATTRIBUTE((warn_unused_result)) { + return (dblwr::recv::find_entry(m_pages, page_id)); + } + // clang-format on /** Check if some pages from the double write buffer diff --git a/storage/innobase/include/fil0fil.h b/storage/innobase/include/fil0fil.h index b3deafc23e1..dd6987043d9 100644 --- a/storage/innobase/include/fil0fil.h +++ b/storage/innobase/include/fil0fil.h @@ -368,7 +368,8 @@ enum ib_file_suffix { CFP = 3, IBT = 4, IBU = 5, - DWR = 6 + DWR = 6, + BWR = 7 }; extern const char *dot_ext[]; @@ -1965,8 +1966,8 @@ dberr_t fil_scan_for_tablespaces(); /** Open the tabelspace and also get the tablespace filenames, space_id must already be known. @param[in] space_id Tablespace ID to lookup -@return true if open was successful */ -bool fil_tablespace_open_for_recovery(space_id_t space_id) +@return DB_SUCCESS if open was successful */ +dberr_t fil_tablespace_open_for_recovery(space_id_t space_id) MY_ATTRIBUTE((warn_unused_result)); /** Replay a file rename operation for ddl replay. diff --git a/storage/innobase/include/log0recv.h b/storage/innobase/include/log0recv.h index a3fb1ef5e8e..ea8c92b9875 100644 --- a/storage/innobase/include/log0recv.h +++ b/storage/innobase/include/log0recv.h @@ -239,8 +239,9 @@ pages. in buffer pool: this alternative means that no new log records can be generated during the application; the caller must in this case - own the log mutex */ -void recv_apply_hashed_log_recs(log_t &log, bool allow_ibuf); + own the log mutex +@return DB_SUCCESS on success, others on failure */ +dberr_t recv_apply_hashed_log_recs(log_t &log, bool allow_ibuf); #if defined(UNIV_DEBUG) || defined(UNIV_HOTBACKUP) /** Return string name of the redo log record type. diff --git a/storage/innobase/log/log0recv.cc b/storage/innobase/log/log0recv.cc index b03bb6c6080..69ff4e8fe97 100644 --- a/storage/innobase/log/log0recv.cc +++ b/storage/innobase/log/log0recv.cc @@ -221,9 +221,10 @@ static bool recv_writer_is_active() { static void recv_read_log_seg(log_t &log, byte *buf, lsn_t start_lsn, lsn_t end_lsn); -/** Initialize crash recovery environment. Can be called iff -recv_needed_recovery == false. */ -static void recv_init_crash_recovery(); +/** Initialize crash recovery environment. Can be called if +recv_needed_recovery == false +@return DB_SUCCESS for success, others for errors */ +static dberr_t recv_init_crash_recovery(); #endif /* !UNIV_HOTBACKUP */ /** Calculates the new value for lsn when more data is added to the log. @@ -1185,7 +1186,7 @@ pages. no new log records can be generated during the application; the caller must in this case own the log mutex */ -void recv_apply_hashed_log_recs(log_t &log, bool allow_ibuf) { +dberr_t recv_apply_hashed_log_recs(log_t &log, bool allow_ibuf) { for (;;) { mutex_enter(&recv_sys->mutex); @@ -1225,18 +1226,27 @@ void recv_apply_hashed_log_recs(log_t &log, bool allow_ibuf) { for (const auto &space : *recv_sys->spaces) { bool dropped; - if (space.first != TRX_SYS_SPACE && - !fil_tablespace_open_for_recovery(space.first)) { - /* Tablespace was dropped. It should not have been scanned unless it - is an undo space that was under construction. */ + if (space.first == TRX_SYS_SPACE) { + dropped = false; + } else { + dberr_t err = fil_tablespace_open_for_recovery(space.first); - if (fil_tablespace_lookup_for_recovery(space.first)) { - ut_ad(fsp_is_undo_tablespace(space.first)); - } + if (err == DB_SUCCESS) { + dropped = false; + } else if (err == DB_CORRUPTION) { + /* Page couldn't be recovered from doublewrite, we can proceed + with recovery. Skip applying redos and abort the startup */ + mutex_exit(&recv_sys->mutex); + return (err); + } else { + /* Tablespace was dropped. It should not have been scanned unless it + is an undo space that was under construction. */ - dropped = true; - } else { - dropped = false; + if (fil_tablespace_lookup_for_recovery(space.first)) { + ut_ad(fsp_is_undo_tablespace(space.first)); + } + dropped = true; + } } for (auto pages : space.second.m_pages) { @@ -1320,6 +1330,7 @@ void recv_apply_hashed_log_recs(log_t &log, bool allow_ibuf) { mutex_exit(&recv_sys->mutex); ib::info(ER_IB_MSG_710); + return (DB_SUCCESS); } #else /* !UNIV_HOTBACKUP */ @@ -3163,6 +3174,8 @@ automatically when the hash table becomes full. @param[in,out] contiguous_lsn it is known that log contain contiguous log data up to this lsn @param[out] read_upto_lsn scanning succeeded up to this lsn +@param[out] err DB_SUCCESS if there are file opening + issues, no dblwr corruptions. @return true if not able to scan any more in this log */ #ifndef UNIV_HOTBACKUP static bool recv_scan_log_recs(log_t &log, @@ -3171,11 +3184,13 @@ bool meb_scan_log_recs( #endif /* !UNIV_HOTBACKUP */ ulint max_memory, const byte *buf, ulint len, lsn_t checkpoint_lsn, lsn_t start_lsn, - lsn_t *contiguous_lsn, lsn_t *read_upto_lsn) { + lsn_t *contiguous_lsn, lsn_t *read_upto_lsn, + dberr_t &err) { const byte *log_block = buf; lsn_t scanned_lsn = start_lsn; bool finished = false; bool more_data = false; + err = DB_SUCCESS; ut_ad(start_lsn % OS_FILE_LOG_BLOCK_SIZE == 0); ut_ad(len % OS_FILE_LOG_BLOCK_SIZE == 0); @@ -3313,7 +3328,10 @@ bool meb_scan_log_recs( scanned_lsn > recv_sys->checkpoint_lsn) { ib::info(ER_IB_MSG_722, ulonglong{recv_sys->scanned_lsn}); - recv_init_crash_recovery(); + err = recv_init_crash_recovery(); + if (err != DB_SUCCESS) { + return (true); + } } #endif /* !UNIV_HOTBACKUP */ @@ -3525,7 +3543,7 @@ Parses and hashes the log records if new data found. @param[in,out] contiguous_lsn log sequence number until which all redo log has been scanned */ -static void recv_recovery_begin(log_t &log, lsn_t *contiguous_lsn) { +static dberr_t recv_recovery_begin(log_t &log, lsn_t *contiguous_lsn) { mutex_enter(&recv_sys->mutex); recv_sys->len = 0; @@ -3573,23 +3591,28 @@ static void recv_recovery_begin(log_t &log, lsn_t *contiguous_lsn) { bool finished = false; while (!finished) { + dberr_t err; lsn_t end_lsn = start_lsn + RECV_SCAN_SIZE; recv_read_log_seg(log, log.buf, start_lsn, end_lsn); finished = recv_scan_log_recs(log, max_mem, log.buf, RECV_SCAN_SIZE, checkpoint_lsn, start_lsn, contiguous_lsn, - &log.scanned_lsn); + &log.scanned_lsn, err); + if (err != DB_SUCCESS) { + return (err); + } start_lsn = end_lsn; } DBUG_PRINT("ib_log", ("scan " LSN_PF " completed", log.scanned_lsn)); + return (DB_SUCCESS); } /** Initialize crash recovery environment. Can be called iff -recv_needed_recovery == false. */ -static void recv_init_crash_recovery() { +@return DB_SUCCESS for success, others for errors */ +static dberr_t recv_init_crash_recovery() { ut_ad(!srv_read_only_mode); ut_a(!recv_needed_recovery); @@ -3598,7 +3621,7 @@ static void recv_init_crash_recovery() { ib::info(ER_IB_MSG_726); ib::info(ER_IB_MSG_727); - recv_sys->dblwr->recover(); + dberr_t err = recv_sys->dblwr->recover(); if (srv_force_recovery < SRV_FORCE_NO_LOG_REDO) { /* Spawn the background thread to flush dirty pages @@ -3609,7 +3632,10 @@ static void recv_init_crash_recovery() { srv_threads.m_recv_writer.start(); } + + return err; } + #endif /* !UNIV_HOTBACKUP */ #ifndef UNIV_HOTBACKUP @@ -3680,7 +3706,7 @@ dberr_t recv_recovery_from_checkpoint_start(log_t &log, lsn_t flush_lsn) { /* Restore state after recovery completes. */ recv_sys->dblwr_state = dblwr::enabled; - dblwr::enabled = false; + dblwr::enabled = dblwr::OFF; dblwr::set(); @@ -3785,13 +3811,21 @@ dberr_t recv_recovery_from_checkpoint_start(log_t &log, lsn_t flush_lsn) { return (DB_READ_ONLY); } - recv_init_crash_recovery(); + dberr_t err = recv_init_crash_recovery(); + + if (err != DB_SUCCESS) { + return (err); + } } } contiguous_lsn = checkpoint_lsn; - recv_recovery_begin(log, &contiguous_lsn); + err = recv_recovery_begin(log, &contiguous_lsn); + + if (err != DB_SUCCESS) { + return (err); + } lsn_t recovered_lsn; diff --git a/storage/innobase/srv/srv0start.cc b/storage/innobase/srv/srv0start.cc index c0f45d415a0..a5258d232df 100644 --- a/storage/innobase/srv/srv0start.cc +++ b/storage/innobase/srv/srv0start.cc @@ -914,7 +914,7 @@ static dberr_t srv_undo_tablespace_open(undo::Tablespace &undo_space) { /* Check if this file supports atomic write. */ #if !defined(NO_FALLOCATE) && defined(UNIV_LINUX) - if (!dblwr::enabled) { + if (!dblwr::is_enabled()) { atomic_write = fil_fusionio_enable_atomic_write(fh); } else { atomic_write = false; @@ -2361,7 +2361,8 @@ dberr_t srv_start(bool create_new_db) { files_checked: - if (dblwr::enabled && ((err = dblwr::open(create_new_db)) != DB_SUCCESS)) { + if (dblwr::is_enabled() && + ((err = dblwr::open(create_new_db)) != DB_SUCCESS)) { return (srv_init_abort(err)); } @@ -2504,14 +2505,15 @@ files_checked: /* Don't allow IBUF operations for cloned database recovery as it would add extra redo log and we may not have enough margin. */ + dberr_t err; if (recv_sys->is_cloned_db) { - recv_apply_hashed_log_recs(*log_sys, false); + err = recv_apply_hashed_log_recs(*log_sys, false); } else { - recv_apply_hashed_log_recs(*log_sys, true); + err = recv_apply_hashed_log_recs(*log_sys, true); } - if (recv_sys->found_corrupt_log) { + if (recv_sys->found_corrupt_log || err != DB_SUCCESS) { err = DB_ERROR; return (srv_init_abort(err)); }