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

MDEV-12123 Page contains nonzero PAGE_MAX_TRX_ID

When MDEV-6076 repurposed the field PAGE_MAX_TRX_ID, it was assumed
that the field always was 0 in the clustered index of old data files.
This was not the case in IMPORT TABLESPACE (introduced in MySQL 5.6
and MariaDB 10.0), which is writing the transaction ID to all index
pages, including clustered index pages.

This means that on a data file that was at some point of its life
IMPORTed to an InnoDB instance, MariaDB 10.2.4 or later could interpret
the transaction ID as a persistent AUTO_INCREMENT value.

This also means that future changes that repurpose PAGE_MAX_TRX_ID
in the clustered index may cause trouble with files that were imported
at some point of their life.

There is a separate minor issue that InnoDB is writing PAGE_MAX_TRX_ID
to every secondary index page, even though it is only needed on leaf
pages. From now on we will write PAGE_MAX_TRX_ID as 0 to non-leaf pages,
just to be able to keep stricter debug assertions.

btr_root_raise_and_insert(): Reset the PAGE_MAX_TRX_ID field on non-root
pages of the clustered index, and on the no-longer-leaf root page of
secondary indexes.

AbstractCallback::is_root_page(): Remove. Use page_is_root() instead.

PageConverter::update_index_page(): Reset the PAGE_MAX_TRX_ID to 0
on other pages than the clustered index root page or secondary index
leaf pages.
parent 0b52b28b
......@@ -796,7 +796,7 @@ t1 CREATE TABLE `t1` (
DROP TABLE test_wl5522.t1;
CREATE TABLE test_wl5522.t1 (c1 INT, c2 VARCHAR(1024), c3 BLOB) ENGINE = Innodb;
INSERT IGNORE INTO test_wl5522.t1 VALUES
(100, REPEAT('Karanbir', 899), REPEAT('Ajeeth', 1200));
(100, REPEAT('Karanbir', 899), REPEAT('Ajeeth', 2731));
Warnings:
Warning 1265 Data truncated for column 'c2' at row 1
INSERT INTO test_wl5522.t1 SELECT * FROM test_wl5522.t1;
......
......@@ -1164,7 +1164,7 @@ DROP TABLE test_wl5522.t1;
CREATE TABLE test_wl5522.t1 (c1 INT, c2 VARCHAR(1024), c3 BLOB) ENGINE = Innodb;
INSERT IGNORE INTO test_wl5522.t1 VALUES
(100, REPEAT('Karanbir', 899), REPEAT('Ajeeth', 1200));
(100, REPEAT('Karanbir', 899), REPEAT('Ajeeth', 2731));
INSERT INTO test_wl5522.t1 SELECT * FROM test_wl5522.t1;
INSERT INTO test_wl5522.t1 SELECT * FROM test_wl5522.t1;
......
......@@ -1602,7 +1602,8 @@ btr_page_reorganize_low(
/* Copy the PAGE_MAX_TRX_ID or PAGE_ROOT_AUTO_INC. */
memcpy(page + (PAGE_HEADER + PAGE_MAX_TRX_ID),
temp_page + (PAGE_HEADER + PAGE_MAX_TRX_ID), 8);
/* PAGE_MAX_TRX_ID is unused in clustered index pages,
/* PAGE_MAX_TRX_ID is unused in clustered index pages
(other than the root where it is repurposed as PAGE_ROOT_AUTO_INC),
non-leaf pages, and in temporary tables. It was always
zero-initialized in page_create() in all InnoDB versions.
PAGE_MAX_TRX_ID must be nonzero on dict_index_is_sec_or_ibuf()
......@@ -1983,6 +1984,36 @@ btr_root_raise_and_insert(
index);
}
if (dict_index_is_sec_or_ibuf(index)) {
/* In secondary indexes and the change buffer,
PAGE_MAX_TRX_ID can be reset on the root page, because
the field only matters on leaf pages, and the root no
longer is a leaf page. (Older versions of InnoDB did
set PAGE_MAX_TRX_ID on all secondary index pages.) */
if (root_page_zip) {
page_zip_write_header(
root_page_zip,
PAGE_HEADER + PAGE_MAX_TRX_ID
+ root, 0, mtr);
} else {
mlog_write_ull(PAGE_HEADER + PAGE_MAX_TRX_ID
+ root, 0, mtr);
}
} else {
/* PAGE_ROOT_AUTO_INC is only present in the clustered index
root page; on other clustered index pages, we want to reserve
the field PAGE_MAX_TRX_ID for future use. */
if (new_page_zip) {
page_zip_write_header(
new_page_zip,
PAGE_HEADER + PAGE_MAX_TRX_ID
+ new_page, 0, mtr);
} else {
mlog_write_ull(PAGE_HEADER + PAGE_MAX_TRX_ID
+ new_page, 0, mtr);
}
}
/* If this is a pessimistic insert which is actually done to
perform a pessimistic update then we have stored the lock
information of the record to be inserted on the infimum of the
......
......@@ -468,16 +468,6 @@ class AbstractCallback : public PageCallback {
return(DB_SUCCESS);
}
/**
@return true if it is a root page */
bool is_root_page(const page_t* page) const UNIV_NOTHROW
{
ut_ad(fil_page_index_page_check(page));
return(mach_read_from_4(page + FIL_PAGE_NEXT) == FIL_NULL
&& mach_read_from_4(page + FIL_PAGE_PREV) == FIL_NULL);
}
/** Check if the page is marked as free in the extent descriptor.
@param page_no page number to check in the extent descriptor.
@return true if the page is marked as free */
......@@ -669,7 +659,7 @@ FetchIndexRootPages::operator() (
err = set_current_xdes(block->page.id.page_no(), page);
} else if (fil_page_index_page_check(page)
&& !is_free(block->page.id.page_no())
&& is_root_page(page)) {
&& page_is_root(page)) {
index_id_t id = btr_page_get_index_id(page);
......@@ -1831,12 +1821,28 @@ PageConverter::update_index_page(
btr_page_set_index_id(
page, m_page_zip_ptr, m_index->m_srv_index->id, 0);
page_set_max_trx_id(block, m_page_zip_ptr, m_trx->id, 0);
if (dict_index_is_clust(m_index->m_srv_index)) {
if (page_is_root(page)) {
/* Preserve the PAGE_ROOT_AUTO_INC. */
} else {
/* Clear PAGE_MAX_TRX_ID so that it can be
used for other purposes in the future. IMPORT
in MySQL 5.6, 5.7 and MariaDB 10.0 and 10.1
would set the field to the transaction ID even
on clustered index pages. */
page_set_max_trx_id(block, m_page_zip_ptr, 0, NULL);
}
} else {
/* Set PAGE_MAX_TRX_ID on secondary index leaf pages,
and clear it on non-leaf pages. */
page_set_max_trx_id(block, m_page_zip_ptr,
page_is_leaf(page) ? m_trx->id : 0, NULL);
}
if (page_is_empty(block->frame)) {
if (page_is_empty(page)) {
/* Only a root page can be empty. */
if (!is_root_page(block->frame)) {
if (!page_is_root(page)) {
// TODO: We should relax this and skip secondary
// indexes. Mark them as corrupt because they can
// always be rebuilt.
......
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