Skip to content

Commit 3eba104

Browse files
gurusamidahlerlend
authored andcommitted
Bug #30417719 ASSERTION FAILURE: FSP0FSP.CC:2371:N_USED_NOT_FULL > 0 || N_TOTAL_NOT_FULL == 0
Problem: The original fix for this bug at rb#23349 has an issue. When the persistent cursor is stored and restored, the position of the record could change. Also, when the purge thread does an intermediate commit, it is possible that the delete marked record is re-used. Solution: After persistent cursor is restored, reset the LOB reference to the correct value. Also, the callers need to be aware that the cursor position could be changed. Check if the clust_rec is re-used by checking the delete mark flag. rb#23459 approved by Kuba <[email protected]>
1 parent f5608e3 commit 3eba104

File tree

6 files changed

+94
-73
lines changed

6 files changed

+94
-73
lines changed

storage/innobase/btr/btr0cur.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4720,10 +4720,32 @@ ibool btr_cur_pessimistic_delete(dberr_t *err, ibool has_reserved_extents,
47204720
if (rec_offs_any_extern(offsets)) {
47214721
lob::BtrContext btr_ctx(mtr, pcur, index, rec, offsets, block);
47224722

4723+
/* The following call will restart the btr_mtr, which could change the
4724+
cursor position. */
47234725
btr_ctx.free_externally_stored_fields(trx_id, undo_no, rollback, rec_type);
47244726
#ifdef UNIV_ZIP_DEBUG
47254727
ut_a(!page_zip || page_zip_validate(page_zip, page, index));
47264728
#endif /* UNIV_ZIP_DEBUG */
4729+
4730+
/* The cursor position could have changed now. */
4731+
if (pcur != nullptr) {
4732+
cursor = pcur->get_btr_cur();
4733+
block = btr_cur_get_block(cursor);
4734+
page = buf_block_get_frame(block);
4735+
rec = btr_cur_get_rec(cursor);
4736+
rec_offs_make_valid(rec, index, offsets);
4737+
}
4738+
4739+
/* While purging LOBs, there are intermediate mtr commits which releases
4740+
the latches. In that duration, it is possible that the clust_rec is reused.
4741+
In such situation, don't proceed further. */
4742+
if (!rollback && rec_type != 0 &&
4743+
!rec_get_deleted_flag(rec, rec_offs_comp(offsets))) {
4744+
/* This is the purge thread. For the purge thread, rec_type will have a
4745+
* valid value. */
4746+
ret = TRUE;
4747+
goto return_after_reservations;
4748+
}
47274749
}
47284750

47294751
if (UNIV_UNLIKELY(page_get_n_recs(page) < 2) &&

storage/innobase/include/lob0inf.h

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*****************************************************************************
22
3-
Copyright (c) 2016, 2018, Oracle and/or its affiliates. All Rights Reserved.
3+
Copyright (c) 2016, 2019, Oracle and/or its affiliates. All Rights Reserved.
44
55
This program is free software; you can redistribute it and/or modify it under
66
the terms of the GNU General Public License, version 2.0, as published by the
@@ -78,19 +78,6 @@ ulint read(ReadContext *ctx, ref_t ref, ulint offset, ulint len, byte *buf);
7878
ulint z_read(lob::ReadContext *ctx, lob::ref_t ref, ulint offset, ulint len,
7979
byte *buf);
8080

81-
/** Purge an LOB (either of compressed or uncompressed).
82-
@param[in] ctx the delete operation context information.
83-
@param[in] index clustered index in which LOB is present
84-
@param[in] trxid the transaction that is being purged.
85-
@param[in] undo_no during rollback to savepoint, purge only upto
86-
this undo number.
87-
@param[in] ref reference to LOB that is purged.
88-
@param[in] rec_type undo record type.
89-
@param[in] uf update done on the field. */
90-
void purge(lob::DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
91-
undo_no_t undo_no, lob::ref_t ref, ulint rec_type,
92-
const upd_field_t *uf);
93-
9481
/** Print information about the given LOB.
9582
@param[in] trx the current transaction.
9683
@param[in] index the clust index that contains the LOB.

storage/innobase/include/lob0lob.h

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,11 +1350,6 @@ struct Reader {
13501350
/** The context information when the delete operation on LOB is
13511351
taking place. */
13521352
struct DeleteContext : public BtrContext {
1353-
DeleteContext(byte *field_ref)
1354-
: m_blobref(field_ref),
1355-
m_page_size(table() == nullptr ? get_page_size()
1356-
: dict_table_page_size(table())) {}
1357-
13581353
/** Constructor. */
13591354
DeleteContext(const BtrContext &btr, byte *field_ref, ulint field_no,
13601355
bool rollback)
@@ -1363,7 +1358,13 @@ struct DeleteContext : public BtrContext {
13631358
m_field_no(field_no),
13641359
m_rollback(rollback),
13651360
m_page_size(table() == nullptr ? get_page_size()
1366-
: dict_table_page_size(table())) {}
1361+
: dict_table_page_size(table())) {
1362+
m_blobref.parse(m_blobref_mem);
1363+
}
1364+
1365+
bool is_ref_valid() const {
1366+
return (m_blobref_mem.m_page_no == m_blobref.page_no());
1367+
}
13671368

13681369
/** Determine if it is compressed page format.
13691370
@return true if compressed. */
@@ -1377,6 +1378,14 @@ struct DeleteContext : public BtrContext {
13771378
return (DICT_TF_HAS_ATOMIC_BLOBS(flags));
13781379
}
13791380

1381+
bool is_delete_marked() const {
1382+
rec_t *clust_rec = rec();
1383+
if (clust_rec == nullptr) {
1384+
return (true);
1385+
}
1386+
return (rec_get_deleted_flag(clust_rec, page_rec_is_comp(clust_rec)));
1387+
}
1388+
13801389
#ifdef UNIV_DEBUG
13811390
/** Validate the LOB reference object.
13821391
@return true if valid, false otherwise. */
@@ -1390,7 +1399,6 @@ struct DeleteContext : public BtrContext {
13901399
}
13911400
return (true);
13921401
}
1393-
13941402
#endif /* UNIV_DEBUG */
13951403

13961404
/** Acquire an x-latch on the index page containing the clustered
@@ -1410,6 +1418,9 @@ struct DeleteContext : public BtrContext {
14101418
page_size_t m_page_size;
14111419

14121420
private:
1421+
/** Memory copy of the original LOB reference. */
1422+
ref_mem_t m_blobref_mem;
1423+
14131424
/** Obtain the page size from the tablespace flags.
14141425
@return the page size. */
14151426
page_size_t get_page_size() const {
@@ -1523,11 +1534,10 @@ ulint btr_rec_get_externally_stored_len(const rec_t *rec, const ulint *offsets);
15231534
@param[in] trxid the transaction that is being purged.
15241535
@param[in] undo_no during rollback to savepoint, purge only upto
15251536
this undo number.
1526-
@param[in] ref reference to LOB that is purged.
1527-
@param[in] rec_type undo record type.*/
1537+
@param[in] rec_type undo record type.
1538+
@param[in] uf the update vector for the field. */
15281539
void purge(lob::DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
1529-
undo_no_t undo_no, lob::ref_t ref, ulint rec_type,
1530-
const upd_field_t *uf);
1540+
undo_no_t undo_no, ulint rec_type, const upd_field_t *uf);
15311541

15321542
/** Update a portion of the given LOB.
15331543
@param[in] ctx update operation context information.

storage/innobase/lob/lob0lob.cc

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ dberr_t zReader::fetch() {
244244
if (m_rctx.m_page_no == FIL_NULL) {
245245
goto end_of_blob;
246246
}
247-
/* fall through */
247+
/* fall through */
248248
default:
249249
err = DB_FAIL;
250250
ib::error(ER_IB_MSG_630)
@@ -1060,9 +1060,7 @@ void BtrContext::free_updated_extern_fields(trx_id_t trx_id, undo_no_t undo_no,
10601060
byte *field_ref = data + len - BTR_EXTERN_FIELD_REF_SIZE;
10611061

10621062
DeleteContext ctx(*this, field_ref, ufield->field_no, rollback);
1063-
1064-
ref_t lobref(field_ref);
1065-
lob::purge(&ctx, m_index, trx_id, undo_no, lobref, 0, ufield);
1063+
lob::purge(&ctx, m_index, trx_id, undo_no, 0, ufield);
10661064
}
10671065
}
10681066
}
@@ -1160,10 +1158,9 @@ void BtrContext::free_externally_stored_fields(trx_id_t trx_id,
11601158
byte *field_ref = btr_rec_get_field_ref(m_rec, m_offsets, i);
11611159

11621160
DeleteContext ctx(*this, field_ref, i, rollback);
1163-
ref_t lobref(field_ref);
11641161

11651162
upd_field_t *uf = nullptr;
1166-
lob::purge(&ctx, m_index, trx_id, undo_no, lobref, rec_type, uf);
1163+
lob::purge(&ctx, m_index, trx_id, undo_no, rec_type, uf);
11671164
}
11681165
}
11691166
}

storage/innobase/lob/lob0purge.cc

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,14 @@ namespace lob {
4141
/** Rollback from undo log information.
4242
@param[in] ctx the delete operation context.
4343
@param[in] index the clustered index to which LOB belongs.
44-
@param[in] ref the LOB reference object.
4544
@param[in] uf the update vector of concerned field. */
4645
static void rollback_from_undolog(DeleteContext *ctx, dict_index_t *index,
47-
ref_t &ref, const upd_field_t *uf) {
46+
const upd_field_t *uf) {
4847
DBUG_TRACE;
4948

5049
trx_t *trx = nullptr;
5150

52-
dberr_t err = apply_undolog(ctx->get_mtr(), trx, index, ref, uf);
51+
dberr_t err = apply_undolog(ctx->get_mtr(), trx, index, ctx->m_blobref, uf);
5352
ut_a(err == DB_SUCCESS);
5453
}
5554

@@ -59,21 +58,21 @@ static void rollback_from_undolog(DeleteContext *ctx, dict_index_t *index,
5958
@param[in] trxid the transaction that is being rolled back.
6059
@param[in] undo_no during rollback to savepoint, rollback only
6160
upto this undo number.
62-
@param[in] ref reference to LOB that is being rolled back.
6361
@param[in] rec_type undo record type.
6462
@param[in] uf update vector of the concerned field. */
6563
static void rollback(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
66-
undo_no_t undo_no, ref_t &ref, ulint rec_type,
67-
const upd_field_t *uf) {
64+
undo_no_t undo_no, ulint rec_type, const upd_field_t *uf) {
6865
DBUG_TRACE;
6966

67+
ref_t &ref = ctx->m_blobref;
68+
7069
ut_ad(ctx->m_rollback);
7170

7271
if (uf != nullptr && uf->lob_diffs != nullptr && uf->lob_diffs->size() > 0) {
7372
/* Undo log contains changes done to the LOB. This must have
7473
been a small change done to LOB. Apply the undo log on the
7574
LOB.*/
76-
rollback_from_undolog(ctx, index, ref, uf);
75+
rollback_from_undolog(ctx, index, uf);
7776
return;
7877
}
7978

@@ -181,12 +180,13 @@ static void rollback(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
181180
@param[in] trxid the transaction that is being rolled back.
182181
@param[in] undo_no during rollback to savepoint, rollback only
183182
upto this undo number.
184-
@param[in] ref reference to LOB that is purged.
185183
@param[in] rec_type undo record type. */
186184
static void z_rollback(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
187-
undo_no_t undo_no, ref_t &ref, ulint rec_type) {
185+
undo_no_t undo_no, ulint rec_type) {
188186
ut_ad(ctx->m_rollback);
189187

188+
ref_t &ref = ctx->m_blobref;
189+
190190
mtr_t local_mtr;
191191
mtr_start(&local_mtr);
192192

@@ -196,7 +196,7 @@ static void z_rollback(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
196196
data should not be read. OTOH we do not ref.set_page_no(FIL_NULL, 0)
197197
until we delete all the pages, so that the recovery can use the reference to
198198
find the remaining parts of the LOB. */
199-
ref.set_length(0, 0);
199+
ref.set_length(0, nullptr);
200200
ctx->zblob_write_blobref(ctx->m_field_no, &local_mtr);
201201

202202
page_no_t first_page_no = ref.page_no();
@@ -256,7 +256,7 @@ static void z_rollback(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
256256
ut_ad(ctx->get_page_zip() != nullptr);
257257
/* We are done with cleaning up index entries for the given version, so now we
258258
can modify the reference, so that it is no longer reachable. */
259-
ref.set_page_no(FIL_NULL, 0);
259+
ref.set_page_no(FIL_NULL, nullptr);
260260
ut_ad(ref.length() == 0);
261261
ctx->x_latch_rec_page(&local_mtr);
262262
ctx->zblob_write_blobref(ctx->m_field_no, &local_mtr);
@@ -272,14 +272,14 @@ static void z_rollback(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
272272
@param[in] trxid the transaction that is being purged.
273273
@param[in] undo_no during rollback to savepoint, purge only upto
274274
this undo number.
275-
@param[in] ref reference to LOB that is purged.
276275
@param[in] rec_type undo record type. */
277276
static void z_purge(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
278-
undo_no_t undo_no, ref_t &ref, ulint rec_type) {
277+
undo_no_t undo_no, ulint rec_type) {
279278
const bool is_rollback = ctx->m_rollback;
279+
ref_t &ref = ctx->m_blobref;
280280

281281
if (is_rollback) {
282-
z_rollback(ctx, index, trxid, undo_no, ref, rec_type);
282+
z_rollback(ctx, index, trxid, undo_no, rec_type);
283283
return;
284284
}
285285

@@ -366,21 +366,12 @@ static void z_purge(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
366366
}
367367
}
368368

369-
/** Purge an uncompressed LOB.
370-
@param[in] ctx the delete operation context information.
371-
@param[in] index clustered index in which LOB is present
372-
@param[in] trxid the transaction that is being purged.
373-
@param[in] undo_no during rollback to savepoint, purge only upto
374-
this undo number.
375-
@param[in] ref reference to LOB that is purged.
376-
@param[in] rec_type undo record type.
377-
@param[in] uf the update vector for the field. */
378369
void purge(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
379-
undo_no_t undo_no, ref_t ref, ulint rec_type,
380-
const upd_field_t *uf) {
370+
undo_no_t undo_no, ulint rec_type, const upd_field_t *uf) {
381371
DBUG_TRACE;
382372
mtr_t lob_mtr;
383373

374+
ref_t &ref = ctx->m_blobref;
384375
mtr_t *mtr = ctx->get_mtr();
385376
const mtr_log_t log_mode = mtr->get_log_mode();
386377
const bool is_rollback = ctx->m_rollback;
@@ -409,29 +400,46 @@ void purge(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
409400
return;
410401
}
411402

412-
/* Below we will restart the btr_mtr. Between the cursor store and restore,
413-
it is possible that the position of the record changes and hence the lob
414-
reference could become invalid. To avoid this take the latches before
415-
restarting the btr_mtr. */
416-
417-
mtr_start(&lob_mtr);
418-
mtr_sx_lock(dict_index_get_lock(index), &lob_mtr);
419-
403+
/* The purpose of the following block is to ensure that the parent mtr does
404+
not hold any redo log while creating child mtrs. */
420405
if (ctx->m_pcur != nullptr) {
406+
/* Below we will restart the btr_mtr. Between the cursor store and restore,
407+
it is possible that the position of the record changes and hence the lob
408+
reference could become invalid. Reset it to correct value. */
421409
ctx->restart_mtr();
410+
byte *field_ref = ctx->get_field_ref(ctx->m_field_no);
411+
ref.set_ref(field_ref);
422412
} else {
413+
/* Since pcur is not available, take latches to ensure that the record
414+
position does not change. We imitate the purge thread for the latches
415+
taken and the order in which they are taken. Kindly refer to the
416+
function row_purge_upd_exist_or_extern_func(). */
417+
mtr_start(&lob_mtr);
418+
mtr_sx_lock(dict_index_get_lock(index), &lob_mtr);
419+
btr_root_get(index, &lob_mtr);
423420
ctx->x_latch_rec_page(&lob_mtr);
424421
ctx->restart_mtr();
425422
mtr_sx_lock(dict_index_get_lock(index), mtr);
423+
btr_root_get(index, mtr);
426424
ctx->x_latch_rec_page(mtr);
425+
mtr_commit(&lob_mtr);
427426
}
428-
mtr_commit(&lob_mtr);
429427

430-
space_id_t space_id = ref.space_id();
428+
/* If rec_type is 0, it is not the purge operation. */
429+
if (!is_rollback && rec_type != 0 && !ctx->is_delete_marked()) {
430+
/* This is the purge operation. The delete marked clustered record has been
431+
reused. Purge shouldn't proceed. */
432+
return;
433+
}
434+
435+
ut_ad(ctx->is_ref_valid());
431436

437+
space_id_t space_id = ref.space_id();
438+
ut_ad(space_id == index->space_id());
432439
page_no_t first_page_no = ref.page_no();
433440
page_id_t page_id(space_id, first_page_no);
434441
page_size_t page_size(dict_table_page_size(index->table));
442+
435443
page_type_t page_type =
436444
first_page_t::get_page_type(index, page_id, page_size);
437445

@@ -445,14 +453,14 @@ void purge(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid,
445453
}
446454

447455
if (page_type == FIL_PAGE_TYPE_ZLOB_FIRST) {
448-
z_purge(ctx, index, trxid, undo_no, ref, rec_type);
456+
z_purge(ctx, index, trxid, undo_no, rec_type);
449457
return;
450458
}
451459

452460
ut_a(page_type == FIL_PAGE_TYPE_LOB_FIRST);
453461

454462
if (is_rollback) {
455-
rollback(ctx, index, trxid, undo_no, ref, rec_type, uf);
463+
rollback(ctx, index, trxid, undo_no, rec_type, uf);
456464
return;
457465
}
458466

storage/innobase/row/row0purge.cc

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -805,13 +805,10 @@ static void row_purge_upd_exist_or_extern_func(
805805

806806
lob::BtrContext btr_ctx(&mtr, NULL, index, NULL, NULL, block);
807807

808-
lob::DeleteContext ctx(btr_ctx, field_ref, 0, false);
809-
810-
lob::ref_t lobref(field_ref);
808+
lob::DeleteContext ctx(btr_ctx, field_ref, ufield->field_no, false);
811809

812810
lob::purge(&ctx, index, node->modifier_trx_id,
813-
trx_undo_rec_get_undo_no(undo_rec), lobref, node->rec_type,
814-
ufield);
811+
trx_undo_rec_get_undo_no(undo_rec), node->rec_type, ufield);
815812

816813
mtr_commit(&mtr);
817814
}

0 commit comments

Comments
 (0)