1 /*****************************************************************************
2
3 Copyright (c) 1996, 2019, Oracle and/or its affiliates. All Rights Reserved.
4
5 This program is free software; you can redistribute it and/or modify it under
6 the terms of the GNU General Public License, version 2.0, as published by the
7 Free Software Foundation.
8
9 This program is also distributed with certain software (including but not
10 limited to OpenSSL) that is licensed under separate terms, as designated in a
11 particular file or component or in included license documentation. The authors
12 of MySQL hereby grant you an additional permission to link the program and
13 your derivative works with the separately licensed software that they have
14 included with MySQL.
15
16 This program is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
19 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 St, Fifth Floor, Boston, MA 02110-1301 USA
24
25 *****************************************************************************/
26
27 /** @file trx/trx0rseg.cc
28 Rollback segment
29
30 Created 3/26/1996 Heikki Tuuri
31 *******************************************************/
32
33 #include "trx0rseg.h"
34
35 #include <stddef.h>
36 #include <algorithm>
37
38 #include "clone0clone.h"
39 #include "fsp0sysspace.h"
40 #include "fut0lst.h"
41 #include "srv0mon.h"
42 #include "srv0srv.h"
43 #include "srv0start.h"
44 #include "trx0purge.h"
45 #include "trx0undo.h"
46
47 /** Creates a rollback segment header.
48 This function is called only when a new rollback segment is created in
49 the database.
50 @param[in] space_id space id
51 @param[in] page_size page size
52 @param[in] max_size max size in pages
53 @param[in] rseg_slot rseg id == slot number in RSEG_ARRAY
54 @param[in,out] mtr mini-transaction
55 @return page number of the created segment, FIL_NULL if fail */
trx_rseg_header_create(space_id_t space_id,const page_size_t & page_size,page_no_t max_size,ulint rseg_slot,mtr_t * mtr)56 page_no_t trx_rseg_header_create(space_id_t space_id,
57 const page_size_t &page_size,
58 page_no_t max_size, ulint rseg_slot,
59 mtr_t *mtr) {
60 page_no_t page_no;
61 trx_rsegf_t *rsegf;
62 trx_sysf_t *sys_header;
63 trx_rsegsf_t *rsegs_header;
64 ulint i;
65 buf_block_t *block;
66
67 ut_ad(mtr);
68 ut_ad(mtr_memo_contains(mtr, fil_space_get_latch(space_id), MTR_MEMO_X_LOCK));
69
70 /* Allocate a new file segment for the rollback segment */
71 block = fseg_create(space_id, 0, TRX_RSEG + TRX_RSEG_FSEG_HEADER, mtr);
72
73 if (block == nullptr) {
74 return (FIL_NULL); /* No space left */
75 }
76
77 buf_block_dbg_add_level(block, SYNC_RSEG_HEADER_NEW);
78
79 page_no = block->page.id.page_no();
80
81 /* Get the rollback segment file page */
82 rsegf = trx_rsegf_get_new(space_id, page_no, page_size, mtr);
83
84 /* Initialize max size field */
85 mlog_write_ulint(rsegf + TRX_RSEG_MAX_SIZE, max_size, MLOG_4BYTES, mtr);
86
87 /* Initialize the history list */
88 mlog_write_ulint(rsegf + TRX_RSEG_HISTORY_SIZE, 0, MLOG_4BYTES, mtr);
89 flst_init(rsegf + TRX_RSEG_HISTORY, mtr);
90
91 /* Reset the undo log slots */
92 for (i = 0; i < TRX_RSEG_N_SLOTS; i++) {
93 trx_rsegf_set_nth_undo(rsegf, i, FIL_NULL, mtr);
94 }
95
96 /* Initialize maximum transaction number. */
97 mlog_write_ull(rsegf + TRX_RSEG_MAX_TRX_NO, 0, mtr);
98
99 if (space_id == TRX_SYS_SPACE) {
100 /* All rollback segments in the system tablespace need
101 to be found in the TRX_SYS page in the rseg_id slot.
102 Add the rollback segment info to the free slot in the
103 trx system header in the TRX_SYS page. */
104
105 sys_header = trx_sysf_get(mtr);
106
107 trx_sysf_rseg_set_space(sys_header, rseg_slot, space_id, mtr);
108
109 trx_sysf_rseg_set_page_no(sys_header, rseg_slot, page_no, mtr);
110
111 } else if (fsp_is_system_temporary(space_id)) {
112 /* Rollback segments in the system temporary tablespace
113 are re-created on restart. So they only need to be
114 referenced in memory. */
115
116 } else {
117 /* Rollback Segments in independent undo tablespaces
118 are tracked in the RSEG_ARRAY page. */
119 rsegs_header = trx_rsegsf_get(space_id, mtr);
120
121 trx_rsegsf_set_page_no(rsegs_header, rseg_slot, page_no, mtr);
122 }
123
124 return (page_no);
125 }
126
127 /** Free an instance of the rollback segment in memory.
128 @param[in] rseg pointer to an rseg to free */
trx_rseg_mem_free(trx_rseg_t * rseg)129 void trx_rseg_mem_free(trx_rseg_t *rseg) {
130 trx_undo_t *undo;
131 trx_undo_t *next_undo;
132
133 mutex_free(&rseg->mutex);
134
135 if (!srv_apply_log_only) {
136 /* There can't be any active transactions. */
137 ut_a(UT_LIST_GET_LEN(rseg->update_undo_list) == 0);
138 ut_a(UT_LIST_GET_LEN(rseg->insert_undo_list) == 0);
139 } else {
140 for (undo = UT_LIST_GET_FIRST(rseg->update_undo_list); undo != NULL;
141 undo = next_undo) {
142 next_undo = UT_LIST_GET_NEXT(undo_list, undo);
143
144 UT_LIST_REMOVE(rseg->update_undo_list, undo);
145
146 MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_CACHED);
147
148 trx_undo_mem_free(undo);
149 }
150 for (undo = UT_LIST_GET_FIRST(rseg->insert_undo_list); undo != NULL;
151 undo = next_undo) {
152 next_undo = UT_LIST_GET_NEXT(undo_list, undo);
153
154 UT_LIST_REMOVE(rseg->insert_undo_list, undo);
155
156 MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_CACHED);
157
158 trx_undo_mem_free(undo);
159 }
160 }
161
162 for (undo = UT_LIST_GET_FIRST(rseg->update_undo_cached); undo != nullptr;
163 undo = next_undo) {
164 next_undo = UT_LIST_GET_NEXT(undo_list, undo);
165
166 UT_LIST_REMOVE(rseg->update_undo_cached, undo);
167
168 MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_CACHED);
169
170 trx_undo_mem_free(undo);
171 }
172
173 for (undo = UT_LIST_GET_FIRST(rseg->insert_undo_cached); undo != nullptr;
174 undo = next_undo) {
175 next_undo = UT_LIST_GET_NEXT(undo_list, undo);
176
177 UT_LIST_REMOVE(rseg->insert_undo_cached, undo);
178
179 MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_CACHED);
180
181 trx_undo_mem_free(undo);
182 }
183
184 ut_free(rseg);
185 }
186
trx_rseg_persist_gtid(trx_rseg_t * rseg,trx_id_t gtid_trx_no)187 static void trx_rseg_persist_gtid(trx_rseg_t *rseg, trx_id_t gtid_trx_no) {
188 /* Old server where GTID persistence were not enabled. */
189 if (gtid_trx_no == 0) {
190 return;
191 }
192 /* The mini transactions used in this function should not do any
193 modification/write operation. We read the undo header and send GTIDs
194 to the GTID persistor. There is no impact if the server crashes
195 anytime during the operation. */
196 mtr_t mtr;
197 mtr_start(&mtr);
198
199 auto rseg_header =
200 trx_rsegf_get_new(rseg->space_id, rseg->page_no, rseg->page_size, &mtr);
201
202 auto rseg_max_trx_no = mach_read_from_8(rseg_header + TRX_RSEG_MAX_TRX_NO);
203
204 /* Check if GTID for transactions in this rollback segment are persisted. */
205 if (rseg_max_trx_no < gtid_trx_no) {
206 mtr_commit(&mtr);
207 return;
208 }
209
210 /* Head of transaction history list in rollback segment. */
211 auto node = rseg_header + TRX_RSEG_HISTORY;
212
213 fil_addr_t node_addr = flst_get_first(node, &mtr);
214 ut_ad(node_addr.page != FIL_NULL);
215
216 mtr_commit(&mtr);
217
218 while (node_addr.page != FIL_NULL) {
219 mtr_start(&mtr);
220 /* Get the undo page pointed by current node. */
221 page_id_t undo_page_id(rseg->space_id, node_addr.page);
222 auto undo_page = trx_undo_page_get(undo_page_id, rseg->page_size, &mtr);
223
224 /* Get undo log and trx_no for the transaction. */
225 node = undo_page + node_addr.boffset;
226 auto undo_log = node - TRX_UNDO_HISTORY_NODE;
227 auto undo_trx_no = mach_read_from_8(undo_log + TRX_UNDO_TRX_NO);
228
229 /* Check and exit if the transaction GTID is already persisted. We
230 don't need to check any more as history list is ordered by trx_no. */
231 if (undo_trx_no < gtid_trx_no) {
232 mtr_commit(&mtr);
233 break;
234 }
235 trx_undo_gtid_read_and_persist(undo_log);
236
237 /* Move to next node. */
238 node_addr = flst_get_next_addr(node, &mtr);
239 mtr_commit(&mtr);
240 }
241 }
242
trx_rseg_mem_create(ulint id,space_id_t space_id,page_no_t page_no,const page_size_t & page_size,trx_id_t gtid_trx_no,purge_pq_t * purge_queue,mtr_t * mtr)243 trx_rseg_t *trx_rseg_mem_create(ulint id, space_id_t space_id,
244 page_no_t page_no, const page_size_t &page_size,
245 trx_id_t gtid_trx_no, purge_pq_t *purge_queue,
246 mtr_t *mtr) {
247 auto rseg = static_cast<trx_rseg_t *>(ut_zalloc_nokey(sizeof(trx_rseg_t)));
248
249 rseg->id = id;
250 rseg->space_id = space_id;
251 rseg->page_size.copy_from(page_size);
252 rseg->page_no = page_no;
253 rseg->trx_ref_count = 0;
254
255 if (fsp_is_system_temporary(space_id)) {
256 mutex_create(LATCH_ID_TEMP_SPACE_RSEG, &rseg->mutex);
257 } else if (fsp_is_undo_tablespace(space_id)) {
258 mutex_create(LATCH_ID_UNDO_SPACE_RSEG, &rseg->mutex);
259 } else {
260 mutex_create(LATCH_ID_TRX_SYS_RSEG, &rseg->mutex);
261 }
262
263 UT_LIST_INIT(rseg->update_undo_list, &trx_undo_t::undo_list);
264 UT_LIST_INIT(rseg->update_undo_cached, &trx_undo_t::undo_list);
265 UT_LIST_INIT(rseg->insert_undo_list, &trx_undo_t::undo_list);
266 UT_LIST_INIT(rseg->insert_undo_cached, &trx_undo_t::undo_list);
267
268 auto rseg_header = trx_rsegf_get_new(space_id, page_no, page_size, mtr);
269
270 rseg->max_size =
271 mtr_read_ulint(rseg_header + TRX_RSEG_MAX_SIZE, MLOG_4BYTES, mtr);
272
273 /* Initialize the undo log lists according to the rseg header */
274
275 auto sum_of_undo_sizes = trx_undo_lists_init(rseg);
276
277 rseg->set_curr_size(
278 mtr_read_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE, MLOG_4BYTES, mtr) +
279 1 + sum_of_undo_sizes);
280
281 auto len = flst_get_len(rseg_header + TRX_RSEG_HISTORY);
282
283 if (len > 0) {
284 trx_sys->rseg_history_len += len;
285
286 /* Extract GTID from history and send to GTID persister. */
287 trx_rseg_persist_gtid(rseg, gtid_trx_no);
288
289 auto node_addr = trx_purge_get_log_from_hist(
290 flst_get_last(rseg_header + TRX_RSEG_HISTORY, mtr));
291
292 rseg->last_page_no = node_addr.page;
293 rseg->last_offset = node_addr.boffset;
294
295 auto undo_log_hdr =
296 trx_undo_page_get(page_id_t(rseg->space_id, node_addr.page),
297 rseg->page_size, mtr) +
298 node_addr.boffset;
299
300 rseg->last_trx_no = mach_read_from_8(undo_log_hdr + TRX_UNDO_TRX_NO);
301
302 #ifdef UNIV_DEBUG
303 /* Update last transaction number during recovery. */
304 if (rseg->last_trx_no > trx_sys->rw_max_trx_no) {
305 trx_sys->rw_max_trx_no = rseg->last_trx_no;
306 }
307 #endif /* UNIV_DEBUG */
308
309 rseg->last_del_marks =
310 mtr_read_ulint(undo_log_hdr + TRX_UNDO_DEL_MARKS, MLOG_2BYTES, mtr);
311
312 TrxUndoRsegs elem(rseg->last_trx_no);
313 elem.push_back(rseg);
314
315 if (rseg->last_page_no != FIL_NULL) {
316 /* The only time an rseg is added that has existing
317 undo is when the server is being started. So no
318 mutex is needed here. */
319 ut_ad(srv_is_being_started);
320
321 ut_ad(space_id == TRX_SYS_SPACE ||
322 (srv_is_upgrade_mode != undo::is_reserved(space_id)));
323
324 purge_queue->push(elem);
325 }
326 } else {
327 rseg->last_page_no = FIL_NULL;
328 }
329
330 return (rseg);
331 }
332
333 /** Return a page number from a slot in the rseg_array page of an
334 undo tablespace.
335 @param[in] space_id undo tablespace ID
336 @param[in] rseg_id rollback segment ID
337 @return page_no Page number of the rollback segment header page */
trx_rseg_get_page_no(space_id_t space_id,ulint rseg_id)338 page_no_t trx_rseg_get_page_no(space_id_t space_id, ulint rseg_id) {
339 mtr_t mtr;
340 mtr.start();
341
342 trx_rsegsf_t *rsegs_header = trx_rsegsf_get(space_id, &mtr);
343
344 page_no_t page_no = trx_rsegsf_get_page_no(rsegs_header, rseg_id, &mtr);
345
346 mtr.commit();
347
348 return (page_no);
349 }
350
351 /** Read each rollback segment slot in the TRX_SYS page and the RSEG_ARRAY
352 page of each undo tablespace. Create trx_rseg_t objects for all rollback
353 segments found. This runs at database startup and initializes the in-memory
354 lists of trx_rseg_t objects. We need to look at all slots in TRX_SYS and
355 each RSEG_ARRAY page because we need to look for any existing undo log that
356 may need to be recovered by purge. No latch is needed since this is still
357 single-threaded startup. If we find existing rseg slots in TRX_SYS page
358 that reference undo tablespaces and have active undo logs, then quit.
359 They require an upgrade of undo tablespaces and that cannot happen with
360 active undo logs.
361 @param[in] purge_queue queue of rsegs to purge */
trx_rsegs_init(purge_pq_t * purge_queue)362 void trx_rsegs_init(purge_pq_t *purge_queue) {
363 trx_sys->rseg_history_len = 0;
364
365 ulint slot;
366 mtr_t mtr;
367 space_id_t space_id;
368 page_no_t page_no;
369 trx_rseg_t *rseg = nullptr;
370
371 /* Get GTID transaction number from SYS */
372 mtr.start();
373 trx_sysf_t *sys_header = trx_sysf_get(&mtr);
374 auto page = sys_header - TRX_SYS;
375 auto gtid_trx_no = mach_read_from_8(page + TRX_SYS_TRX_NUM_GTID);
376
377 mtr.commit();
378
379 auto >id_persistor = clone_sys->get_gtid_persistor();
380 gtid_persistor.set_oldest_trx_no_recovery(gtid_trx_no);
381
382 for (slot = 0; slot < TRX_SYS_N_RSEGS; slot++) {
383 mtr.start();
384 trx_sysf_t *sys_header = trx_sysf_get(&mtr);
385
386 page_no = trx_sysf_rseg_get_page_no(sys_header, slot, &mtr);
387
388 if (page_no != FIL_NULL) {
389 space_id = trx_sysf_rseg_get_space(sys_header, slot, &mtr);
390
391 if (!undo::is_active_truncate_log_present(undo::id2num(space_id))) {
392 /* Create the trx_rseg_t object.
393 Note that all tablespaces with rollback segments
394 use univ_page_size. (system, temp & undo) */
395 rseg = trx_rseg_mem_create(slot, space_id, page_no, univ_page_size,
396 gtid_trx_no, purge_queue, &mtr);
397
398 ut_a(rseg->id == slot);
399
400 trx_sys->rsegs.push_back(rseg);
401 }
402 }
403 mtr.commit();
404 }
405
406 undo::spaces->s_lock();
407 for (auto undo_space : undo::spaces->m_spaces) {
408 /* Remember the size of the purge queue before processing this
409 undo tablespace. */
410 size_t purge_queue_size = purge_queue->size();
411
412 undo_space->rsegs()->x_lock();
413
414 for (slot = 0; slot < FSP_MAX_ROLLBACK_SEGMENTS; slot++) {
415 page_no = trx_rseg_get_page_no(undo_space->id(), slot);
416
417 /* There are no gaps in an RSEG_ARRAY page. New rsegs
418 are added sequentially and never deleted until the
419 undo tablespace is truncated.*/
420 if (page_no == FIL_NULL) {
421 break;
422 }
423
424 mtr.start();
425
426 /* Create the trx_rseg_t object.
427 Note that all tablespaces with rollback segments
428 use univ_page_size. */
429 rseg =
430 trx_rseg_mem_create(slot, undo_space->id(), page_no, univ_page_size,
431 gtid_trx_no, purge_queue, &mtr);
432
433 ut_a(rseg->id == slot);
434
435 undo_space->rsegs()->push_back(rseg);
436
437 mtr.commit();
438 }
439 undo_space->rsegs()->x_unlock();
440
441 /* If there are no undo logs in this explicit undo tablespace at
442 startup, mark it empty so that it will not be used until the state
443 recorded in the DD can be applied in apply_dd_undo_state(). */
444 if (undo_space->is_explicit() && !undo_space->is_empty()) {
445 size_t cur_size = purge_queue->size();
446 if (purge_queue_size == cur_size) {
447 undo_space->set_empty();
448 }
449 }
450 }
451 undo::spaces->s_unlock();
452 }
453
454 /** Create a rollback segment in the given tablespace. This could be either
455 the system tablespace, the temporary tablespace, or an undo tablespace.
456 @param[in] space_id tablespace to get the rollback segment
457 @param[in] rseg_id slot number of the rseg within this tablespace
458 @return page number of the rollback segment header page created */
trx_rseg_create(space_id_t space_id,ulint rseg_id)459 page_no_t trx_rseg_create(space_id_t space_id, ulint rseg_id) {
460 mtr_t mtr;
461 fil_space_t *space = fil_space_get(space_id);
462
463 log_free_check();
464
465 mtr_start(&mtr);
466
467 /* To obey the latching order, acquire the file space
468 x-latch before the mutex for trx_sys. */
469 mtr_x_lock(&space->latch, &mtr);
470
471 ut_ad(space->purpose == (fsp_is_system_temporary(space_id)
472 ? FIL_TYPE_TEMPORARY
473 : FIL_TYPE_TABLESPACE));
474 ut_ad(univ_page_size.equals_to(page_size_t(space->flags)));
475
476 if (fsp_is_system_temporary(space_id)) {
477 mtr_set_log_mode(&mtr, MTR_LOG_NO_REDO);
478 } else if (space_id == TRX_SYS_SPACE) {
479 /* We will modify TRX_SYS_RSEGS in TRX_SYS page. */
480 }
481
482 page_no_t page_no = trx_rseg_header_create(space_id, univ_page_size,
483 PAGE_NO_MAX, rseg_id, &mtr);
484
485 mtr_commit(&mtr);
486
487 return (page_no);
488 }
489
490 /** Initialize */
init()491 void Rsegs::init() {
492 m_rsegs.reserve(TRX_SYS_N_RSEGS);
493
494 m_latch = static_cast<rw_lock_t *>(ut_zalloc_nokey(sizeof(*m_latch)));
495
496 rw_lock_create(rsegs_lock_key, m_latch, SYNC_RSEGS);
497 }
498
499 /** De-initialize */
deinit()500 void Rsegs::deinit() {
501 clear();
502
503 rw_lock_free(m_latch);
504 ut_free(m_latch);
505 m_latch = nullptr;
506 }
507
508 /** Clear the vector of cached rollback segments leaving the
509 reserved space allocated. */
clear()510 void Rsegs::clear() {
511 for (auto rseg : m_rsegs) {
512 trx_rseg_mem_free(rseg);
513 }
514 m_rsegs.clear();
515 m_rsegs.shrink_to_fit();
516 }
517
518 /** Find an rseg in the std::vector that uses the rseg_id given.
519 @param[in] rseg_id A slot in a durable array such as
520 the TRX_SYS page or RSEG_ARRAY page.
521 @return a pointer to an trx_rseg_t that uses the rseg_id. */
find(ulint rseg_id)522 trx_rseg_t *Rsegs::find(ulint rseg_id) {
523 trx_rseg_t *rseg;
524
525 /* In most cases, the rsegs will be in slot order with no gaps. */
526 if (rseg_id < m_rsegs.size()) {
527 rseg = m_rsegs.at(rseg_id);
528 if (rseg->id == rseg_id) {
529 return (rseg);
530 }
531 }
532
533 /* If there are gaps in the numbering, do a search. */
534 for (auto rseg : m_rsegs) {
535 if (rseg->id == rseg_id) {
536 return (rseg);
537 }
538 }
539
540 return (nullptr);
541 }
542
543 /** This does two things to the target tablespace.
544 1. Find or create (trx_rseg_create) the requested number of rollback segments.
545 2. Make sure each rollback segment is tracked in memory (trx_rseg_mem_create).
546 All existing rollback segments were found earlier in trx_rsegs_init().
547 This will add new ones if we need them according to target_rsegs.
548 @param[in] space_id tablespace ID that should contain rollback
549 segments
550 @param[in] target_rsegs target number of rollback segments per
551 tablespace
552 @param[in] rsegs list of rsegs to add to
553 @param[in,out] n_total_created A running total of rollback segment created in
554 undo tablespaces
555 @return true if all rsegs are added, false if not. */
trx_rseg_add_rollback_segments(space_id_t space_id,ulong target_rsegs,Rsegs * rsegs,ulint * const n_total_created)556 bool trx_rseg_add_rollback_segments(space_id_t space_id, ulong target_rsegs,
557 Rsegs *rsegs,
558 ulint *const n_total_created) {
559 bool success = true;
560 mtr_t mtr;
561 page_no_t page_no;
562 trx_rseg_t *rseg;
563 ulint n_existing = 0;
564 ulint n_created = 0;
565 ulint n_tracked = 0;
566
567 enum space_type_t { TEMP, UNDO } type;
568
569 ut_ad(space_id != TRX_SYS_SPACE);
570
571 type = (fsp_is_undo_tablespace(space_id) ? UNDO : TEMP);
572 ut_ad(type == UNDO || fsp_is_system_temporary(space_id));
573
574 /* Protect against two threads trying to add rollback segments
575 at the same time. */
576 rsegs->x_lock();
577
578 for (ulint num = 0; num < FSP_MAX_ROLLBACK_SEGMENTS; num++) {
579 if (rsegs->size() >= target_rsegs) {
580 break;
581 }
582
583 ulint rseg_id = num;
584
585 /* If the rseg object exists, move to the next rseg_id. */
586 rseg = rsegs->find(rseg_id);
587 if (rseg != nullptr) {
588 ut_ad(rseg->id == rseg_id);
589 n_existing++;
590 continue;
591 }
592
593 /* Look in the tablespace to discover if the rollback segment
594 already exists. */
595 if (type == UNDO) {
596 page_no = trx_rseg_get_page_no(space_id, rseg_id);
597
598 } else {
599 /* There is no durable list of rollback segments in
600 the temporary tablespace. Since it was not found in
601 the rsegs vector, assume the rollback segment does
602 not exist in the temp tablespace. */
603 page_no = FIL_NULL;
604 }
605
606 if (page_no == FIL_NULL) {
607 /* Create the missing rollback segment if allowed. */
608 if (type == TEMP || (!srv_read_only_mode && srv_force_recovery == 0 &&
609 !srv_apply_log_only)) {
610 page_no = trx_rseg_create(space_id, rseg_id);
611 if (page_no == FIL_NULL) {
612 /* There may not be enough space in
613 the temporary tablespace since it is
614 possible to limit its size. */
615 ut_ad(type == TEMP);
616 continue;
617 }
618 n_created++;
619 } else {
620 /* trx_rseg_create() is being prevented
621 in an UNDO tablespace. Don't try to create
622 any more. */
623 break;
624 }
625 } else {
626 n_existing++;
627 }
628
629 /* Create the trx_rseg_t object. */
630 mtr.start();
631
632 fil_space_t *space = fil_space_get(space_id);
633 ut_ad(univ_page_size.equals_to(page_size_t(space->flags)));
634 mtr_x_lock(&space->latch, &mtr);
635
636 if (type == TEMP) {
637 mtr_set_log_mode(&mtr, MTR_LOG_NO_REDO);
638 }
639
640 rseg = trx_rseg_mem_create(rseg_id, space_id, page_no, univ_page_size, 0,
641 purge_sys->purge_queue, &mtr);
642
643 mtr.commit();
644
645 if (rseg != nullptr) {
646 ut_a(rseg->id == rseg_id);
647 rsegs->push_back(rseg);
648 n_tracked++;
649 }
650 }
651
652 rsegs->x_unlock();
653
654 std::ostringstream loc;
655 switch (type) {
656 case UNDO:
657 loc << "undo tablespace number " << undo::id2num(space_id);
658 break;
659 case TEMP:
660 loc << "the temporary tablespace";
661 break;
662 }
663
664 ulint n_known = rsegs->size();
665 if (n_known < target_rsegs) {
666 if (srv_read_only_mode || srv_force_recovery > 0 || srv_apply_log_only) {
667 bool use_and = srv_read_only_mode && srv_force_recovery > 0;
668 bool use_and_second = srv_read_only_mode || srv_force_recovery > 0;
669
670 ib::info(ER_IB_MSG_1191)
671 << "Could not create all " << target_rsegs << " rollback segments in "
672 << loc.str() << " because "
673 << (srv_read_only_mode ? " read-only mode is set" : "")
674 << (use_and ? " and" : "")
675 << (srv_force_recovery > 0 ? " innodb_force_recovery is set" : "")
676 << (use_and_second ? " and" : "")
677 << (srv_apply_log_only ? " --apply-log-only is set" : "")
678 << ". Only " << n_known << " are active.";
679
680 srv_rollback_segments =
681 ut_min(srv_rollback_segments, static_cast<ulong>(n_known));
682
683 } else {
684 ib::warn(ER_IB_MSG_1192)
685 << "Could not create all " << target_rsegs << " rollback segments in "
686 << loc.str() << ". Only " << n_known << " are active.";
687
688 srv_rollback_segments =
689 ut_min(srv_rollback_segments, static_cast<ulong>(n_known));
690
691 success = false;
692 }
693
694 } else if (n_created > 0) {
695 ib::info(ER_IB_MSG_1193)
696 << "Created " << n_created << " and tracked " << n_tracked
697 << " new rollback segment(s) in " << loc.str() << ". " << target_rsegs
698 << " are now active.";
699
700 } else if (n_tracked > 0) {
701 ib::info(ER_IB_MSG_1194)
702 << "Using " << n_tracked << " more rollback segment(s) in " << loc.str()
703 << ". " << target_rsegs << " are now active.";
704
705 } else if (target_rsegs < n_known) {
706 ib::info(ER_IB_MSG_1195)
707 << target_rsegs << " rollback segment(s) are now active in "
708 << loc.str() << ".";
709 }
710
711 if (n_total_created != nullptr) {
712 *n_total_created += n_created;
713 }
714
715 return (success);
716 }
717
718 /** Add more rsegs to the rseg list in each tablespace until there are
719 srv_rollback_segments of them. Use any rollback segment that already
720 exists so that the purge_queue can be filled and processed with any
721 existing undo log. If the rollback segments do not exist in this
722 tablespace and we need them according to target_rollback_segments,
723 then build them in the tablespace.
724 @param[in] target_rollback_segments new number of rollback
725 segments per space
726 @return true if all necessary rollback segments and trx_rseg_t objects
727 were created. */
trx_rseg_adjust_rollback_segments(ulong target_rollback_segments)728 bool trx_rseg_adjust_rollback_segments(ulong target_rollback_segments) {
729 /** The number of rollback segments created in the datafile. */
730 ulint n_total_created = 0;
731
732 /* Make sure Temporary Tablespace has enough rsegs. */
733 if (!trx_rseg_add_rollback_segments(srv_tmp_space.space_id(),
734 target_rollback_segments,
735 &(trx_sys->tmp_rsegs), nullptr)) {
736 return (false);
737 }
738
739 /* Only the temp rsegs are used with a high force_recovery. */
740 if (srv_force_recovery >= SRV_FORCE_NO_UNDO_LOG_SCAN) {
741 return (true);
742 }
743
744 /* Adjust the number of rollback segments in each Undo Tablespace
745 whether or not it is currently active. If rollback segments are written
746 to the tablespace, they will be checkpointed. But we cannot hold
747 undo::spaces->s_lock while doing a checkpoint because of latch order
748 violation. So traverse the list by ID. */
749 undo::spaces->s_lock();
750 for (auto undo_space : undo::spaces->m_spaces) {
751 if (!trx_rseg_add_rollback_segments(
752 undo_space->id(), target_rollback_segments, undo_space->rsegs(),
753 &n_total_created)) {
754 undo::spaces->s_unlock();
755 return (false);
756 }
757 }
758 undo::spaces->s_unlock();
759
760 /* Make sure these rollback segments are checkpointed. */
761 if (n_total_created > 0 && !srv_read_only_mode && srv_force_recovery == 0) {
762 log_make_latest_checkpoint();
763 }
764
765 return (true);
766 }
767
768 /** Create the requested number of Rollback Segments in the undo tablespace
769 and add them to the Rsegs object.
770 @param[in] space_id undo tablespace ID
771 @param[in] target_rollback_segments number of rollback segments per space
772 @return true if all necessary rollback segments and trx_rseg_t objects
773 were created. */
trx_rseg_init_rollback_segments(space_id_t space_id,ulong target_rollback_segments)774 bool trx_rseg_init_rollback_segments(space_id_t space_id,
775 ulong target_rollback_segments) {
776 /** The number of rollback segments created in the datafile. */
777 ulint n_total_created = 0;
778
779 undo::spaces->s_lock();
780 space_id_t space_num = undo::id2num(space_id);
781 undo::Tablespace *undo_space = undo::spaces->find(space_num);
782 undo::spaces->s_unlock();
783
784 if (!trx_rseg_add_rollback_segments(space_id, target_rollback_segments,
785 undo_space->rsegs(), &n_total_created)) {
786 return (false);
787 }
788
789 return (true);
790 }
791
792 /** Build a list of unique undo tablespaces found in the TRX_SYS page.
793 Do not count the system tablespace. The vector will be sorted on space id.
794 @param[in,out] spaces_to_open list of undo tablespaces found. */
trx_rseg_get_n_undo_tablespaces(Space_Ids * spaces_to_open)795 void trx_rseg_get_n_undo_tablespaces(Space_Ids *spaces_to_open) {
796 ulint i;
797 mtr_t mtr;
798 trx_sysf_t *sys_header;
799
800 ut_ad(spaces_to_open->empty());
801
802 mtr_start(&mtr);
803
804 sys_header = trx_sysf_get(&mtr);
805
806 for (i = 0; i < TRX_SYS_N_RSEGS; i++) {
807 page_no_t page_no;
808 space_id_t space_id;
809
810 page_no = trx_sysf_rseg_get_page_no(sys_header, i, &mtr);
811
812 if (page_no == FIL_NULL) {
813 continue;
814 }
815
816 space_id = trx_sysf_rseg_get_space(sys_header, i, &mtr);
817
818 /* The system space id should not be in this array. */
819 if (space_id != TRX_SYS_SPACE && !spaces_to_open->contains(space_id)) {
820 spaces_to_open->push_back(space_id);
821 }
822 }
823
824 mtr_commit(&mtr);
825
826 ut_a(spaces_to_open->size() <= TRX_SYS_N_RSEGS);
827 }
828
829 /** Upgrade the TRX_SYS page so that it no longer tracks rsegs in undo
830 tablespaces. It should only track rollback segments in the system tablespace.
831 Put FIL_NULL in the slots in TRX_SYS. Latch protection is not needed since
832 this is during single-threaded startup. */
trx_rseg_upgrade_undo_tablespaces()833 void trx_rseg_upgrade_undo_tablespaces() {
834 ulint i;
835 mtr_t mtr;
836 trx_sysf_t *sys_header;
837
838 mtr_start(&mtr);
839 fil_space_t *space = fil_space_get(TRX_SYS_SPACE);
840 mtr_x_lock(&space->latch, &mtr);
841
842 sys_header = trx_sysf_get(&mtr);
843
844 /* First, put FIL_NULL in all the slots that contain the space_id
845 of any non-system tablespace. The rollback segments in those
846 tablespaces are replaced when the file is replaced. */
847 for (i = 0; i < TRX_SYS_N_RSEGS; i++) {
848 page_no_t page_no;
849 space_id_t space_id;
850
851 page_no = trx_sysf_rseg_get_page_no(sys_header, i, &mtr);
852
853 if (page_no == FIL_NULL) {
854 continue;
855 }
856
857 space_id = trx_sysf_rseg_get_space(sys_header, i, &mtr);
858
859 /* The TRX_SYS page only tracks older undo tablespaces
860 that do not use the RSEG_ARRAY page. */
861 ut_a(space_id < dict_sys_t::s_min_undo_space_id);
862
863 /* Leave rollback segments in the system tablespace
864 untouched in case innodb_undo_tablespaces is later
865 set back to 0. */
866 if (space_id != 0) {
867 trx_sysf_rseg_set_space(sys_header, i, FIL_NULL, &mtr);
868
869 trx_sysf_rseg_set_page_no(sys_header, i, FIL_NULL, &mtr);
870 }
871 }
872
873 mtr_commit(&mtr);
874 }
875
876 /** Create the file page for the rollback segment directory in an undo
877 tablespace. This function is called just after an undo tablespace is
878 created so the next page created here should by FSP_FSEG_DIR_PAGE_NUM.
879 @param[in] space_id Undo Tablespace ID
880 @param[in] mtr mtr */
trx_rseg_array_create(space_id_t space_id,mtr_t * mtr)881 void trx_rseg_array_create(space_id_t space_id, mtr_t *mtr) {
882 trx_rsegsf_t *rsegs_header;
883 buf_block_t *block;
884 page_t *page;
885 byte *ptr;
886 ulint len;
887
888 /* Create the fseg directory file block in a new allocated file segment */
889 block = fseg_create(space_id, 0,
890 RSEG_ARRAY_HEADER + RSEG_ARRAY_FSEG_HEADER_OFFSET, mtr);
891 buf_block_dbg_add_level(block, SYNC_RSEG_ARRAY_HEADER);
892
893 ut_a(block->page.id.page_no() == FSP_RSEG_ARRAY_PAGE_NO);
894
895 page = buf_block_get_frame(block);
896
897 mlog_write_ulint(page + FIL_PAGE_TYPE, FIL_PAGE_TYPE_RSEG_ARRAY, MLOG_2BYTES,
898 mtr);
899
900 rsegs_header = page + RSEG_ARRAY_HEADER;
901
902 /* Initialize the rseg array version. */
903 mach_write_to_4(rsegs_header + RSEG_ARRAY_VERSION_OFFSET, RSEG_ARRAY_VERSION);
904
905 /* Initialize the directory size. */
906 mach_write_to_4(rsegs_header + RSEG_ARRAY_SIZE_OFFSET, 0);
907
908 /* Reset the rollback segment header page slots. Use the full page
909 minus overhead. Reserve some extra room for future use. */
910 ptr = RSEG_ARRAY_PAGES_OFFSET + rsegs_header;
911 len = UNIV_PAGE_SIZE - RSEG_ARRAY_HEADER - RSEG_ARRAY_PAGES_OFFSET -
912 RSEG_ARRAY_RESERVED_BYTES - FIL_PAGE_DATA_END;
913 memset(ptr, 0xff, len);
914
915 mlog_log_string(rsegs_header,
916 UNIV_PAGE_SIZE - RSEG_ARRAY_HEADER - FIL_PAGE_DATA_END, mtr);
917 }
918
919 #ifdef UNIV_DEBUG
validate_curr_size(bool take_mutex)920 bool trx_rseg_t::validate_curr_size(bool take_mutex) {
921 mtr_t mtr;
922 mtr_start(&mtr);
923
924 if (take_mutex) {
925 mutex_enter(&mutex);
926 } else {
927 ut_ad(mutex_own(&mutex));
928 }
929
930 /* Obtain the rollback segment header. */
931 trx_rsegf_t *rseg_hdr = trx_rsegf_get(space_id, page_no, page_size, &mtr);
932
933 /* Number of file pages occupied by the logs in the history list */
934 ulint hist_size =
935 mtr_read_ulint(rseg_hdr + TRX_RSEG_HISTORY_SIZE, MLOG_4BYTES, &mtr);
936
937 ulint sum_undo_size = 0;
938
939 for (ulint i = 0; i < TRX_RSEG_N_SLOTS; i++) {
940 /* Get the file page number of the nth undo log slot. */
941 page_no_t undo_page_no = trx_rsegf_get_nth_undo(rseg_hdr, i, &mtr);
942
943 if (undo_page_no == FIL_NULL) {
944 /* Skip the empty slot. */
945 continue;
946 }
947
948 /* Get the undo log page. */
949 page_t *undo_page =
950 trx_undo_page_get(page_id_t(space_id, undo_page_no), page_size, &mtr);
951
952 /* Obtain the undo log segment header. */
953 trx_usegf_t *seg_header = undo_page + TRX_UNDO_SEG_HDR;
954
955 /* Get the number of pages in the undo log segment. */
956 ulint undo_size = flst_get_len(seg_header + TRX_UNDO_PAGE_LIST);
957
958 sum_undo_size += undo_size;
959 }
960
961 if (take_mutex) {
962 mutex_exit(&mutex);
963 }
964 mtr_commit(&mtr);
965
966 ulint total_size = sum_undo_size + hist_size + 1;
967
968 ut_ad(total_size == curr_size);
969
970 return (total_size == curr_size);
971 }
972 #endif /* UNIV_DEBUG */
973