Bug #105592 | Lob's purge may suffer from nested mtr, which cause FSEG NOT FULL list broken. | ||
---|---|---|---|
Submitted: | 16 Nov 2021 11:39 | Modified: | 10 May 2022 14:32 |
Reporter: | biao li | Email Updates: | |
Status: | Closed | Impact on me: | |
Category: | MySQL Server: InnoDB storage engine | Severity: | S1 (Critical) |
Version: | MySQL 8.0.27, 8.0.28 | OS: | Linux |
Assigned to: | CPU Architecture: | x86 | |
Tags: | lob, purge, regression, rollback |
[16 Nov 2021 11:39]
biao li
[16 Nov 2021 11:39]
biao li
Before 8.0.26, purge also suffers from this nested mtr problem, however, the commit 5ba03cadf63b6c53a780ffd887d903eae6aac4ee(in 8.0.26) happens to fix the issue for purge, however, for the rollback case, there is still the problem. key code: if (rec_type == TRX_UNDO_DEL_MARK_REC || ok_to_free) { ut_ad(btr_first.get_page_type() == FIL_PAGE_TYPE_LOB_FIRST); if (dict_index_is_online_ddl(index)) { row_log_table_blob_free(index, ref.page_no()); } /* use the btr_mtr to free lob's first page, if we continue to free another lob field, nested mtr occurs.*/ if (purge_node == nullptr) { btr_first.destroy(); } else { /* In this case, the LOB is left with only the first page. Subsequently the LOB first page number in the LOB reference is set to FIL_NULL. This means that the LOB page is only accessible via an in-memory reference held in the purge node. If a crash happens after the btr_mtr commit and before freeing the LOB first page, then the LOB first page will be leaked. We need to come up with a mechanism to avoid this leak.*/ btr_first.make_empty(); purge_node->add_lob_page(index, page_id); } ref.set_page_no(FIL_NULL, mtr); ref.set_length(0, mtr); return; }
[16 Nov 2021 12:34]
MySQL Verification Team
Hello biao li, Thank you for the report and test case. Verified as described. regards, Umesh
[21 Jan 2022 3:52]
Rahul Sisondia
Since there are two columns involved, I think the analysis added by biao is simplified version of the following. Correct me if I am wrong. mtr1 start mtr2 start free page A and add A's extent to `FSEG_NOT_FULL`, and the FSEG_NOT_FULL list is: X(next: A, pre:null) -> A(next: null, pre: X) mtr2 commit free page B and add B's extent to `FSEG_NOT_FULL`, and the FSEG_NOT_FULL list is: X(next:A, prev:null) -> A(next:B,prev:X) -> B(next:null,prev:A) mtr3 start free page C and add C's extent to `FSEG_NOT_FULL`, and the FSEG_NOT_FULL list is: X(next:A, prev:null) -> A(next:B,prev:X) -> B(next:null,prev:A) -> C(next:null,prev:B) mtr3 commit free page D and add D's extent to `FSEG_NOT_FULL`, and the FSEG_NOT_FULL list is: X(next:A, prev:null) -> A(next:B,prev:X) -> B(next:null,prev:A) -> C(next:null,prev:B)->D(... mtr1 commit. >>>>Crashed replay mtr2 X(next: A, pre:null) -> A(next: null, pre: X) replay mtr3 X(next:A, prev:null) -> A(next:null, prev:X) -> C(next:null,prev:B) <---Broken mtr1 : btree mtr mtr2 : local mtr for column `b` in the table t1 mtr3 : local mtr for column `c` in the table t2
[21 Jan 2022 5:27]
Rahul Sisondia
I also think this might be related to https://bugs.mysql.com/bug.php?id=105176
[21 Jan 2022 7:05]
Rahul Sisondia
Just to complete the previous comment. And what would happen when the mtr1 will be replayed. replay mtr2 X(next: A, pre:null) -> A(next: null, pre: X) replay mtr3 X(next:A, prev:null) -> A(next:null, prev:X) -> C(next:null,prev:B) <---Broken replay mtr1 X(next:A, prev:null) ->A(next:B,prev:X) -> B(next:null,prev:A) -> C(next:null,prev:B) < --- Broken <not shown for D>
[25 Jan 2022 3:09]
Rahul Sisondia
Proposed patch : diff --git a/storage/innobase/lob/lob0purge.cc b/storage/innobase/lob/lob0purge.cc index 584fe362a18..8456eaf3438 100644 --- a/storage/innobase/lob/lob0purge.cc +++ b/storage/innobase/lob/lob0purge.cc @@ -509,7 +509,7 @@ void purge(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid, do not make conflicting modifications. */ ut_ad(!lob_mtr.conflicts_with(mtr)); mtr_commit(&lob_mtr); - first.set_mtr(ctx->get_mtr()); + mtr_start(&lob_mtr); first.load_x(page_id, page_size); bool ok_to_free = (rec_type == TRX_UNDO_UPD_EXIST_REC || @@ -525,9 +525,12 @@ void purge(DeleteContext *ctx, dict_index_t *index, trx_id_t trxid, } first.destroy(); } - - ref.set_page_no(FIL_NULL, mtr); - ref.set_length(0, mtr); + page_id = ctx->block()->get_page_id(); + ut_a(buf_page_get(page_id, page_size, RW_X_LATCH, &lob_mtr) == ctx->block()); + ref.set_page_no(FIL_NULL, &lob_mtr); + ref.set_length(0, &lob_mtr); + ut_ad(!lob_mtr.conflicts_with(mtr)); + mtr_commit(&lob_mtr); } } /* namespace lob */
[18 Feb 2022 6:52]
MySQL Verification Team
8.0.28 mtr test results
Attachment: 105592_8.0.28_debug.results (application/octet-stream, text), 100.87 KiB.
[22 Feb 2022 2:37]
Annamalai Gurusami
Posted by developer: Yes, I am able to reproduce the problem. I'll analysis the problem and will report back.
[10 May 2022 14:32]
Daniel Price
Posted by developer: Fixed as of the upcoming 8.0.30 release: Purging a record with multiple binary large object values raised an insertion failure due to a mini-transaction (mtr) conflict.