Skip to content

Commit 518d3e4

Browse files
Marko Mäkeläbjornmu
authored andcommitted
Bug#20691930 5.7.6 CRASH WHEN RUNNING MYSQL_UPGRADE AFTER BINARY UPGRADE
This is a regression from Bug#17345513 CHECKING FIL_PAGE_TYPE BREAKS COMPATIBILITY WITH OLD INNODB DATA FILES This affected a production environment where the data files were originally created with MySQL 5.0 or earlier. Originally, InnoDB only initialized FIL_PAGE_TYPE on two types of pages: #define FIL_PAGE_INDEX 17855 /*!< B-tree node */ #define FIL_PAGE_UNDO_LOG 2 /*!< Undo log page */ When files were allocated in the file system, the field was initialized to 0. When a page was initialized in the buffer pool, the field would be left uninitialized, reusing whatever value happened to be at that address (typically one of the 3 values). In the originally reported incident, page 32768 in the system tablespace is an allocation bitmap page, but the uninitialized FIL_PAGE_TYPE field on it happened to be FIL_PAGE_INDEX, which caused the flush-time check to fail. Our fix comprises the following parts: 1. Reset wrong page type on allocation bitmap pages and change buffer bitmap pages based on the page number, without checking the page contents and without writing redo log. #define FIL_PAGE_IBUF_BITMAP 5 /*!< Insert buffer bitmap */ #define FIL_PAGE_TYPE_FSP_HDR 8 /*!< File space header */ #define FIL_PAGE_TYPE_XDES 9 /*!< Extent descriptor page */ 2. On database startup, reset the page types on the following pages in the system tablespace, writing redo log: #define FSP_IBUF_HEADER_PAGE_NO 3 // init to 6=FIL_PAGE_TYPE_SYS #define FSP_TRX_SYS_PAGE_NO 5 // init to 7=FIL_PAGE_TYPE_TRX_SYS #define FSP_FIRST_RSEG_PAGE_NO 6 // init to 6=FIL_PAGE_TYPE_SYS #define FSP_DICT_HDR_PAGE_NO 7 // init to 6=FIL_PAGE_TYPE_SYS 3. Whenever we modify other types of pages, we reset the FIL_PAGE_TYPE within the same mini-transaction, to one of the following values: #define FIL_PAGE_INODE 3 /*!< Index node */ #define FIL_PAGE_TYPE_SYS 6 /*!< System page */ #define FIL_PAGE_TYPE_FSP_HDR 8 /*!< File space header */ #define FIL_PAGE_TYPE_XDES 9 /*!< Extent descriptor page */ Note: Some page types are initialized immediately after page allocation, and the pages are not modified further without changing the page type first. Nothing needs to be done for these page types, if the requirement is to have valid page type when we are writing back pages from the buffer pool to files. #define FIL_PAGE_IBUF_FREE_LIST 4 /*!< Insert buffer free list */ #define FIL_PAGE_TYPE_BLOB 10 /*!< Uncompressed BLOB page */ #define FIL_PAGE_TYPE_ZBLOB 11 /*!< First compressed BLOB page */ #define FIL_PAGE_TYPE_ZBLOB2 12 /*!< Subsequent compressed BLOB page */ Because MySQL does not officially support upgrade following by a server crash, there should be no legitimate usage scenario where such pages with an incorrect page type would be written out as a result of applying redo log during crash recovery. BLOB pages created before MySQL 5.1 could carry any page type (including FIL_PAGE_INDEX), but this should not be an issue, because existing BLOB pages are never updated in place. The BLOB columns are always updated by copy-on-write, with a valid FIL_PAGE_TYPE. Note: InnoDB never modifies BLOB pages in place. If BLOB data is modified, entirely new pages will be initialized and rewritten. Thus, no logic is implemented to update FIL_PAGE_TYPE on BLOB pages. This means that even after this fix, subsequent versions of MySQL must be prepared to read BLOB pages that contain anything in FIL_PAGE_TYPE. RB: 8314 Reviewed-by: Kevin Lewis <[email protected]> Reviewed-by: Vasil Dimov <[email protected]> Reviewed-by: Sunny Bains <[email protected]> (cherry picked from commit fc1f91f396bc73fcea72f17d6c22c174ba057e9b)
1 parent 403c683 commit 518d3e4

File tree

12 files changed

+355
-132
lines changed

12 files changed

+355
-132
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#
2+
# Bug#20691930 5.7.6 CRASH WHEN RUNNING MYSQL_UPGRADE
3+
# AFTER BINARY UPGRADE
4+
#
5+
CREATE TABLE t (a int primary key, b char(255) not null default '', index(b))
6+
ENGINE=InnoDB;
7+
INSERT t(a) VALUES(1),(2),(3),(4),(5),(6),(7),(8);
8+
# restart
9+
INSERT t(a) SELECT a+8 FROM t;
10+
INSERT t(a) SELECT a+16 FROM t;
11+
INSERT t(a) SELECT a+32 FROM t;
12+
INSERT t(a) SELECT a+64 FROM t;
13+
# Page types: 00080005000345bf(8)
14+
# restart
15+
DROP TABLE t;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--source include/have_innodb.inc
2+
# In old versions that wrote garbage into files, the page size was 16k.
3+
--source include/have_innodb_16k.inc
4+
# Embedded server does not support restarting.
5+
--source include/not_embedded.inc
6+
7+
--echo #
8+
--echo # Bug#20691930 5.7.6 CRASH WHEN RUNNING MYSQL_UPGRADE
9+
--echo # AFTER BINARY UPGRADE
10+
--echo #
11+
12+
CREATE TABLE t (a int primary key, b char(255) not null default '', index(b))
13+
ENGINE=InnoDB;
14+
15+
INSERT t(a) VALUES(1),(2),(3),(4),(5),(6),(7),(8);
16+
let $MYSQLD_DATADIR=`select @@datadir`;
17+
let IBD_FILE=$MYSQLD_DATADIR/test/t.ibd;
18+
let IBD_PAGE_SIZE=`select @@innodb_page_size`;
19+
20+
--source include/shutdown_mysqld.inc
21+
perl;
22+
use strict;
23+
use warnings;
24+
use Fcntl qw(:DEFAULT :seek);
25+
my $file = $ENV{IBD_FILE};
26+
my $size = $ENV{IBD_PAGE_SIZE};
27+
sysopen FILE, $file, O_RDWR || die "Unable to open $file: $!";
28+
die "Unable to read $file: $!" unless sysread(FILE, $_, $size*3) == $size*3;
29+
my $ck = pack("H*", "DEADBEEF");
30+
my $type = pack("H*", "45BF"); # FIL_PAGE_INDEX
31+
my $payload = $size-26-8;
32+
# Change each page type to FIL_PAGE_INDEX, and rewrite the checksum.
33+
s/....(.{16})(....)..(.{$payload})......../$ck.$1.$2.$type.$3.$ck.$2/ges;
34+
sysseek(FILE, 0, SEEK_SET) || die "Unable to seek $file: $!";
35+
syswrite(FILE, $_, $size * 3) || warn "Unable to write $file: $!";
36+
close(FILE) || die "Unable to close $file: $!";
37+
EOF
38+
39+
--source include/start_mysqld.inc
40+
41+
INSERT t(a) SELECT a+8 FROM t;
42+
INSERT t(a) SELECT a+16 FROM t;
43+
INSERT t(a) SELECT a+32 FROM t;
44+
INSERT t(a) SELECT a+64 FROM t;
45+
--source include/shutdown_mysqld.inc
46+
perl;
47+
use strict;
48+
use warnings;
49+
my $file = $ENV{IBD_FILE};
50+
my $size = $ENV{IBD_PAGE_SIZE};
51+
open(FILE, "<$file") || die "Unable to open $file: $!";
52+
die "Unable to read $file: $!" unless read(FILE, $_, $size*4) == $size*4;
53+
my $payload = $size-26;
54+
s/.{24}(..).{$payload}/$1/ges;
55+
print "# Page types: ", unpack("H*", substr($_, 0, 8)), "(", length($_), ")\n";
56+
close(FILE);
57+
EOF
58+
--source include/start_mysqld.inc
59+
DROP TABLE t;

storage/innobase/buf/buf0dblwr.cc

Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*****************************************************************************
22
3-
Copyright (c) 1995, 2014, Oracle and/or its affiliates. All Rights Reserved.
3+
Copyright (c) 1995, 2015, Oracle and/or its affiliates. All Rights Reserved.
44
55
This program is free software; you can redistribute it and/or modify it under
66
the terms of the GNU General Public License as published by the Free Software
@@ -732,17 +732,6 @@ buf_dblwr_check_block(
732732
return;
733733
}
734734

735-
/* On uncompressed pages, it is possible that invalid
736-
FIL_PAGE_TYPE was left behind by an older version of InnoDB
737-
that did not initialize FIL_PAGE_TYPE on other pages than
738-
B-tree pages. Here, we will reset invalid page types to
739-
FIL_PAGE_TYPE_UNKNOWN when flushing. */
740-
bool reset_invalid = block->page.zip.data == NULL;
741-
742-
if (reset_invalid) {
743-
buf_dblwr_check_page_lsn(block->frame);
744-
}
745-
746735
switch (fil_page_get_type(block->frame)) {
747736
case FIL_PAGE_INDEX:
748737
case FIL_PAGE_RTREE:
@@ -753,24 +742,21 @@ buf_dblwr_check_block(
753742
} else if (page_simple_validate_old(block->frame)) {
754743
return;
755744
}
756-
/* Do not attempt to fix the page type. While it is
757-
possible that this is not an index page but just
758-
happens to have this value in the uninitialized
759-
FIL_PAGE_TYPE field, it is also possible that we
760-
caught genuine corruption of an index page, and want
761-
to prevent the corruption from reaching the file
762-
system. */
763-
reset_invalid = false;
745+
/* While it is possible that this is not an index page
746+
but just happens to have wrongly set FIL_PAGE_TYPE,
747+
such pages should never be modified to without also
748+
adjusting the page type during page allocation or
749+
buf_flush_init_for_writing() or fil_page_reset_type(). */
764750
break;
751+
case FIL_PAGE_TYPE_FSP_HDR:
752+
case FIL_PAGE_IBUF_BITMAP:
765753
case FIL_PAGE_TYPE_UNKNOWN:
766754
/* Do not complain again, we already reset this field. */
767755
case FIL_PAGE_UNDO_LOG:
768756
case FIL_PAGE_INODE:
769757
case FIL_PAGE_IBUF_FREE_LIST:
770-
case FIL_PAGE_IBUF_BITMAP:
771758
case FIL_PAGE_TYPE_SYS:
772759
case FIL_PAGE_TYPE_TRX_SYS:
773-
case FIL_PAGE_TYPE_FSP_HDR:
774760
case FIL_PAGE_TYPE_XDES:
775761
case FIL_PAGE_TYPE_BLOB:
776762
case FIL_PAGE_TYPE_ZBLOB:
@@ -780,33 +766,9 @@ buf_dblwr_check_block(
780766
case FIL_PAGE_TYPE_ALLOCATED:
781767
/* empty pages should never be flushed */
782768
break;
783-
default:
784-
break;
785769
}
786770

787-
if (reset_invalid
788-
&& fil_space_get_flags(block->page.id.space()) == 0) {
789-
ib::warn()
790-
<< "Resetting unknown page "
791-
<< block->page.id << " type "
792-
<< fil_page_get_type(block->frame)
793-
<< " to " << FIL_PAGE_TYPE_UNKNOWN
794-
<< " before writing to data file.";
795-
/* We are skipping redo logging here, because this is
796-
a special case. Flushing is covered by previously
797-
generated redo log records (write-ahead log). */
798-
mach_write_to_2(block->frame
799-
+ FIL_PAGE_TYPE, FIL_PAGE_TYPE_UNKNOWN);
800-
} else {
801-
/* Only in tablespaces that were created before MySQL
802-
5.1, some pages could be created with garbage in the
803-
FIL_PAGE_TYPE field. These tablespaces always carried
804-
flags==0, indicating ROW_FORMAT=REDUNDANT or
805-
ROW_FORMAT=COMPACT. For all other tablespaces,
806-
FIL_PAGE_TYPE must always be valid, already during
807-
page creation. */
808-
buf_dblwr_assert_on_corrupt_block(block);
809-
}
771+
buf_dblwr_assert_on_corrupt_block(block);
810772
}
811773

812774
/********************************************************************//**

storage/innobase/buf/buf0flu.cc

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -801,19 +801,25 @@ buf_flush_update_zip_checksum(
801801
mach_write_to_4(page + FIL_PAGE_SPACE_OR_CHKSUM, checksum);
802802
}
803803

804-
/********************************************************************//**
805-
Initializes a page for writing to the tablespace. */
804+
/** Initialize a page for writing to the tablespace.
805+
@param[in] block buffer block; NULL if bypassing the buffer pool
806+
@param[in,out] page page frame
807+
@param[in,out] page_zip_ compressed page, or NULL if uncompressed
808+
@param[in] newest_lsn newest modification LSN to the page
809+
@param[in] skip_checksum whether to disable the page checksum */
806810
void
807811
buf_flush_init_for_writing(
808-
/*=======================*/
809-
byte* page, /*!< in/out: page */
810-
void* page_zip_, /*!< in/out: compressed page, or NULL */
811-
lsn_t newest_lsn, /*!< in: newest modification lsn
812-
to the page */
813-
bool skip_checksum) /*!< in: if true, disable/skip checksum. */
812+
const buf_block_t* block,
813+
byte* page,
814+
void* page_zip_,
815+
lsn_t newest_lsn,
816+
bool skip_checksum)
814817
{
815818
ib_uint32_t checksum = BUF_NO_CHECKSUM_MAGIC;
816819

820+
ut_ad(block == NULL || block->frame == page);
821+
ut_ad(block == NULL || page_zip_ == NULL
822+
|| &block->page.zip == page_zip_);
817823
ut_ad(page);
818824

819825
if (page_zip_) {
@@ -865,6 +871,58 @@ buf_flush_init_for_writing(
865871
if (skip_checksum) {
866872
mach_write_to_4(page + FIL_PAGE_SPACE_OR_CHKSUM, checksum);
867873
} else {
874+
if (block != NULL && UNIV_PAGE_SIZE == 16384) {
875+
/* The page type could be garbage in old files
876+
created before MySQL 5.5. Such files always
877+
had a page size of 16 kilobytes. */
878+
ulint page_type = fil_page_get_type(page);
879+
ulint reset_type = page_type;
880+
881+
switch (block->page.id.page_no() % 16384) {
882+
case 0:
883+
reset_type = block->page.id.page_no() == 0
884+
? FIL_PAGE_TYPE_FSP_HDR
885+
: FIL_PAGE_TYPE_XDES;
886+
break;
887+
case 1:
888+
reset_type = FIL_PAGE_IBUF_BITMAP;
889+
break;
890+
default:
891+
switch (page_type) {
892+
case FIL_PAGE_INDEX:
893+
case FIL_PAGE_RTREE:
894+
case FIL_PAGE_UNDO_LOG:
895+
case FIL_PAGE_INODE:
896+
case FIL_PAGE_IBUF_FREE_LIST:
897+
case FIL_PAGE_TYPE_ALLOCATED:
898+
case FIL_PAGE_TYPE_SYS:
899+
case FIL_PAGE_TYPE_TRX_SYS:
900+
case FIL_PAGE_TYPE_BLOB:
901+
case FIL_PAGE_TYPE_ZBLOB:
902+
case FIL_PAGE_TYPE_ZBLOB2:
903+
break;
904+
case FIL_PAGE_TYPE_FSP_HDR:
905+
case FIL_PAGE_TYPE_XDES:
906+
case FIL_PAGE_IBUF_BITMAP:
907+
/* These pages should have
908+
predetermined page numbers
909+
(see above). */
910+
default:
911+
reset_type = FIL_PAGE_TYPE_UNKNOWN;
912+
break;
913+
}
914+
}
915+
916+
if (UNIV_UNLIKELY(page_type != reset_type)) {
917+
ib::info()
918+
<< "Resetting invalid page "
919+
<< block->page.id << " type "
920+
<< page_type << " to "
921+
<< reset_type << " when flushing.";
922+
fil_page_set_type(page, reset_type);
923+
}
924+
}
925+
868926
switch ((srv_checksum_algorithm_t) srv_checksum_algorithm) {
869927
case SRV_CHECKSUM_ALGORITHM_CRC32:
870928
case SRV_CHECKSUM_ALGORITHM_STRICT_CRC32:
@@ -979,12 +1037,12 @@ buf_flush_write_block_low(
9791037
frame = ((buf_block_t*) bpage)->frame;
9801038
}
9811039

982-
buf_flush_init_for_writing(((buf_block_t*) bpage)->frame,
983-
bpage->zip.data
984-
? &bpage->zip : NULL,
985-
bpage->newest_modification,
986-
fsp_is_checksum_disabled(
987-
bpage->id.space()));
1040+
buf_flush_init_for_writing(
1041+
reinterpret_cast<const buf_block_t*>(bpage),
1042+
reinterpret_cast<const buf_block_t*>(bpage)->frame,
1043+
bpage->zip.data ? &bpage->zip : NULL,
1044+
bpage->newest_modification,
1045+
fsp_is_checksum_disabled(bpage->id.space()));
9881046
break;
9891047
}
9901048

storage/innobase/fil/fil0fil.cc

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2198,7 +2198,8 @@ fil_recreate_tablespace(
21982198
#endif /* UNIV_DEBUG */
21992199
page_zip.m_end = page_zip.m_nonempty = page_zip.n_blobs = 0;
22002200
buf_flush_init_for_writing(
2201-
page, &page_zip, 0, fsp_is_checksum_disabled(space_id));
2201+
NULL, page, &page_zip, 0,
2202+
fsp_is_checksum_disabled(space_id));
22022203

22032204
err = fil_write(page_id_t(space_id, 0), page_size, 0,
22042205
page_size.physical(), page_zip.data);
@@ -2262,7 +2263,7 @@ fil_recreate_tablespace(
22622263
ut_ad(!page_size.is_compressed());
22632264

22642265
buf_flush_init_for_writing(
2265-
page, NULL, recv_lsn,
2266+
block, page, NULL, recv_lsn,
22662267
fsp_is_checksum_disabled(space_id));
22672268

22682269
err = fil_write(cur_page_id, page_size, 0,
@@ -2277,7 +2278,7 @@ fil_recreate_tablespace(
22772278
buf_block_get_page_zip(block);
22782279

22792280
buf_flush_init_for_writing(
2280-
page, page_zip, recv_lsn,
2281+
block, page, page_zip, recv_lsn,
22812282
fsp_is_checksum_disabled(space_id));
22822283

22832284
err = fil_write(cur_page_id, page_size, 0,
@@ -3445,7 +3446,8 @@ fil_ibd_create(
34453446

34463447
if (!page_size.is_compressed()) {
34473448
buf_flush_init_for_writing(
3448-
page, NULL, 0, fsp_is_checksum_disabled(space_id));
3449+
NULL, page, NULL, 0,
3450+
fsp_is_checksum_disabled(space_id));
34493451
success = os_file_write(path, file, page, 0,
34503452
page_size.physical());
34513453
} else {
@@ -3460,7 +3462,8 @@ fil_ibd_create(
34603462
page_zip.n_blobs = 0;
34613463

34623464
buf_flush_init_for_writing(
3463-
page, &page_zip, 0, fsp_is_checksum_disabled(space_id));
3465+
NULL, page, &page_zip, 0,
3466+
fsp_is_checksum_disabled(space_id));
34643467
success = os_file_write(path, file, page_zip.data, 0,
34653468
page_size.physical());
34663469
}
@@ -5566,18 +5569,25 @@ fil_page_set_type(
55665569
mach_write_to_2(page + FIL_PAGE_TYPE, type);
55675570
}
55685571

5569-
/*********************************************************************//**
5570-
Gets the file page type.
5571-
@return type; NOTE that if the type has not been written to page, the
5572-
return value not defined */
5573-
ulint
5574-
fil_page_get_type(
5575-
/*==============*/
5576-
const byte* page) /*!< in: file page */
5572+
/** Reset the page type.
5573+
Data files created before MySQL 5.1 may contain garbage in FIL_PAGE_TYPE.
5574+
In MySQL 3.23.53, only undo log pages and index pages were tagged.
5575+
Any other pages were written with unitialized bytes in FIL_PAGE_TYPE.
5576+
@param[in] page_id page number
5577+
@param[in,out] page page with invalid FIL_PAGE_TYPE
5578+
@param[in] type expected page type
5579+
@param[in,out] mtr mini-transaction */
5580+
void
5581+
fil_page_reset_type(
5582+
const page_id_t& page_id,
5583+
byte* page,
5584+
ulint type,
5585+
mtr_t* mtr)
55775586
{
5578-
ut_ad(page);
5579-
5580-
return(mach_read_from_2(page + FIL_PAGE_TYPE));
5587+
ib::info()
5588+
<< "Resetting invalid page " << page_id << " type "
5589+
<< fil_page_get_type(page) << " to " << type << ".";
5590+
mlog_write_ulint(page + FIL_PAGE_TYPE, type, MLOG_2BYTES, mtr);
55815591
}
55825592

55835593
/****************************************************************//**

0 commit comments

Comments
 (0)