summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarko Mäkelä <marko.makela@mariadb.com>2023-03-22 14:31:00 +0200
committerMarko Mäkelä <marko.makela@mariadb.com>2023-03-22 14:31:00 +0200
commitff3d4395d808b6421d2e0714e10d48c7aa2f3c3a (patch)
tree123bf2a8a509716d707c376c2b28b326716f7d5b
parent7c91082e393f1817401b334b3d3584b401d909d7 (diff)
downloadmariadb-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.cc69
-rw-r--r--storage/innobase/page/page0zip.cc3
-rw-r--r--storage/innobase/row/row0upd.cc37
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);
}
/*********************************************************************//**