Skip to content

Commit 6e5333f

Browse files
MDEV-32445 InnoDB may corrupt its log before upgrading it on startup
Problem: ======== During upgrade, InnoDB does write the redo log for adjusting the tablespace size or tablespace flags even before the log has upgraded to configured format. This could lead to data inconsistent if any crash happened during upgrade process. Fix: === srv_start(): Write the tablespace flags adjustment, increased tablespace size redo log only after redo log upgradation. log_write_low(), log_reserve_and_write_fast(): Check whether the redo log is in physical format.
1 parent 1b568fb commit 6e5333f

File tree

5 files changed

+191
-87
lines changed

5 files changed

+191
-87
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
call mtr.add_suppression("InnoDB: The change buffer is corrupted");
2+
call mtr.add_suppression("InnoDB: Tablespace size stored in header is 768 pages, but the sum of data file sizes is 384 pages");
3+
call mtr.add_suppression("InnoDB: adjusting FSP_SPACE_FLAGS of file");
4+
# restart: --innodb-data-home-dir=MYSQLTEST_VARDIR/tmp/log_upgrade --innodb-log-group-home-dir=MYSQLTEST_VARDIR/tmp/log_upgrade --innodb-force-recovery=5 --innodb-log-file-size=4m --innodb_page_size=32k --innodb_buffer_pool_size=10M
5+
SELECT COUNT(*) FROM INFORMATION_SCHEMA.ENGINES
6+
WHERE engine = 'innodb'
7+
AND support IN ('YES', 'DEFAULT', 'ENABLED');
8+
COUNT(*)
9+
1
10+
FOUND 1 /InnoDB: Upgrading redo log:/ in mysqld.1.err
11+
# restart
12+
# End of 10.5 tests
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
--source include/have_innodb.inc
2+
--source include/big_test.inc
3+
--source include/not_embedded.inc
4+
call mtr.add_suppression("InnoDB: The change buffer is corrupted");
5+
call mtr.add_suppression("InnoDB: Tablespace size stored in header is 768 pages, but the sum of data file sizes is 384 pages");
6+
call mtr.add_suppression("InnoDB: adjusting FSP_SPACE_FLAGS of file");
7+
--source include/shutdown_mysqld.inc
8+
let bugdir= $MYSQLTEST_VARDIR/tmp/log_upgrade;
9+
--mkdir $bugdir
10+
--let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.1.err
11+
--let $dirs= --innodb-data-home-dir=$bugdir --innodb-log-group-home-dir=$bugdir
12+
13+
# Test case similar to log_upgrade.test
14+
perl;
15+
do "$ENV{MTR_SUITE_DIR}/../innodb/include/crc32.pl";
16+
my $polynomial = 0x82f63b78; # CRC-32C
17+
18+
die unless open OUT, ">", "$ENV{bugdir}/ibdata1";
19+
binmode OUT;
20+
21+
my $head = pack("Nx[18]", 0);
22+
# Add FSP_SPACE_FLAGS as 49152 (10.1.0...10.1.20), page_size = 32k
23+
my $body = pack("x[8]Nx[4]Nx[2]Nx[32696]", 768, 49152, 97937874);
24+
my $ck = mycrc32($head, 0, $polynomial) ^ mycrc32($body, 0, $polynomial);
25+
print OUT pack("N",$ck).$head.pack("x[12]").$body.pack("Nx[4]",$ck);
26+
# Dummy pages 1..6.
27+
print OUT chr(0) x (6 * 32768);
28+
# Dictionary header page (page 7).
29+
$head = pack("Nx[18]", 7);
30+
$body = pack("x[32]Nx[8]Nx[32674]", 8, 9);
31+
$ck = mycrc32($head, 0, $polynomial) ^ mycrc32($body, 0, $polynomial);
32+
print OUT pack("N",$ck).$head.pack("x[12]").$body.pack("Nx[4]",$ck);
33+
34+
# Empty SYS_TABLES page (page 8).
35+
$head = pack("NNNx[8]n", 8, ~0, ~0, 17855);
36+
$body = pack("nnx[31]Cx[20]", 2, 124, 1);
37+
$body .= pack("nxnn", 0x801, 3, 116) . "infimum";
38+
$body .= pack("xnxnxx", 0x901, 0x803) . "supremum";
39+
$body .= pack("x[32632]nn", 116, 101);
40+
$ck = mycrc32($head, 0, $polynomial) ^ mycrc32($body, 0, $polynomial);
41+
print OUT pack("N",$ck).$head.pack("x[12]").$body.pack("Nx[4]",$ck);
42+
43+
# Empty SYS_INDEXES page (page 9).
44+
$head = pack("NNNx[8]n", 9, ~0, ~0, 17855);
45+
$body = pack("nnx[31]Cx[20]", 2, 124, 3);
46+
$body .= pack("nxnn", 0x801, 3, 116) . "infimum";
47+
$body .= pack("xnxnxx", 0x901, 0x803) . "supremum";
48+
$body .= pack("x[32632]nn", 116, 101);
49+
$ck = mycrc32($head, 0, $polynomial) ^ mycrc32($body, 0, $polynomial);
50+
print OUT pack("N",$ck).$head.pack("x[12]").$body.pack("Nx[4]",$ck);
51+
52+
die unless seek(OUT, 768 * 16384 - 1, 0);
53+
print OUT chr(0);
54+
close OUT or die;
55+
56+
die unless open OUT, ">", "$ENV{bugdir}/ib_logfile0";
57+
binmode OUT;
58+
$_= pack("Nx[5]nx[5]", 1, 0x1286) . "BogoDB 4.3.2.1" . chr(0) x 478;
59+
print OUT $_, pack("N", mycrc32($_, 0, $polynomial));
60+
# checkpoint page 1 and all-zero checkpoint 2
61+
$_= pack("x[13]nCNNx[484]", 0x1286, 12, 2, 0x80c);
62+
print OUT $_, pack("N", mycrc32($_, 0, $polynomial));
63+
die unless seek(OUT, 0x1FFFFFFFF, 0);
64+
print OUT chr(0);
65+
close OUT or die;
66+
die unless open OUT, ">", "$ENV{bugdir}/ib_logfile1";
67+
binmode OUT;
68+
die unless seek(OUT, 0x800, 0); # the first 2048 bytes are unused!
69+
$_= pack("Nnnx[500]", 0x80000944, 12, 12);
70+
print OUT $_, pack("N", mycrc32($_, 0, $polynomial));
71+
die unless seek(OUT, 0x1FFFFFFFF, 0);
72+
print OUT chr(0);
73+
close OUT or die;
74+
EOF
75+
76+
--let $restart_parameters= $dirs --innodb-force-recovery=5 --innodb-log-file-size=4m --innodb_page_size=32k --innodb_buffer_pool_size=10M
77+
--source include/start_mysqld.inc
78+
SELECT COUNT(*) FROM INFORMATION_SCHEMA.ENGINES
79+
WHERE engine = 'innodb'
80+
AND support IN ('YES', 'DEFAULT', 'ENABLED');
81+
--source include/shutdown_mysqld.inc
82+
--let SEARCH_PATTERN= InnoDB: Upgrading redo log:
83+
--source include/search_pattern_in_file.inc
84+
--let $restart_parameters= $dirs
85+
86+
--remove_files_wildcard $bugdir
87+
--rmdir $bugdir
88+
--let $restart_parameters=
89+
--source include/start_mysqld.inc
90+
91+
--echo # End of 10.5 tests

storage/innobase/include/log0log.inl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ log_reserve_and_write_fast(
270270
}
271271

272272
lsn_t lsn = log_sys.get_lsn();
273+
ut_ad(log_sys.is_physical());
273274
*start_lsn = lsn;
274275

275276
memcpy(log_sys.buf + log_sys.buf_free, str, len);

storage/innobase/mtr/mtr0mtr.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,8 @@ static void log_write_low(const void *str, size_t size)
850850
len= trailer_offset - log_sys.buf_free % OS_FILE_LOG_BLOCK_SIZE;
851851
}
852852

853+
ut_ad(log_sys.is_physical());
854+
853855
memcpy(log_sys.buf + log_sys.buf_free, str, len);
854856

855857
size-= len;

storage/innobase/srv/srv0start.cc

Lines changed: 85 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,91 +1555,6 @@ dberr_t srv_start(bool create_new_db)
15551555

15561556
fil_system.space_id_reuse_warned = false;
15571557

1558-
if (!srv_read_only_mode) {
1559-
const ulint flags = FSP_FLAGS_PAGE_SSIZE();
1560-
for (ulint id = 0; id <= srv_undo_tablespaces; id++) {
1561-
if (fil_space_t* space = fil_space_get(id)) {
1562-
fsp_flags_try_adjust(space, flags);
1563-
}
1564-
}
1565-
1566-
if (sum_of_new_sizes > 0) {
1567-
/* New data file(s) were added */
1568-
mtr.start();
1569-
mtr.x_lock_space(fil_system.sys_space,
1570-
__FILE__, __LINE__);
1571-
buf_block_t* block = buf_page_get(
1572-
page_id_t(0, 0), 0,
1573-
RW_SX_LATCH, &mtr);
1574-
ulint size = mach_read_from_4(
1575-
FSP_HEADER_OFFSET + FSP_SIZE
1576-
+ block->frame);
1577-
ut_ad(size == fil_system.sys_space
1578-
->size_in_header);
1579-
size += sum_of_new_sizes;
1580-
mtr.write<4>(*block,
1581-
FSP_HEADER_OFFSET + FSP_SIZE
1582-
+ block->frame, size);
1583-
fil_system.sys_space->size_in_header
1584-
= uint32_t(size);
1585-
mtr.commit();
1586-
/* Immediately write the log record about
1587-
increased tablespace size to disk, so that it
1588-
is durable even if mysqld would crash
1589-
quickly */
1590-
log_buffer_flush_to_disk();
1591-
}
1592-
}
1593-
1594-
#ifdef UNIV_DEBUG
1595-
{
1596-
mtr.start();
1597-
buf_block_t* block = buf_page_get(page_id_t(0, 0), 0,
1598-
RW_S_LATCH, &mtr);
1599-
ut_ad(mach_read_from_4(FSP_SIZE + FSP_HEADER_OFFSET
1600-
+ block->frame)
1601-
== fil_system.sys_space->size_in_header);
1602-
mtr.commit();
1603-
}
1604-
#endif
1605-
const ulint tablespace_size_in_header
1606-
= fil_system.sys_space->size_in_header;
1607-
const ulint sum_of_data_file_sizes
1608-
= srv_sys_space.get_sum_of_sizes();
1609-
/* Compare the system tablespace file size to what is
1610-
stored in FSP_SIZE. In srv_sys_space.open_or_create()
1611-
we already checked that the file sizes match the
1612-
innodb_data_file_path specification. */
1613-
if (srv_read_only_mode
1614-
|| sum_of_data_file_sizes == tablespace_size_in_header) {
1615-
/* Do not complain about the size. */
1616-
} else if (!srv_sys_space.can_auto_extend_last_file()
1617-
|| sum_of_data_file_sizes
1618-
< tablespace_size_in_header) {
1619-
ib::error() << "Tablespace size stored in header is "
1620-
<< tablespace_size_in_header
1621-
<< " pages, but the sum of data file sizes is "
1622-
<< sum_of_data_file_sizes << " pages";
1623-
1624-
if (srv_force_recovery == 0
1625-
&& sum_of_data_file_sizes
1626-
< tablespace_size_in_header) {
1627-
ib::error() <<
1628-
"Cannot start InnoDB. The tail of"
1629-
" the system tablespace is"
1630-
" missing. Have you edited"
1631-
" innodb_data_file_path in my.cnf"
1632-
" in an inappropriate way, removing"
1633-
" data files from there?"
1634-
" You can set innodb_force_recovery=1"
1635-
" in my.cnf to force"
1636-
" a startup if you are trying to"
1637-
" recover a badly corrupt database.";
1638-
1639-
return(srv_init_abort(DB_ERROR));
1640-
}
1641-
}
1642-
16431558
if (srv_operation == SRV_OPERATION_RESTORE
16441559
|| srv_operation == SRV_OPERATION_RESTORE_EXPORT) {
16451560
buf_flush_sync();
@@ -1667,7 +1582,6 @@ dberr_t srv_start(bool create_new_db)
16671582
}
16681583
return(err);
16691584
}
1670-
16711585
/* Upgrade or resize or rebuild the redo logs before
16721586
generating any dirty pages, so that the old redo log
16731587
file will not be written to. */
@@ -1735,8 +1649,92 @@ dberr_t srv_start(bool create_new_db)
17351649
return(srv_init_abort(err));
17361650
}
17371651
}
1738-
17391652
recv_sys.debug_free();
1653+
1654+
if (!srv_read_only_mode) {
1655+
const ulint flags = FSP_FLAGS_PAGE_SSIZE();
1656+
for (ulint id = 0; id <= srv_undo_tablespaces; id++) {
1657+
if (fil_space_t* space = fil_space_get(id)) {
1658+
fsp_flags_try_adjust(space, flags);
1659+
}
1660+
}
1661+
1662+
if (sum_of_new_sizes > 0) {
1663+
/* New data file(s) were added */
1664+
mtr.start();
1665+
mtr.x_lock_space(fil_system.sys_space,
1666+
__FILE__, __LINE__);
1667+
buf_block_t* block = buf_page_get(
1668+
page_id_t(0, 0), 0,
1669+
RW_SX_LATCH, &mtr);
1670+
ulint size = mach_read_from_4(
1671+
FSP_HEADER_OFFSET + FSP_SIZE
1672+
+ block->frame);
1673+
ut_ad(size == fil_system.sys_space
1674+
->size_in_header);
1675+
size += sum_of_new_sizes;
1676+
mtr.write<4>(*block,
1677+
FSP_HEADER_OFFSET + FSP_SIZE
1678+
+ block->frame, size);
1679+
fil_system.sys_space->size_in_header
1680+
= uint32_t(size);
1681+
mtr.commit();
1682+
/* Immediately write the log record about
1683+
increased tablespace size to disk, so that it
1684+
is durable even if mysqld would crash
1685+
quickly */
1686+
log_buffer_flush_to_disk();
1687+
}
1688+
}
1689+
1690+
#ifdef UNIV_DEBUG
1691+
{
1692+
mtr.start();
1693+
buf_block_t* block = buf_page_get(page_id_t(0, 0), 0,
1694+
RW_S_LATCH, &mtr);
1695+
ut_ad(mach_read_from_4(FSP_SIZE + FSP_HEADER_OFFSET
1696+
+ block->frame)
1697+
== fil_system.sys_space->size_in_header);
1698+
mtr.commit();
1699+
}
1700+
#endif
1701+
const ulint tablespace_size_in_header
1702+
= fil_system.sys_space->size_in_header;
1703+
const ulint sum_of_data_file_sizes
1704+
= srv_sys_space.get_sum_of_sizes();
1705+
/* Compare the system tablespace file size to what is
1706+
stored in FSP_SIZE. In srv_sys_space.open_or_create()
1707+
we already checked that the file sizes match the
1708+
innodb_data_file_path specification. */
1709+
if (srv_read_only_mode
1710+
|| sum_of_data_file_sizes == tablespace_size_in_header) {
1711+
/* Do not complain about the size. */
1712+
} else if (!srv_sys_space.can_auto_extend_last_file()
1713+
|| sum_of_data_file_sizes
1714+
< tablespace_size_in_header) {
1715+
ib::error() << "Tablespace size stored in header is "
1716+
<< tablespace_size_in_header
1717+
<< " pages, but the sum of data file sizes is "
1718+
<< sum_of_data_file_sizes << " pages";
1719+
1720+
if (srv_force_recovery == 0
1721+
&& sum_of_data_file_sizes
1722+
< tablespace_size_in_header) {
1723+
ib::error() <<
1724+
"Cannot start InnoDB. The tail of"
1725+
" the system tablespace is"
1726+
" missing. Have you edited"
1727+
" innodb_data_file_path in my.cnf"
1728+
" in an inappropriate way, removing"
1729+
" data files from there?"
1730+
" You can set innodb_force_recovery=1"
1731+
" in my.cnf to force"
1732+
" a startup if you are trying to"
1733+
" recover a badly corrupt database.";
1734+
1735+
return(srv_init_abort(DB_ERROR));
1736+
}
1737+
}
17401738
}
17411739

17421740
ut_ad(err == DB_SUCCESS);

0 commit comments

Comments
 (0)