diff options
author | Marko Mäkelä <marko.makela@mariadb.com> | 2019-11-06 08:24:48 +0200 |
---|---|---|
committer | Marko Mäkelä <marko.makela@mariadb.com> | 2019-11-06 08:48:48 +0200 |
commit | d7a2401750bb29dfdb45929a536539b9f17b347f (patch) | |
tree | 2e06b1abc82af53c12a755adc71b3f763d6eba33 | |
parent | 7bc26de591c5de185e351e673f12aa69528ea75d (diff) | |
download | mariadb-git-d7a2401750bb29dfdb45929a536539b9f17b347f.tar.gz |
MDEV-20934 Infinite loop on innodb_fast_shutdown=0 with inconsistent change buffer
Due to a data corruption bug that may have occurred a long time earlier
(possibly involving physical backup and MySQL Bug #69122, which was
addressed in commit f166ec71b78fdf7a08ba413509cf00ad9e003b3c)
it seems possible that the InnoDB change buffer might end up containing
entries, while no buffered changes exist according to the change buffer
bitmap pages in the .ibd files.
ibuf_delete_recs(): New function, to be invoked on slow shutdown only.
Remove all buffered changes for a specific page.
ibuf_merge_or_delete_for_page(): If the change buffer bitmap is clean
and a slow shutdown is in progress, invoke ibuf_delete_recs().
We do not want to do that during normal operation, due to the additional
overhead that is involved. The bitmap page should be consistent with
the change buffer in the first place.
-rw-r--r-- | mysql-test/suite/innodb/r/ibuf_not_empty.result | 1 | ||||
-rw-r--r-- | mysql-test/suite/innodb/t/ibuf_not_empty.test | 30 | ||||
-rw-r--r-- | storage/innobase/ibuf/ibuf0ibuf.cc | 83 |
3 files changed, 106 insertions, 8 deletions
diff --git a/mysql-test/suite/innodb/r/ibuf_not_empty.result b/mysql-test/suite/innodb/r/ibuf_not_empty.result index 2c898b8916d..7c61e74850b 100644 --- a/mysql-test/suite/innodb/r/ibuf_not_empty.result +++ b/mysql-test/suite/innodb/r/ibuf_not_empty.result @@ -22,4 +22,5 @@ check table t1; Table Op Msg_type Msg_text test.t1 check Warning InnoDB: Index 'b' contains #### entries, should be 4096. test.t1 check error Corrupt +SET GLOBAL innodb_fast_shutdown=0; DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/ibuf_not_empty.test b/mysql-test/suite/innodb/t/ibuf_not_empty.test index 9ee0b180f44..8b16d197e03 100644 --- a/mysql-test/suite/innodb/t/ibuf_not_empty.test +++ b/mysql-test/suite/innodb/t/ibuf_not_empty.test @@ -40,15 +40,43 @@ INSERT INTO t1 SELECT 0,b,c FROM t1; INSERT INTO t1 SELECT 0,b,c FROM t1; INSERT INTO t1 SELECT 0,b,c FROM t1; INSERT INTO t1 SELECT 0,b,c FROM t1; +let MYSQLD_DATADIR=`select @@datadir`; +let PAGE_SIZE=`select @@innodb_page_size`; + +--source include/shutdown_mysqld.inc + +# Corrupt the change buffer bitmap, to claim that pages are clean +perl; +do "$ENV{MTR_SUITE_DIR}/include/crc32.pl"; +my $file = "$ENV{MYSQLD_DATADIR}/test/t1.ibd"; +open(FILE, "+<$file") || die "Unable to open $file"; +binmode FILE; +my $ps= $ENV{PAGE_SIZE}; +my $page; +sysseek(FILE, $ps, 0) || die "Unable to seek $file\n"; +die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps; +# Clean the change buffer bitmap. +substr($page,38,$ps - 38 - 8) = chr(0) x ($ps - 38 - 8); +my $polynomial = 0x82f63b78; # CRC-32C +my $ck= pack("N",mycrc32(substr($page, 4, 22), 0, $polynomial) ^ + mycrc32(substr($page, 38, $ps - 38 - 8), 0, $polynomial)); +substr($page,0,4)=$ck; +substr($page,$ps-8,4)=$ck; +sysseek(FILE, $ps, 0) || die "Unable to rewind $file\n"; +syswrite(FILE, $page, $ps)==$ps || die "Unable to write $file\n"; +close(FILE) || die "Unable to close $file"; +EOF --let $restart_parameters= --innodb-force-recovery=6 --innodb-change-buffer-dump ---source include/restart_mysqld.inc +--source include/start_mysqld.inc --replace_regex /contains \d+ entries/contains #### entries/ check table t1; --let $restart_parameters= --source include/restart_mysqld.inc +SET GLOBAL innodb_fast_shutdown=0; +--source include/restart_mysqld.inc # Cleanup DROP TABLE t1; diff --git a/storage/innobase/ibuf/ibuf0ibuf.cc b/storage/innobase/ibuf/ibuf0ibuf.cc index e701271379e..57ff91fc14e 100644 --- a/storage/innobase/ibuf/ibuf0ibuf.cc +++ b/storage/innobase/ibuf/ibuf0ibuf.cc @@ -2537,8 +2537,6 @@ ibuf_merge_space( ut_ad(space < SRV_LOG_SPACE_FIRST_ID); - ut_ad(space < SRV_LOG_SPACE_FIRST_ID); - ibuf_mtr_start(&mtr); /* Position the cursor on the first matching record. */ @@ -4329,6 +4327,71 @@ func_exit: return(TRUE); } +/** +Delete any buffered entries for a page. +This prevents an infinite loop on slow shutdown +in the case where the change buffer bitmap claims that no buffered +changes exist, while entries exist in the change buffer tree. +@param page_id page number for which there should be no unbuffered changes */ +ATTRIBUTE_COLD static void ibuf_delete_recs(const page_id_t page_id) +{ + ulint dops[IBUF_OP_COUNT]; + mtr_t mtr; + btr_pcur_t pcur; + mem_heap_t* heap = mem_heap_create(512); + const dtuple_t* tuple = ibuf_search_tuple_build( + page_id.space(), page_id.page_no(), heap); + memset(dops, 0, sizeof(dops)); + +loop: + ibuf_mtr_start(&mtr); + btr_pcur_open(ibuf->index, tuple, PAGE_CUR_GE, BTR_MODIFY_LEAF, + &pcur, &mtr); + + if (!btr_pcur_is_on_user_rec(&pcur)) { + ut_ad(btr_pcur_is_after_last_in_tree(&pcur, &mtr)); + goto func_exit; + } + + for (;;) { + ut_ad(btr_pcur_is_on_user_rec(&pcur)); + + const rec_t* ibuf_rec = btr_pcur_get_rec(&pcur); + + if (ibuf_rec_get_space(&mtr, ibuf_rec) + != page_id.space() + || ibuf_rec_get_page_no(&mtr, ibuf_rec) + != page_id.page_no()) { + break; + } + + dops[ibuf_rec_get_op_type(&mtr, ibuf_rec)]++; + + /* Delete the record from ibuf */ + if (ibuf_delete_rec(page_id.space(), page_id.page_no(), + &pcur, tuple, &mtr)) { + /* Deletion was pessimistic and mtr was committed: + we start from the beginning again */ + ut_ad(mtr.has_committed()); + goto loop; + } + + if (btr_pcur_is_after_last_on_page(&pcur)) { + ibuf_mtr_commit(&mtr); + btr_pcur_close(&pcur); + goto loop; + } + } + +func_exit: + ibuf_mtr_commit(&mtr); + btr_pcur_close(&pcur); + + ibuf_add_ops(ibuf->n_discarded_ops, dops); + + mem_heap_free(heap); +} + /** When an index page is read from a disk to the buffer pool, this function applies any buffered operations to the page and deletes the entries from the insert buffer. If the page is not read, but created in the buffer pool, this @@ -4348,9 +4411,7 @@ ibuf_merge_or_delete_for_page( const page_size_t* page_size, ibool update_ibuf_bitmap) { - mem_heap_t* heap; btr_pcur_t pcur; - dtuple_t* search_tuple; #ifdef UNIV_IBUF_DEBUG ulint volume = 0; #endif /* UNIV_IBUF_DEBUG */ @@ -4423,9 +4484,17 @@ ibuf_merge_or_delete_for_page( ibuf_mtr_commit(&mtr); if (!bitmap_bits) { - /* No inserts buffered for this page */ + /* No changes are buffered for this page. */ fil_space_release(space); + if (UNIV_UNLIKELY(srv_shutdown_state) + && !srv_fast_shutdown) { + /* Prevent an infinite loop on slow + shutdown, in case the bitmap bits are + wrongly clear even though buffered + changes exist. */ + ibuf_delete_recs(page_id); + } return; } } @@ -4438,9 +4507,9 @@ ibuf_merge_or_delete_for_page( space = NULL; } - heap = mem_heap_create(512); + mem_heap_t* heap = mem_heap_create(512); - search_tuple = ibuf_search_tuple_build( + const dtuple_t* search_tuple = ibuf_search_tuple_build( page_id.space(), page_id.page_no(), heap); if (block != NULL) { |