Skip to content

Commit 1b31d88

Browse files
committed
MDEV-17899: Fix a regression from MDEV-17793
The fix of MDEV-17793 was updating SYS_INDEXES.TABLE_ID in order to make the table invisible to purge (lazily delete old undo log records). By design of InnoDB, an update of TABLE_ID cannot be rolled back, because the rollback would effectively drop all indexes of the table due to the internal 'trigger' on SYS_INDEXES modifications. So, we revert the code change of MDEV-17793 and instead fix MDEV-17793 in a different way: by tweaking the undo log parsing during purge. The MDEV-17793 bug scenario is that a table becomes empty and a third instant ALTER TABLE is executed before purge processes the undo log record for the second instant ALTER TABLE. After this point, when purge sees the record, the table could have a mismatching number of rows. The test case works with this alternative fix. But what about a scenario where a fourth instant ALTER TABLE arrives before purge processes the second one? Could anything bad happen? Purge is only doing two things: First, free any BLOBs that were affected by the update record, and then, reset the DB_TRX_ID,DB_ROLL_PTR if a matching record is found. For the hidden metadata record, the only BLOB that we update is the hidden metadata BLOB that was introduced by MDEV-15562. Any other BLOBs (for the initial default values of instantly added columns) are never updated. So, in our scenario, the metadata BLOB that was created by the first instant ALTER TABLE (if it involved dropping or permuting columns) would be freed by purge when it is processing the undo record of the second ALTER TABLE. The BLOB value that was written by the second ALTER TABLE should be freed when the table is emptied. This is currently not done: MDEV-17383 should fix that. There is no possibility of double-free, because purge would only free old values of BLOBs. What about MVCC and other callers of trx_undo_update_rec_get_update()? The answer is simple: they should never be accessing the hidden metadata record in the first place. dict_table_t::reassign_id(): Remove. btr_cur_pessimistic_delete(): Clarify a comment. row_mysql_table_id_reassign(), row_discard_tablespace_for_mysql(): Add comments explaining that the operation cannot be rolled back. trx_undo_update_rec_get_update(): Avoid out-of-bounds access when parsing a metadata record. Avoid unnecessary memory allocation when filtering out fields from the update vector.
1 parent 1c53aef commit 1b31d88

File tree

5 files changed

+60
-98
lines changed

5 files changed

+60
-98
lines changed

storage/innobase/btr/btr0cur.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5836,7 +5836,8 @@ btr_cur_pessimistic_delete(
58365836
only user record, also delete the metadata record
58375837
if one exists for instant ADD COLUMN
58385838
(not generic ALTER TABLE).
5839-
If we are deleting the metadata record and the
5839+
If we are deleting the metadata record
5840+
(in the rollback of instant ALTER TABLE) and the
58405841
table becomes empty, clean up the whole page. */
58415842

58425843
const rec_t* first_rec = page_rec_get_next_const(

storage/innobase/handler/handler0alter.cc

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5277,53 +5277,6 @@ dict_index_t::instant_metadata(const dtuple_t& row, mem_heap_t* heap) const
52775277
return entry;
52785278
}
52795279

5280-
/** Assign a new id to invalidate old undo log records, so
5281-
that purge will be unable to refer to fields that used to be
5282-
instantly added to the end of the index. This is only to be
5283-
used during ALTER TABLE when the table is empty, before
5284-
invoking dict_index_t::clear_instant_alter().
5285-
@param[in,out] trx dictionary transaction
5286-
@return error code */
5287-
inline dberr_t dict_table_t::reassign_id(trx_t* trx)
5288-
{
5289-
DBUG_ASSERT(instant);
5290-
ut_ad(magic_n == DICT_TABLE_MAGIC_N);
5291-
ut_ad(!is_temporary());
5292-
5293-
table_id_t new_id;
5294-
dict_hdr_get_new_id(&new_id, NULL, NULL);
5295-
pars_info_t* pinfo = pars_info_create();
5296-
5297-
pars_info_add_ull_literal(pinfo, "old", id);
5298-
pars_info_add_ull_literal(pinfo, "new", new_id);
5299-
5300-
ut_ad(mutex_own(&dict_sys->mutex));
5301-
ut_ad(rw_lock_own(dict_operation_lock, RW_LOCK_X));
5302-
ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH);
5303-
5304-
dberr_t err = que_eval_sql(
5305-
pinfo,
5306-
"PROCEDURE RENUMBER_TABLE_ID_PROC () IS\n"
5307-
"BEGIN\n"
5308-
"UPDATE SYS_TABLES SET ID=:new WHERE ID=:old;\n"
5309-
"UPDATE SYS_COLUMNS SET TABLE_ID=:new WHERE TABLE_ID=:old;\n"
5310-
"UPDATE SYS_INDEXES SET TABLE_ID=:new WHERE TABLE_ID=:old;\n"
5311-
"UPDATE SYS_VIRTUAL SET TABLE_ID=:new WHERE TABLE_ID=:old;\n"
5312-
"END;\n"
5313-
, FALSE, trx);
5314-
if (err == DB_SUCCESS) {
5315-
auto fold = ut_fold_ull(id);
5316-
HASH_DELETE(dict_table_t, id_hash, dict_sys->table_id_hash,
5317-
fold, this);
5318-
id = new_id;
5319-
fold = ut_fold_ull(id);
5320-
HASH_INSERT(dict_table_t, id_hash, dict_sys->table_id_hash,
5321-
fold, this);
5322-
}
5323-
5324-
return err;
5325-
}
5326-
53275280
/** Insert or update SYS_COLUMNS and the hidden metadata record
53285281
for instant ALTER TABLE.
53295282
@param[in] ha_alter_info ALTER TABLE context
@@ -5612,19 +5565,6 @@ static bool innobase_instant_try(
56125565
empty_table:
56135566
/* The table is empty. */
56145567
ut_ad(page_is_root(block->frame));
5615-
if (index->table->instant) {
5616-
/* Assign a new dict_table_t::id
5617-
to invalidate old undo log records in purge,
5618-
so that they cannot refer to fields that were
5619-
instantly added to the end of the index,
5620-
instead of using the canonical positions
5621-
that will be replaced below
5622-
by index->clear_instant_alter(). */
5623-
err = index->table->reassign_id(trx);
5624-
if (err != DB_SUCCESS) {
5625-
goto func_exit;
5626-
}
5627-
}
56285568
/* MDEV-17383: free metadata BLOBs! */
56295569
btr_page_empty(block, NULL, index, 0, &mtr);
56305570
index->clear_instant_alter();

storage/innobase/include/dict0mem.h

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1676,15 +1676,6 @@ struct dict_table_t {
16761676
const char* old_v_col_names,
16771677
const ulint* col_map);
16781678

1679-
/** Assign a new id to invalidate old undo log records, so
1680-
that purge will be unable to refer to fields that used to be
1681-
instantly added to the end of the index. This is only to be
1682-
used during ALTER TABLE when the table is empty, before
1683-
invoking dict_index_t::clear_instant_alter().
1684-
@param[in,out] trx dictionary transaction
1685-
@return error code */
1686-
inline dberr_t reassign_id(trx_t* trx);
1687-
16881679
/** Add the table definition to the data dictionary cache */
16891680
void add_to_cache();
16901681

storage/innobase/row/row0mysql.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2847,6 +2847,10 @@ row_mysql_table_id_reassign(
28472847
pars_info_add_ull_literal(info, "old_id", table->id);
28482848
pars_info_add_ull_literal(info, "new_id", *new_id);
28492849

2850+
/* Note: This cannot be rolled back. Rollback would see the
2851+
UPDATE SYS_INDEXES as two operations: DELETE and INSERT.
2852+
It would invoke btr_free_if_exists() when rolling back the
2853+
INSERT, effectively dropping all indexes of the table. */
28502854
err = que_eval_sql(
28512855
info,
28522856
"PROCEDURE RENUMBER_TABLE_PROC () IS\n"
@@ -3135,6 +3139,12 @@ row_discard_tablespace_for_mysql(
31353139
err = row_discard_tablespace_foreign_key_checks(trx, table);
31363140

31373141
if (err == DB_SUCCESS) {
3142+
/* Note: This cannot be rolled back.
3143+
Rollback would see the UPDATE SYS_INDEXES
3144+
as two operations: DELETE and INSERT.
3145+
It would invoke btr_free_if_exists()
3146+
when rolling back the INSERT, effectively
3147+
dropping all indexes of the table. */
31383148
err = row_discard_tablespace(trx, table);
31393149
}
31403150
}

storage/innobase/trx/trx0rec.cc

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,7 +1548,6 @@ trx_undo_update_rec_get_update(
15481548
upd_t* update;
15491549
ulint n_fields;
15501550
byte* buf;
1551-
ulint i;
15521551
bool first_v_col = true;
15531552
bool is_undo_log = true;
15541553
ulint n_skip_field = 0;
@@ -1561,7 +1560,7 @@ trx_undo_update_rec_get_update(
15611560
n_fields = 0;
15621561
}
15631562

1564-
update = upd_create(n_fields + 2, heap);
1563+
*upd = update = upd_create(n_fields + 2, heap);
15651564

15661565
update->info_bits = info_bits;
15671566

@@ -1587,8 +1586,7 @@ trx_undo_update_rec_get_update(
15871586

15881587
/* Store then the updated ordinary columns to the update vector */
15891588

1590-
for (i = 0; i < n_fields; i++) {
1591-
1589+
for (ulint i = 0; i < n_fields; i++) {
15921590
const byte* field;
15931591
ulint len;
15941592
ulint orig_len;
@@ -1621,23 +1619,53 @@ trx_undo_update_rec_get_update(
16211619
upd_field_set_v_field_no(upd_field, field_no, index);
16221620
} else if (UNIV_UNLIKELY((update->info_bits
16231621
& ~REC_INFO_DELETED_FLAG)
1624-
== REC_INFO_MIN_REC_FLAG)
1625-
&& index->is_instant()) {
1622+
== REC_INFO_MIN_REC_FLAG)) {
1623+
ut_ad(type == TRX_UNDO_UPD_EXIST_REC);
16261624
const ulint uf = index->first_user_field();
16271625
ut_ad(field_no >= uf);
16281626

16291627
if (update->info_bits != REC_INFO_MIN_REC_FLAG) {
1628+
/* Generic instant ALTER TABLE */
16301629
if (field_no == uf) {
16311630
upd_field->new_val.type
16321631
.metadata_blob_init();
1632+
} else if (field_no >= index->n_fields) {
1633+
/* This is reachable during
1634+
purge if the table was emptied
1635+
and converted to the canonical
1636+
format on a later ALTER TABLE.
1637+
In this case,
1638+
row_purge_upd_exist_or_extern()
1639+
would only be interested in
1640+
freeing any BLOBs that were
1641+
updated, that is, the metadata
1642+
BLOB above. Other BLOBs in
1643+
the metadata record are never
1644+
updated; they are for the
1645+
initial DEFAULT values of the
1646+
instantly added columns, and
1647+
they will never change.
1648+
1649+
Note: if the table becomes
1650+
empty during ROLLBACK or is
1651+
empty during subsequent ALTER
1652+
TABLE, and btr_page_empty() is
1653+
called to re-create the root
1654+
page without the metadata
1655+
record, in that case we should
1656+
only free the latest version
1657+
of BLOBs in the record,
1658+
which purge would never touch. */
1659+
field_no = REC_MAX_N_FIELDS;
1660+
n_skip_field++;
16331661
} else {
1634-
ut_ad(field_no > uf);
16351662
dict_col_copy_type(
16361663
dict_index_get_nth_col(
16371664
index, field_no - 1),
16381665
&upd_field->new_val.type);
16391666
}
16401667
} else {
1668+
/* Instant ADD COLUMN...LAST */
16411669
dict_col_copy_type(
16421670
dict_index_get_nth_col(index,
16431671
field_no),
@@ -1701,31 +1729,23 @@ trx_undo_update_rec_get_update(
17011729
}
17021730
}
17031731

1704-
/* In rare scenario, we could have skipped virtual column (as they
1705-
are dropped. We will regenerate a update vector and skip them */
1706-
if (n_skip_field > 0) {
1707-
ulint n = 0;
1708-
ut_ad(n_skip_field <= n_fields);
1732+
/* We may have to skip dropped indexed virtual columns.
1733+
Also, we may have to trim the update vector of a metadata record
1734+
if dict_index_t::clear_instant_alter() was invoked on the table
1735+
later, and the number of fields no longer matches. */
17091736

1710-
upd_t* new_update = upd_create(
1711-
n_fields + 2 - n_skip_field, heap);
1737+
if (n_skip_field) {
1738+
upd_field_t* d = upd_get_nth_field(update, 0);
1739+
const upd_field_t* const end = d + n_fields + 2;
17121740

1713-
for (i = 0; i < n_fields + 2; i++) {
1714-
upd_field = upd_get_nth_field(update, i);
1715-
1716-
if (upd_field->field_no == REC_MAX_N_FIELDS) {
1717-
continue;
1741+
for (const upd_field_t* s = d; s != end; s++) {
1742+
if (s->field_no != REC_MAX_N_FIELDS) {
1743+
*d++ = *s;
17181744
}
1719-
1720-
upd_field_t* new_upd_field
1721-
= upd_get_nth_field(new_update, n);
1722-
*new_upd_field = *upd_field;
1723-
n++;
17241745
}
1725-
ut_ad(n == n_fields + 2 - n_skip_field);
1726-
*upd = new_update;
1727-
} else {
1728-
*upd = update;
1746+
1747+
ut_ad(d + n_skip_field == end);
1748+
update->n_fields = d - upd_get_nth_field(update, 0);
17291749
}
17301750

17311751
return(const_cast<byte*>(ptr));

0 commit comments

Comments
 (0)