Skip to content

Commit fe0e263

Browse files
committed
MDEV-15370 Upgrade fails when both insert_undo and update_undo exist
Before MDEV-12288 in MariaDB 10.3.1, InnoDB used to partition the persistent transaction undo log into insert_undo and update_undo. MDEV-12288 repurposes the update_undo as the single undo log. In order to support an upgrade from earlier MariaDB versions, the insert_undo is recovered in data structures, called old_insert. An assertion failure occurred in TrxUndoRsegsIterator::set_next() when an incomplete transaction was recovered with both insert_undo and update_undo log. This could be easily demonstrated by starting ./mysql-test-run --manual-gdb innodb.read_only_recovery in MariaDB 10.2, and after the first kill, start up the MariaDB 10.3 server with the same parameters. The problem is that MariaDB 10.3 would roll back the recovered transaction, and finally "commit" it twice (with all changes to data rolled back), both insert_undo and update_undo with the same commit end identifier (trx->no). Our fix is to introduce a "commit number" that comprises two components: (trx->no << 1 | !old_insert). In this way, the assertion in the purge subsystem can be relaxed so that only the trx->no component must match.
1 parent 6a370e4 commit fe0e263

File tree

6 files changed

+47
-36
lines changed

6 files changed

+47
-36
lines changed

storage/innobase/include/trx0purge.h

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,16 @@ class TrxUndoRsegs {
103103
TrxUndoRsegs() {}
104104
/** Constructor */
105105
TrxUndoRsegs(trx_rseg_t& rseg)
106-
: m_trx_no(rseg.last_trx_no), m_rsegs(1, &rseg) {}
106+
: m_commit(rseg.last_commit), m_rsegs(1, &rseg) {}
107107
/** Constructor */
108108
TrxUndoRsegs(trx_id_t trx_no, trx_rseg_t& rseg)
109-
: m_trx_no(trx_no), m_rsegs(1, &rseg) {}
109+
: m_commit(trx_no << 1), m_rsegs(1, &rseg) {}
110110

111-
/** Get the commit number */
112-
trx_id_t get_trx_no() const { return m_trx_no; }
111+
/** @return the transaction commit identifier */
112+
trx_id_t trx_no() const { return m_commit >> 1; }
113113

114+
bool operator!=(const TrxUndoRsegs& other) const
115+
{ return m_commit != other.m_commit; }
114116
bool empty() const { return m_rsegs.empty(); }
115117
void erase(iterator& it) { m_rsegs.erase(it); }
116118
iterator begin() { return(m_rsegs.begin()); }
@@ -124,13 +126,12 @@ class TrxUndoRsegs {
124126
@return true if elem1 > elem2 else false.*/
125127
bool operator()(const TrxUndoRsegs& lhs, const TrxUndoRsegs& rhs)
126128
{
127-
return(lhs.m_trx_no > rhs.m_trx_no);
129+
return(lhs.m_commit > rhs.m_commit);
128130
}
129131

130132
private:
131-
/** Copy trx_rseg_t::last_trx_no */
132-
trx_id_tm_trx_no;
133-
133+
/** Copy trx_rseg_t::last_commit */
134+
trx_id_tm_commit;
134135
/** Rollback segments of a transaction, scheduled for purge. */
135136
trx_rsegs_tm_rsegs;
136137
};
@@ -438,13 +439,17 @@ class purge_sys_t
438439
{
439440
bool operator<=(const iterator& other) const
440441
{
441-
if (trx_no < other.trx_no) return true;
442-
if (trx_no > other.trx_no) return false;
442+
if (commit < other.commit) return true;
443+
if (commit > other.commit) return false;
443444
return undo_no <= other.undo_no;
444445
}
445446

446-
/** The trx_t::no of the committed transaction */
447-
trx_id_ttrx_no;
447+
/** @return the commit number of the transaction */
448+
trx_id_t trx_no() const { return commit >> 1; }
449+
void reset_trx_no(trx_id_t trx_no) { commit = trx_no << 1; }
450+
451+
/** 2 * trx_t::no + old_insert of the committed transaction */
452+
trx_id_tcommit;
448453
/** The record number within the committed transaction's undo
449454
log, increasing, purged from from 0 onwards */
450455
undo_no_tundo_no;

storage/innobase/include/trx0rseg.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ struct trx_rseg_t {
164164
/** Byte offset of the last not yet purged log header */
165165
ulintlast_offset;
166166

167-
/** Transaction number of the last not yet purged log */
168-
trx_id_tlast_trx_no;
167+
/** trx_t::no * 2 + old_insert of the last not yet purged log */
168+
trx_id_tlast_commit;
169169

170170
/** Whether the log segment needs purge */
171171
boolneeds_purge;
@@ -177,6 +177,14 @@ struct trx_rseg_t {
177177
UNDO-tablespace marked for truncate. */
178178
boolskip_allocation;
179179

180+
/** @return the commit ID of the last committed transaction */
181+
trx_id_t last_trx_no() const { return last_commit >> 1; }
182+
183+
void set_last_trx_no(trx_id_t trx_no, bool is_update)
184+
{
185+
last_commit = trx_no << 1 | trx_id_t(is_update);
186+
}
187+
180188
/** @return whether the rollback segment is persistent */
181189
bool is_persistent() const
182190
{

storage/innobase/lock/lock0lock.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5258,7 +5258,7 @@ lock_print_info_summary(
52585258
fprintf(file,
52595259
"Purge done for trx's n:o < " TRX_ID_FMT
52605260
" undo n:o < " TRX_ID_FMT " state: ",
5261-
purge_sys->tail.trx_no,
5261+
purge_sys->tail.trx_no(),
52625262
purge_sys->tail.undo_no);
52635263

52645264
/* Note: We are reading the state without the latch. One because it

storage/innobase/trx/trx0purge.cc

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,17 @@ inline bool TrxUndoRsegsIterator::set_next()
8585
/* Check if there are more rsegs to process in the
8686
current element. */
8787
if (m_iter != m_rsegs.end()) {
88-
8988
/* We are still processing rollback segment from
9089
the same transaction and so expected transaction
9190
number shouldn't increase. Undo the increment of
92-
expected trx_no done by caller assuming rollback
91+
expected commit done by caller assuming rollback
9392
segments from given transaction are done. */
94-
purge_sys->tail.trx_no = (*m_iter)->last_trx_no;
93+
purge_sys->tail.commit = (*m_iter)->last_commit;
9594
} else if (!purge_sys->purge_queue.empty()) {
9695
m_rsegs = purge_sys->purge_queue.top();
9796
purge_sys->purge_queue.pop();
9897
ut_ad(purge_sys->purge_queue.empty()
99-
|| purge_sys->purge_queue.top().get_trx_no()
100-
!= m_rsegs.get_trx_no());
98+
|| purge_sys->purge_queue.top() != m_rsegs);
10199
m_iter = m_rsegs.begin();
102100
} else {
103101
/* Queue is empty, reset iterator. */
@@ -113,16 +111,16 @@ inline bool TrxUndoRsegsIterator::set_next()
113111
mutex_enter(&purge_sys->rseg->mutex);
114112

115113
ut_a(purge_sys->rseg->last_page_no != FIL_NULL);
116-
ut_ad(purge_sys->rseg->last_trx_no == m_rsegs.get_trx_no());
114+
ut_ad(purge_sys->rseg->last_trx_no() == m_rsegs.trx_no());
117115

118116
/* We assume in purge of externally stored fields that space id is
119117
in the range of UNDO tablespace space ids */
120118
ut_a(purge_sys->rseg->space == TRX_SYS_SPACE
121119
|| srv_is_undo_tablespace(purge_sys->rseg->space));
122120

123-
ut_a(purge_sys->tail.trx_no <= purge_sys->rseg->last_trx_no);
121+
ut_a(purge_sys->tail.commit <= purge_sys->rseg->last_commit);
124122

125-
purge_sys->tail.trx_no = purge_sys->rseg->last_trx_no;
123+
purge_sys->tail.commit = purge_sys->rseg->last_commit;
126124
purge_sys->hdr_offset = purge_sys->rseg->last_offset;
127125
purge_sys->hdr_page_no = purge_sys->rseg->last_page_no;
128126

@@ -306,7 +304,7 @@ trx_purge_add_undo_to_history(const trx_t* trx, trx_undo_t*& undo, mtr_t* mtr)
306304
if (rseg->last_page_no == FIL_NULL) {
307305
rseg->last_page_no = undo->hdr_page_no;
308306
rseg->last_offset = undo->hdr_offset;
309-
rseg->last_trx_no = trx->no;
307+
rseg->set_last_trx_no(trx->no, undo == trx->rsegs.m_redo.undo);
310308
rseg->needs_purge = true;
311309
}
312310

@@ -462,8 +460,8 @@ trx_purge_truncate_rseg_history(
462460

463461
undo_trx_no = mach_read_from_8(log_hdr + TRX_UNDO_TRX_NO);
464462

465-
if (undo_trx_no >= limit.trx_no) {
466-
if (undo_trx_no == limit.trx_no) {
463+
if (undo_trx_no >= limit.trx_no()) {
464+
if (undo_trx_no == limit.trx_no()) {
467465
trx_undo_truncate_start(
468466
&rseg, hdr_addr.page,
469467
hdr_addr.boffset, limit.undo_no);
@@ -933,7 +931,7 @@ trx_purge_initiate_truncate(
933931
undo != NULL && all_free;
934932
undo = UT_LIST_GET_NEXT(undo_list, undo)) {
935933

936-
if (limit.trx_no < undo->trx_id) {
934+
if (limit.trx_no() < undo->trx_id) {
937935
all_free = false;
938936
} else {
939937
cached_undo_size += undo->size;
@@ -1040,12 +1038,12 @@ function is called, the caller must not have any latches on undo log pages!
10401038
static void trx_purge_truncate_history()
10411039
{
10421040
ut_ad(purge_sys->head <= purge_sys->tail);
1043-
purge_sys_t::iterator& head = purge_sys->head.trx_no
1041+
purge_sys_t::iterator& head = purge_sys->head.commit
10441042
? purge_sys->head : purge_sys->tail;
10451043

1046-
if (head.trx_no >= purge_sys->view.low_limit_no()) {
1044+
if (head.trx_no() >= purge_sys->view.low_limit_no()) {
10471045
/* This is sometimes necessary. TODO: find out why. */
1048-
head.trx_no = purge_sys->view.low_limit_no();
1046+
head.reset_trx_no(purge_sys->view.low_limit_no());
10491047
head.undo_no = 0;
10501048
}
10511049

@@ -1086,7 +1084,7 @@ trx_purge_rseg_get_next_history_log(
10861084

10871085
ut_a(rseg->last_page_no != FIL_NULL);
10881086

1089-
purge_sys->tail.trx_no = rseg->last_trx_no + 1;
1087+
purge_sys->tail.commit = rseg->last_commit + 1;
10901088
purge_sys->tail.undo_no = 0;
10911089
purge_sys->next_stored = false;
10921090

@@ -1136,7 +1134,7 @@ trx_purge_rseg_get_next_history_log(
11361134

11371135
rseg->last_page_no = prev_log_addr.page;
11381136
rseg->last_offset = prev_log_addr.boffset;
1139-
rseg->last_trx_no = trx_no;
1137+
rseg->set_last_trx_no(trx_no, purge != 0);
11401138
rseg->needs_purge = purge != 0;
11411139

11421140
/* Purge can also produce events, however these are already ordered
@@ -1235,7 +1233,7 @@ trx_purge_get_next_rec(
12351233
mtr_tmtr;
12361234

12371235
ut_ad(purge_sys->next_stored);
1238-
ut_ad(purge_sys->tail.trx_no < purge_sys->view.low_limit_no());
1236+
ut_ad(purge_sys->tail.trx_no() < purge_sys->view.low_limit_no());
12391237

12401238
space = purge_sys->rseg->space;
12411239
page_no = purge_sys->page_no;
@@ -1330,7 +1328,7 @@ trx_purge_fetch_next_rec(
13301328
}
13311329
}
13321330

1333-
if (purge_sys->tail.trx_no >= purge_sys->view.low_limit_no()) {
1331+
if (purge_sys->tail.trx_no() >= purge_sys->view.low_limit_no()) {
13341332

13351333
return(NULL);
13361334
}

storage/innobase/trx/trx0rseg.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,13 +470,13 @@ trx_rseg_mem_restore(
470470
max_trx_id = id;
471471
}
472472
id = mach_read_from_8(undo_log_hdr + TRX_UNDO_TRX_NO);
473-
rseg->last_trx_no = id;
474473
if (id > max_trx_id) {
475474
max_trx_id = id;
476475
}
477476
unsigned purge = mach_read_from_2(
478477
undo_log_hdr + TRX_UNDO_NEEDS_PURGE);
479478
ut_ad(purge <= 1);
479+
rseg->set_last_trx_no(id, purge != 0);
480480
rseg->needs_purge = purge != 0;
481481

482482
if (rseg->last_page_no != FIL_NULL) {

storage/innobase/trx/trx0undo.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1761,7 +1761,7 @@ trx_undo_truncate_tablespace(
17611761
rseg->trx_ref_count = 0;
17621762
rseg->last_page_no = FIL_NULL;
17631763
rseg->last_offset = 0;
1764-
rseg->last_trx_no = 0;
1764+
rseg->last_commit = 0;
17651765
rseg->needs_purge = false;
17661766
}
17671767
mtr_commit(&mtr);

0 commit comments

Comments
 (0)