1 /*****************************************************************************
2
3 Copyright (c) 1996, 2021, Oracle and/or its affiliates.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License, version 2.0,
7 as published by the Free Software Foundation.
8
9 This program is also distributed with certain software (including
10 but not limited to OpenSSL) that is licensed under separate terms,
11 as designated in a particular file or component or in included license
12 documentation. The authors of MySQL hereby grant you an additional
13 permission to link the program and your derivative works with the
14 separately licensed software that they have included with MySQL.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License, version 2.0, for more details.
20
21 You should have received a copy of the GNU General Public License along with
22 this program; if not, write to the Free Software Foundation, Inc.,
23 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
24
25 *****************************************************************************/
26
27 /**************************************************//**
28 @file trx/trx0rseg.cc
29 Rollback segment
30
31 Created 3/26/1996 Heikki Tuuri
32 *******************************************************/
33
34 #include "trx0rseg.h"
35
36 #ifdef UNIV_NONINL
37 #include "trx0rseg.ic"
38 #endif
39
40 #include "trx0undo.h"
41 #include "fut0lst.h"
42 #include "srv0srv.h"
43 #include "trx0purge.h"
44 #include "srv0mon.h"
45 #include "fsp0sysspace.h"
46
47 #include <algorithm>
48
49 /** Creates a rollback segment header.
50 This function is called only when a new rollback segment is created in
51 the database.
52 @param[in] space space id
53 @param[in] page_size page size
54 @param[in] max_size max size in pages
55 @param[in] rseg_slot_no rseg id == slot number in trx sys
56 @param[in,out] mtr mini-transaction
57 @return page number of the created segment, FIL_NULL if fail */
58 ulint
trx_rseg_header_create(ulint space,const page_size_t & page_size,ulint max_size,ulint rseg_slot_no,mtr_t * mtr)59 trx_rseg_header_create(
60 ulint space,
61 const page_size_t& page_size,
62 ulint max_size,
63 ulint rseg_slot_no,
64 mtr_t* mtr)
65 {
66 ulint page_no;
67 trx_rsegf_t* rsegf;
68 trx_sysf_t* sys_header;
69 ulint i;
70 buf_block_t* block;
71
72 ut_ad(mtr);
73 ut_ad(mtr_memo_contains(mtr, fil_space_get_latch(space, NULL),
74 MTR_MEMO_X_LOCK));
75
76 /* Allocate a new file segment for the rollback segment */
77 block = fseg_create(space, 0, TRX_RSEG + TRX_RSEG_FSEG_HEADER, mtr);
78
79 if (block == NULL) {
80 /* No space left */
81
82 return(FIL_NULL);
83 }
84
85 buf_block_dbg_add_level(block, SYNC_RSEG_HEADER_NEW);
86
87 page_no = block->page.id.page_no();
88
89 /* Get the rollback segment file page */
90 rsegf = trx_rsegf_get_new(space, page_no, page_size, mtr);
91
92 /* Initialize max size field */
93 mlog_write_ulint(rsegf + TRX_RSEG_MAX_SIZE, max_size,
94 MLOG_4BYTES, mtr);
95
96 /* Initialize the history list */
97
98 mlog_write_ulint(rsegf + TRX_RSEG_HISTORY_SIZE, 0, MLOG_4BYTES, mtr);
99 flst_init(rsegf + TRX_RSEG_HISTORY, mtr);
100
101 /* Reset the undo log slots */
102 for (i = 0; i < TRX_RSEG_N_SLOTS; i++) {
103
104 trx_rsegf_set_nth_undo(rsegf, i, FIL_NULL, mtr);
105 }
106
107 if (!trx_sys_is_noredo_rseg_slot(rseg_slot_no)) {
108 /* Non-redo rseg are re-created on restart and so no need
109 to persist this information in sys-header. Anyway, on restart
110 this information is not valid too as there is no space with
111 persisted space-id on restart. */
112
113 /* Add the rollback segment info to the free slot in
114 the trx system header */
115
116 sys_header = trx_sysf_get(mtr);
117
118 trx_sysf_rseg_set_space(sys_header, rseg_slot_no, space, mtr);
119
120 trx_sysf_rseg_set_page_no(
121 sys_header, rseg_slot_no, page_no, mtr);
122 }
123
124 return(page_no);
125 }
126
127 /***********************************************************************//**
128 Free's an instance of the rollback segment in memory. */
129 void
trx_rseg_mem_free(trx_rseg_t * rseg,trx_rseg_t ** rseg_array)130 trx_rseg_mem_free(
131 /*==============*/
132 trx_rseg_t* rseg, /* in, own: instance to free */
133 trx_rseg_t** rseg_array) /*!< out: add rseg reference to this
134 central array. */
135 {
136 trx_undo_t* undo;
137 trx_undo_t* next_undo;
138
139 mutex_free(&rseg->mutex);
140
141 /* There can't be any active transactions. */
142 ut_a(UT_LIST_GET_LEN(rseg->update_undo_list) == 0);
143 ut_a(UT_LIST_GET_LEN(rseg->insert_undo_list) == 0);
144
145 for (undo = UT_LIST_GET_FIRST(rseg->update_undo_cached);
146 undo != NULL;
147 undo = next_undo) {
148
149 next_undo = UT_LIST_GET_NEXT(undo_list, undo);
150
151 UT_LIST_REMOVE(rseg->update_undo_cached, undo);
152
153 MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_CACHED);
154
155 trx_undo_mem_free(undo);
156 }
157
158 for (undo = UT_LIST_GET_FIRST(rseg->insert_undo_cached);
159 undo != NULL;
160 undo = next_undo) {
161
162 next_undo = UT_LIST_GET_NEXT(undo_list, undo);
163
164 UT_LIST_REMOVE(rseg->insert_undo_cached, undo);
165
166 MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_CACHED);
167
168 trx_undo_mem_free(undo);
169 }
170
171 ut_a(*((trx_rseg_t**) rseg_array + rseg->id) == rseg);
172 *((trx_rseg_t**) rseg_array + rseg->id) = NULL;
173
174 ut_free(rseg);
175 }
176
177 /** Creates and initializes a rollback segment object.
178 The values for the fields are read from the header. The object is inserted to
179 the rseg list of the trx system object and a pointer is inserted in the rseg
180 array in the trx system object.
181 @param[in] id rollback segment id
182 @param[in] space space where the segment is placed
183 @param[in] page_no page number of the segment header
184 @param[in] page_size page size
185 @param[in,out] purge_queue rseg queue
186 @param[out] rseg_array add rseg reference to this central array
187 @param[in,out] mtr mini-transaction
188 @return own: rollback segment object */
189 static
190 trx_rseg_t*
trx_rseg_mem_create(ulint id,ulint space,ulint page_no,const page_size_t & page_size,purge_pq_t * purge_queue,trx_rseg_t ** rseg_array,mtr_t * mtr)191 trx_rseg_mem_create(
192 ulint id,
193 ulint space,
194 ulint page_no,
195 const page_size_t& page_size,
196 purge_pq_t* purge_queue,
197 trx_rseg_t** rseg_array,
198 mtr_t* mtr)
199 {
200 ulint len;
201 trx_rseg_t* rseg;
202 fil_addr_t node_addr;
203 trx_rsegf_t* rseg_header;
204 trx_ulogf_t* undo_log_hdr;
205 ulint sum_of_undo_sizes;
206
207 rseg = static_cast<trx_rseg_t*>(ut_zalloc_nokey(sizeof(trx_rseg_t)));
208
209 rseg->id = id;
210 rseg->space = space;
211 rseg->page_size.copy_from(page_size);
212 rseg->page_no = page_no;
213 rseg->trx_ref_count = 0;
214 rseg->skip_allocation = false;
215
216 if (fsp_is_system_temporary(space)) {
217 mutex_create(LATCH_ID_NOREDO_RSEG, &rseg->mutex);
218 } else {
219 mutex_create(LATCH_ID_REDO_RSEG, &rseg->mutex);
220 }
221
222 UT_LIST_INIT(rseg->update_undo_list, &trx_undo_t::undo_list);
223 UT_LIST_INIT(rseg->update_undo_cached, &trx_undo_t::undo_list);
224 UT_LIST_INIT(rseg->insert_undo_list, &trx_undo_t::undo_list);
225 UT_LIST_INIT(rseg->insert_undo_cached, &trx_undo_t::undo_list);
226
227 *((trx_rseg_t**) rseg_array + rseg->id) = rseg;
228
229 rseg_header = trx_rsegf_get_new(space, page_no, page_size, mtr);
230
231 rseg->max_size = mtr_read_ulint(
232 rseg_header + TRX_RSEG_MAX_SIZE, MLOG_4BYTES, mtr);
233
234 /* Initialize the undo log lists according to the rseg header */
235
236 sum_of_undo_sizes = trx_undo_lists_init(rseg);
237
238 rseg->curr_size = mtr_read_ulint(
239 rseg_header + TRX_RSEG_HISTORY_SIZE, MLOG_4BYTES, mtr)
240 + 1 + sum_of_undo_sizes;
241
242 len = flst_get_len(rseg_header + TRX_RSEG_HISTORY);
243
244 if (len > 0) {
245 trx_sys->rseg_history_len += len;
246
247 node_addr = trx_purge_get_log_from_hist(
248 flst_get_last(rseg_header + TRX_RSEG_HISTORY, mtr));
249
250 rseg->last_page_no = node_addr.page;
251 rseg->last_offset = node_addr.boffset;
252
253 undo_log_hdr = trx_undo_page_get(
254 page_id_t(rseg->space, node_addr.page),
255 rseg->page_size, mtr) + node_addr.boffset;
256
257 rseg->last_trx_no = mach_read_from_8(
258 undo_log_hdr + TRX_UNDO_TRX_NO);
259
260 rseg->last_del_marks = mtr_read_ulint(
261 undo_log_hdr + TRX_UNDO_DEL_MARKS, MLOG_2BYTES, mtr);
262
263 TrxUndoRsegs elem(rseg->last_trx_no);
264 elem.push_back(rseg);
265
266 if (rseg->last_page_no != FIL_NULL) {
267
268 /* There is no need to cover this operation by the purge
269 mutex because we are still bootstrapping. */
270
271 purge_queue->push(elem);
272 }
273 } else {
274 rseg->last_page_no = FIL_NULL;
275 }
276
277 return(rseg);
278 }
279
280 /********************************************************************
281 Check if rseg in given slot needs to be scheduled for purge. */
282 static
283 void
trx_rseg_schedule_pending_purge(trx_sysf_t * sys_header,purge_pq_t * purge_queue,ulint slot,mtr_t * mtr)284 trx_rseg_schedule_pending_purge(
285 /*============================*/
286 trx_sysf_t* sys_header, /*!< in: trx system header */
287 purge_pq_t* purge_queue, /*!< in/out: rseg queue */
288 ulint slot, /*!< in: check rseg from given slot. */
289 mtr_t* mtr) /*!< in: mtr */
290 {
291 ulint page_no;
292 ulint space;
293
294 page_no = trx_sysf_rseg_get_page_no(sys_header, slot, mtr);
295 space = trx_sysf_rseg_get_space(sys_header, slot, mtr);
296
297 if (page_no != FIL_NULL
298 && is_system_or_undo_tablespace(space)) {
299
300 /* rseg resides in system or undo tablespace and so
301 this is an upgrade scenario. trx_rseg_mem_create
302 will add rseg to purge queue if needed. */
303
304 trx_rseg_t* rseg = NULL;
305 bool found = true;
306 const page_size_t& page_size
307 = is_system_tablespace(space)
308 ? univ_page_size
309 : fil_space_get_page_size(space, &found);
310
311 ut_ad(found);
312
313 rseg = trx_rseg_mem_create(
314 slot, space, page_no, page_size,
315 purge_queue, trx_sys->pending_purge_rseg_array, mtr);
316
317 ut_a(rseg->id == slot);
318 }
319 }
320
321 /********************************************************************
322 Creates the memory copies for the rollback segments and initializes the
323 rseg array in trx_sys at a database startup. */
324 static
325 void
trx_rseg_create_instance(purge_pq_t * purge_queue)326 trx_rseg_create_instance(
327 /*=====================*/
328 purge_pq_t* purge_queue) /*!< in/out: rseg queue */
329 {
330 ulint i;
331
332 for (i = 0; i < TRX_SYS_N_RSEGS; i++) {
333 ulint page_no;
334
335 mtr_t mtr;
336 mtr.start();
337 trx_sysf_t* sys_header = trx_sysf_get(&mtr);
338
339 page_no = trx_sysf_rseg_get_page_no(sys_header, i, &mtr);
340
341 /* Slot-1....Slot-n are reserved for non-redo rsegs.
342 Non-redo rsegs are recreated on server re-start so
343 avoid initializing the existing non-redo rsegs. */
344 if (trx_sys_is_noredo_rseg_slot(i)) {
345
346 /* If this is an upgrade scenario then existing rsegs
347 in range from slot-1....slot-n needs to be scheduled
348 for purge if there are pending purge operation. */
349 trx_rseg_schedule_pending_purge(
350 sys_header, purge_queue, i, &mtr);
351
352 } else if (page_no != FIL_NULL) {
353 ulint space;
354 trx_rseg_t* rseg = NULL;
355
356 ut_a(!trx_rseg_get_on_id(i, true));
357
358 space = trx_sysf_rseg_get_space(sys_header, i, &mtr);
359
360 bool found = true;
361 const page_size_t& page_size
362 = is_system_tablespace(space)
363 ? univ_page_size
364 : fil_space_get_page_size(space, &found);
365
366 ut_ad(found);
367
368 trx_rseg_t** rseg_array =
369 static_cast<trx_rseg_t**>(trx_sys->rseg_array);
370
371 rseg = trx_rseg_mem_create(
372 i, space, page_no, page_size,
373 purge_queue, rseg_array, &mtr);
374
375 ut_a(rseg->id == i);
376 } else {
377 ut_a(trx_sys->rseg_array[i] == NULL);
378 }
379 mtr.commit();
380 }
381 }
382
383 /*********************************************************************
384 Creates a rollback segment.
385 @return pointer to new rollback segment if create successful */
386 trx_rseg_t*
trx_rseg_create(ulint space_id,ulint nth_free_slot)387 trx_rseg_create(
388 /*============*/
389 ulint space_id, /*!< in: id of UNDO tablespace */
390 ulint nth_free_slot) /*!< in: allocate nth free slot.
391 0 means next free slots. */
392 {
393 mtr_t mtr;
394 ulint slot_no;
395 trx_rseg_t* rseg = NULL;
396
397 mtr_start(&mtr);
398
399 /* To obey the latching order, acquire the file space
400 x-latch before the trx_sys->mutex. */
401 const fil_space_t* space = mtr_x_lock_space(space_id, &mtr);
402
403 switch (space->purpose) {
404 case FIL_TYPE_LOG:
405 case FIL_TYPE_IMPORT:
406 ut_ad(0);
407 case FIL_TYPE_TEMPORARY:
408 mtr_set_log_mode(&mtr, MTR_LOG_NO_REDO);
409 break;
410 case FIL_TYPE_TABLESPACE:
411 break;
412 }
413
414 slot_no = trx_sysf_rseg_find_free(
415 &mtr, space->purpose == FIL_TYPE_TEMPORARY, nth_free_slot);
416
417 if (slot_no != ULINT_UNDEFINED) {
418 ulint id;
419 ulint page_no;
420 trx_sysf_t* sys_header;
421 page_size_t page_size(space->flags);
422
423 page_no = trx_rseg_header_create(
424 space_id, page_size, ULINT_MAX, slot_no, &mtr);
425
426 if (page_no == FIL_NULL) {
427 mtr_commit(&mtr);
428
429 return(rseg);
430 }
431
432 sys_header = trx_sysf_get(&mtr);
433
434 id = trx_sysf_rseg_get_space(sys_header, slot_no, &mtr);
435 ut_a(id == space_id || trx_sys_is_noredo_rseg_slot(slot_no));
436
437 trx_rseg_t** rseg_array =
438 ((trx_rseg_t**) trx_sys->rseg_array);
439
440 rseg = trx_rseg_mem_create(
441 slot_no, space_id, page_no, page_size,
442 purge_sys->purge_queue, rseg_array, &mtr);
443 }
444
445 mtr_commit(&mtr);
446
447 return(rseg);
448 }
449
450 /*********************************************************************//**
451 Creates the memory copies for rollback segments and initializes the
452 rseg array in trx_sys at a database startup. */
453 void
trx_rseg_array_init(purge_pq_t * purge_queue)454 trx_rseg_array_init(
455 /*================*/
456 purge_pq_t* purge_queue) /*!< in: rseg queue */
457 {
458 trx_sys->rseg_history_len = 0;
459
460 trx_rseg_create_instance(purge_queue);
461 }
462
463 /********************************************************************
464 Get the number of unique rollback tablespaces in use except space id 0.
465 The last space id will be the sentinel value ULINT_UNDEFINED. The array
466 will be sorted on space id. Note: space_ids should have have space for
467 TRX_SYS_N_RSEGS + 1 elements.
468 @return number of unique rollback tablespaces in use. */
469 ulint
trx_rseg_get_n_undo_tablespaces(ulint * space_ids)470 trx_rseg_get_n_undo_tablespaces(
471 /*============================*/
472 ulint* space_ids) /*!< out: array of space ids of
473 UNDO tablespaces */
474 {
475 ulint i;
476 mtr_t mtr;
477 trx_sysf_t* sys_header;
478 ulint n_undo_tablespaces = 0;
479
480 mtr_start(&mtr);
481
482 sys_header = trx_sysf_get(&mtr);
483
484 for (i = 0; i < TRX_SYS_N_RSEGS; i++) {
485 ulint page_no;
486 ulint space;
487
488 page_no = trx_sysf_rseg_get_page_no(sys_header, i, &mtr);
489
490 if (page_no == FIL_NULL) {
491 continue;
492 }
493
494 space = trx_sysf_rseg_get_space(sys_header, i, &mtr);
495
496 if (space != 0) {
497 ulint j;
498 ibool found = FALSE;
499
500 for (j = 0; j < n_undo_tablespaces; ++j) {
501 if (space_ids[j] == space) {
502 found = TRUE;
503 break;
504 }
505 }
506
507 if (!found) {
508 ut_a(n_undo_tablespaces <= i);
509 space_ids[n_undo_tablespaces++] = space;
510 }
511 }
512 }
513
514 mtr_commit(&mtr);
515
516 ut_a(n_undo_tablespaces <= TRX_SYS_N_RSEGS);
517
518 space_ids[n_undo_tablespaces] = ULINT_UNDEFINED;
519
520 if (n_undo_tablespaces > 0) {
521 std::sort(space_ids, space_ids + n_undo_tablespaces);
522 }
523
524 return(n_undo_tablespaces);
525 }
526