Commit d7a24017 authored by Marko Mäkelä's avatar Marko Mäkelä

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 f166ec71)
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.
parent 7bc26de5
......@@ -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;
......@@ -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;
......@@ -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 @@ ibuf_delete_rec(
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) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment