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

MDEV-30882 Crash on ROLLBACK in a ROW_FORMAT=COMPRESSED table

row_upd_rec_in_place(): Avoid calling page_zip_write_rec() if we
are not modifying any fields that are stored in compressed format.

btr_cur_update_in_place_zip_check(): New function to check if a
ROW_FORMAT=COMPRESSED record can actually be updated in place.

btr_cur_pessimistic_update(): If the BTR_KEEP_POS_FLAG is not set
(we are in a ROLLBACK and cannot write any BLOBs), ignore the potential
overflow and let page_zip_reorganize() or page_zip_compress() handle it.
This avoids a failure when an attempted UPDATE of an NULL column to 0 is
rolled back. During the ROLLBACK, we would try to move a non-updated
long column to off-page storage in order to avoid a compression failure
of the ROW_FORMAT=COMPRESSED page.

page_zip_write_trx_id_and_roll_ptr(): Remove an assertion that would fail
in row_upd_rec_in_place() because the uncompressed page would already
have been modified there.

Thanks to Jean-François Gagné for providing a copy of a page that
triggered these bugs on the ROLLBACK of UPDATE and DELETE.

A 10.6 version of this was tested by Matthias Leich using
cmake -DWITH_INNODB_EXTRA_DEBUG=ON a.k.a. UNIV_ZIP_DEBUG.
parent 7c91082e
...@@ -4210,6 +4210,52 @@ btr_cur_update_alloc_zip_func( ...@@ -4210,6 +4210,52 @@ btr_cur_update_alloc_zip_func(
return(false); return(false);
} }
/** Check if a ROW_FORMAT=COMPRESSED page can be updated in place
@param cur cursor pointing to ROW_FORMAT=COMPRESSED page
@param offsets rec_get_offsets(btr_cur_get_rec(cur))
@param update index fields being updated
@param mtr mini-transaction
@return the record in the ROW_FORMAT=COMPRESSED page
@retval nullptr if the page cannot be updated in place */
ATTRIBUTE_COLD static
rec_t *btr_cur_update_in_place_zip_check(btr_cur_t *cur, rec_offs *offsets,
const upd_t& update, mtr_t *mtr)
{
dict_index_t *index= cur->index;
ut_ad(!index->table->is_temporary());
switch (update.n_fields) {
case 0:
/* We are only changing the delete-mark flag. */
break;
case 1:
if (!index->is_clust() ||
update.fields[0].field_no != index->db_roll_ptr())
goto check_for_overflow;
/* We are only changing the delete-mark flag and DB_ROLL_PTR. */
break;
case 2:
if (!index->is_clust() ||
update.fields[0].field_no != index->db_trx_id() ||
update.fields[1].field_no != index->db_roll_ptr())
goto check_for_overflow;
/* We are only changing DB_TRX_ID, DB_ROLL_PTR, and the delete-mark.
They can be updated in place in the uncompressed part of the
ROW_FORMAT=COMPRESSED page. */
break;
check_for_overflow:
default:
if (!btr_cur_update_alloc_zip(btr_cur_get_page_zip(cur),
btr_cur_get_page_cur(cur),
index,
offsets, rec_offs_size(offsets),
false, mtr))
return nullptr;
}
return btr_cur_get_rec(cur);
}
/*************************************************************//** /*************************************************************//**
Updates a record when the update causes no size changes in its fields. Updates a record when the update causes no size changes in its fields.
We assume here that the ordering fields of the record do not change. We assume here that the ordering fields of the record do not change.
...@@ -4271,17 +4317,10 @@ btr_cur_update_in_place( ...@@ -4271,17 +4317,10 @@ btr_cur_update_in_place(
page_zip = buf_block_get_page_zip(block); page_zip = buf_block_get_page_zip(block);
/* Check that enough space is available on the compressed page. */ /* Check that enough space is available on the compressed page. */
if (page_zip) { if (UNIV_LIKELY_NULL(page_zip)
ut_ad(!index->table->is_temporary()); && !(rec = btr_cur_update_in_place_zip_check(
cursor, offsets, *update, mtr))) {
if (!btr_cur_update_alloc_zip( return DB_ZIP_OVERFLOW;
page_zip, btr_cur_get_page_cur(cursor),
index, offsets, rec_offs_size(offsets),
false, mtr)) {
return(DB_ZIP_OVERFLOW);
}
rec = btr_cur_get_rec(cursor);
} }
/* Do lock checking and undo logging */ /* Do lock checking and undo logging */
...@@ -5034,7 +5073,13 @@ btr_cur_pessimistic_update( ...@@ -5034,7 +5073,13 @@ btr_cur_pessimistic_update(
ut_ad(page_is_leaf(page)); ut_ad(page_is_leaf(page));
ut_ad(dict_index_is_clust(index)); ut_ad(dict_index_is_clust(index));
ut_ad(flags & BTR_KEEP_POS_FLAG); if (UNIV_UNLIKELY(!(flags & BTR_KEEP_POS_FLAG))) {
ut_ad(page_zip != NULL);
dtuple_convert_back_big_rec(index, new_entry,
big_rec_vec);
big_rec_vec = NULL;
n_ext = dtuple_get_n_ext(new_entry);
}
} }
/* Do lock checking and undo logging */ /* Do lock checking and undo logging */
......
...@@ -4132,9 +4132,6 @@ page_zip_write_trx_id_and_roll_ptr( ...@@ -4132,9 +4132,6 @@ page_zip_write_trx_id_and_roll_ptr(
ut_ad(field + DATA_TRX_ID_LEN ut_ad(field + DATA_TRX_ID_LEN
== rec_get_nth_field(rec, offsets, trx_id_col + 1, &len)); == rec_get_nth_field(rec, offsets, trx_id_col + 1, &len));
ut_ad(len == DATA_ROLL_PTR_LEN); ut_ad(len == DATA_ROLL_PTR_LEN);
#if defined UNIV_DEBUG || defined UNIV_ZIP_DEBUG
ut_a(!memcmp(storage, field, DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN));
#endif /* UNIV_DEBUG || UNIV_ZIP_DEBUG */
compile_time_assert(DATA_TRX_ID_LEN == 6); compile_time_assert(DATA_TRX_ID_LEN == 6);
mach_write_to_6(field, trx_id); mach_write_to_6(field, trx_id);
compile_time_assert(DATA_ROLL_PTR_LEN == 7); compile_time_assert(DATA_ROLL_PTR_LEN == 7);
......
...@@ -692,9 +692,42 @@ row_upd_rec_in_place( ...@@ -692,9 +692,42 @@ row_upd_rec_in_place(
dfield_get_len(new_val)); dfield_get_len(new_val));
} }
if (page_zip) { if (UNIV_LIKELY(!page_zip)) {
page_zip_write_rec(page_zip, rec, index, offsets, 0); return;
} }
switch (update->n_fields) {
case 0:
/* We only changed the delete-mark flag. */
update_del_mark:
page_zip_rec_set_deleted(page_zip, rec,
rec_get_deleted_flag(rec, true));
return;
case 1:
if (!index->is_clust()
|| update->fields[0].field_no != index->db_roll_ptr()) {
break;
}
goto update_sys;
case 2:
if (!index->is_clust()
|| update->fields[0].field_no != index->db_trx_id()
|| update->fields[1].field_no != index->db_roll_ptr()) {
break;
}
update_sys:
ulint len;
const byte* sys = rec_get_nth_field(rec, offsets,
index->db_trx_id(), &len);
ut_ad(len == DATA_TRX_ID_LEN);
page_zip_write_trx_id_and_roll_ptr(
page_zip, rec, offsets, index->db_trx_id(),
trx_read_trx_id(sys),
trx_read_roll_ptr(sys + DATA_TRX_ID_LEN));
goto update_del_mark;
}
page_zip_write_rec(page_zip, rec, index, offsets, 0);
} }
/*********************************************************************//** /*********************************************************************//**
......
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