diff options
author | Marko Mäkelä <marko.makela@mariadb.com> | 2023-03-22 14:31:00 +0200 |
---|---|---|
committer | Marko Mäkelä <marko.makela@mariadb.com> | 2023-03-22 14:31:00 +0200 |
commit | ff3d4395d808b6421d2e0714e10d48c7aa2f3c3a (patch) | |
tree | 123bf2a8a509716d707c376c2b28b326716f7d5b | |
parent | 7c91082e393f1817401b334b3d3584b401d909d7 (diff) | |
download | mariadb-git-ff3d4395d808b6421d2e0714e10d48c7aa2f3c3a.tar.gz |
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.
-rw-r--r-- | storage/innobase/btr/btr0cur.cc | 69 | ||||
-rw-r--r-- | storage/innobase/page/page0zip.cc | 3 | ||||
-rw-r--r-- | storage/innobase/row/row0upd.cc | 37 |
3 files changed, 92 insertions, 17 deletions
diff --git a/storage/innobase/btr/btr0cur.cc b/storage/innobase/btr/btr0cur.cc index 3164352892e..d4e1497d9b3 100644 --- a/storage/innobase/btr/btr0cur.cc +++ b/storage/innobase/btr/btr0cur.cc @@ -4210,6 +4210,52 @@ out_of_space: 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. We assume here that the ordering fields of the record do not change. @@ -4271,17 +4317,10 @@ btr_cur_update_in_place( page_zip = buf_block_get_page_zip(block); /* Check that enough space is available on the compressed page. */ - if (page_zip) { - ut_ad(!index->table->is_temporary()); - - if (!btr_cur_update_alloc_zip( - 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); + if (UNIV_LIKELY_NULL(page_zip) + && !(rec = btr_cur_update_in_place_zip_check( + cursor, offsets, *update, mtr))) { + return DB_ZIP_OVERFLOW; } /* Do lock checking and undo logging */ @@ -5034,7 +5073,13 @@ btr_cur_pessimistic_update( ut_ad(page_is_leaf(page)); 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 */ diff --git a/storage/innobase/page/page0zip.cc b/storage/innobase/page/page0zip.cc index 13c6f58a171..742c91606b2 100644 --- a/storage/innobase/page/page0zip.cc +++ b/storage/innobase/page/page0zip.cc @@ -4132,9 +4132,6 @@ page_zip_write_trx_id_and_roll_ptr( ut_ad(field + DATA_TRX_ID_LEN == rec_get_nth_field(rec, offsets, trx_id_col + 1, &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); mach_write_to_6(field, trx_id); compile_time_assert(DATA_ROLL_PTR_LEN == 7); diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index c7087559f47..0ea7aea7fb1 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -692,9 +692,42 @@ row_upd_rec_in_place( dfield_get_len(new_val)); } - if (page_zip) { - page_zip_write_rec(page_zip, rec, index, offsets, 0); + if (UNIV_LIKELY(!page_zip)) { + 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); } /*********************************************************************//** |