1/*****************************************************************************
2
3Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved.
4Copyright (c) 2014, 2018, MariaDB Corporation.
5
6This program is free software; you can redistribute it and/or modify it under
7the terms of the GNU General Public License as published by the Free Software
8Foundation; version 2 of the License.
9
10This program is distributed in the hope that it will be useful, but WITHOUT
11ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License along with
15this program; if not, write to the Free Software Foundation, Inc.,
1651 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
17
18*****************************************************************************/
19
20/**************************************************//**
21@file trx/trx0undo.cc
22Transaction undo log
23
24Created 3/26/1996 Heikki Tuuri
25*******************************************************/
26
27#include "ha_prototypes.h"
28
29#include "trx0undo.h"
30#include "fsp0fsp.h"
31#include "mach0data.h"
32#include "mtr0log.h"
33#include "srv0mon.h"
34#include "srv0srv.h"
35#include "srv0start.h"
36#include "trx0purge.h"
37#include "trx0rec.h"
38#include "trx0rseg.h"
39#include "trx0trx.h"
40
41/* How should the old versions in the history list be managed?
42 ----------------------------------------------------------
43If each transaction is given a whole page for its update undo log, file
44space consumption can be 10 times higher than necessary. Therefore,
45partly filled update undo log pages should be reusable. But then there
46is no way individual pages can be ordered so that the ordering agrees
47with the serialization numbers of the transactions on the pages. Thus,
48the history list must be formed of undo logs, not their header pages as
49it was in the old implementation.
50 However, on a single header page the transactions are placed in
51the order of their serialization numbers. As old versions are purged, we
52may free the page when the last transaction on the page has been purged.
53 A problem is that the purge has to go through the transactions
54in the serialization order. This means that we have to look through all
55rollback segments for the one that has the smallest transaction number
56in its history list.
57 When should we do a purge? A purge is necessary when space is
58running out in any of the rollback segments. Then we may have to purge
59also old version which might be needed by some consistent read. How do
60we trigger the start of a purge? When a transaction writes to an undo log,
61it may notice that the space is running out. When a read view is closed,
62it may make some history superfluous. The server can have an utility which
63periodically checks if it can purge some history.
64 In a parallellized purge we have the problem that a query thread
65can remove a delete marked clustered index record before another query
66thread has processed an earlier version of the record, which cannot then
67be done because the row cannot be constructed from the clustered index
68record. To avoid this problem, we will store in the update and delete mark
69undo record also the columns necessary to construct the secondary index
70entries which are modified.
71 We can latch the stack of versions of a single clustered index record
72by taking a latch on the clustered index page. As long as the latch is held,
73no new versions can be added and no versions removed by undo. But, a purge
74can still remove old versions from the bottom of the stack. */
75
76/* How to protect rollback segments, undo logs, and history lists with
77 -------------------------------------------------------------------
78latches?
79-------
80The contention of the trx_sys.mutex should be minimized. When a transaction
81does its first insert or modify in an index, an undo log is assigned for it.
82Then we must have an x-latch to the rollback segment header.
83 When the transaction performs modifications or rolls back, its
84undo log is protected by undo page latches.
85Only the thread that is associated with the transaction may hold multiple
86undo page latches at a time. Undo pages are always private to a single
87transaction. Other threads that are performing MVCC reads
88or checking for implicit locks will lock at most one undo page at a time
89in trx_undo_get_undo_rec_low().
90 When the transaction commits, its persistent undo log is added
91to the history list. If it is not suitable for reuse, its slot is reset.
92In both cases, an x-latch must be acquired on the rollback segment header page.
93 The purge operation steps through the history list without modifying
94it until a truncate operation occurs, which can remove undo logs from the end
95of the list and release undo log segments. In stepping through the list,
96s-latches on the undo log pages are enough, but in a truncate, x-latches must
97be obtained on the rollback segment and individual pages. */
98
99/********************************************************************//**
100Creates and initializes an undo log memory object.
101@return own: the undo log memory object */
102static
103trx_undo_t*
104trx_undo_mem_create(
105/*================*/
106 trx_rseg_t* rseg, /*!< in: rollback segment memory object */
107 ulint id, /*!< in: slot index within rseg */
108 trx_id_t trx_id, /*!< in: id of the trx for which the undo log
109 is created */
110 const XID* xid, /*!< in: X/Open XA transaction identification*/
111 ulint page_no,/*!< in: undo log header page number */
112 ulint offset);/*!< in: undo log header byte offset on page */
113
114/** Determine the start offset of undo log records of an undo log page.
115@param[in] undo_page undo log page
116@param[in] page_no undo log header page number
117@param[in] offset undo log header offset
118@return start offset */
119static
120uint16_t
121trx_undo_page_get_start(const page_t* undo_page, ulint page_no, ulint offset)
122{
123 return page_no == page_get_page_no(undo_page)
124 ? mach_read_from_2(offset + TRX_UNDO_LOG_START + undo_page)
125 : TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE;
126}
127
128/** Get the first undo log record on a page.
129@param[in] page undo log page
130@param[in] page_no undo log header page number
131@param[in] offset undo log header page offset
132@return pointer to first record
133@retval NULL if none exists */
134static
135trx_undo_rec_t*
136trx_undo_page_get_first_rec(page_t* page, ulint page_no, ulint offset)
137{
138 ulint start = trx_undo_page_get_start(page, page_no, offset);
139 return start == trx_undo_page_get_end(page, page_no, offset)
140 ? NULL
141 : page + start;
142}
143
144/** Get the last undo log record on a page.
145@param[in] page undo log page
146@param[in] page_no undo log header page number
147@param[in] offset undo log header page offset
148@return pointer to last record
149@retval NULL if none exists */
150static
151trx_undo_rec_t*
152trx_undo_page_get_last_rec(page_t* page, ulint page_no, ulint offset)
153{
154 ulint end = trx_undo_page_get_end(page, page_no, offset);
155
156 return trx_undo_page_get_start(page, page_no, offset) == end
157 ? NULL
158 : page + mach_read_from_2(page + end - 2);
159}
160
161/***********************************************************************//**
162Gets the previous record in an undo log from the previous page.
163@return undo log record, the page s-latched, NULL if none */
164static
165trx_undo_rec_t*
166trx_undo_get_prev_rec_from_prev_page(
167/*=================================*/
168 trx_undo_rec_t* rec, /*!< in: undo record */
169 ulint page_no,/*!< in: undo log header page number */
170 ulint offset, /*!< in: undo log header offset on page */
171 bool shared, /*!< in: true=S-latch, false=X-latch */
172 mtr_t* mtr) /*!< in: mtr */
173{
174 ulint space;
175 ulint prev_page_no;
176 page_t* prev_page;
177 page_t* undo_page;
178
179 undo_page = page_align(rec);
180
181 prev_page_no = flst_get_prev_addr(undo_page + TRX_UNDO_PAGE_HDR
182 + TRX_UNDO_PAGE_NODE, mtr)
183 .page;
184
185 if (prev_page_no == FIL_NULL) {
186
187 return(NULL);
188 }
189
190 space = page_get_space_id(undo_page);
191
192 buf_block_t* block = buf_page_get(
193 page_id_t(space, prev_page_no), univ_page_size,
194 shared ? RW_S_LATCH : RW_X_LATCH, mtr);
195
196 buf_block_dbg_add_level(block, SYNC_TRX_UNDO_PAGE);
197
198 prev_page = buf_block_get_frame(block);
199
200 return(trx_undo_page_get_last_rec(prev_page, page_no, offset));
201}
202
203/** Get the previous undo log record.
204@param[in] rec undo log record
205@param[in] page_no undo log header page number
206@param[in] offset undo log header page offset
207@return pointer to record
208@retval NULL if none */
209static
210trx_undo_rec_t*
211trx_undo_page_get_prev_rec(trx_undo_rec_t* rec, ulint page_no, ulint offset)
212{
213 page_t* undo_page;
214 ulint start;
215
216 undo_page = (page_t*) ut_align_down(rec, srv_page_size);
217
218 start = trx_undo_page_get_start(undo_page, page_no, offset);
219
220 if (start + undo_page == rec) {
221
222 return(NULL);
223 }
224
225 return(undo_page + mach_read_from_2(rec - 2));
226}
227
228/***********************************************************************//**
229Gets the previous record in an undo log.
230@return undo log record, the page s-latched, NULL if none */
231trx_undo_rec_t*
232trx_undo_get_prev_rec(
233/*==================*/
234 trx_undo_rec_t* rec, /*!< in: undo record */
235 ulint page_no,/*!< in: undo log header page number */
236 ulint offset, /*!< in: undo log header offset on page */
237 bool shared, /*!< in: true=S-latch, false=X-latch */
238 mtr_t* mtr) /*!< in: mtr */
239{
240 trx_undo_rec_t* prev_rec;
241
242 prev_rec = trx_undo_page_get_prev_rec(rec, page_no, offset);
243
244 if (prev_rec) {
245
246 return(prev_rec);
247 }
248
249 /* We have to go to the previous undo log page to look for the
250 previous record */
251
252 return(trx_undo_get_prev_rec_from_prev_page(rec, page_no, offset,
253 shared, mtr));
254}
255
256/** Gets the next record in an undo log from the next page.
257@param[in] space undo log header space
258@param[in] undo_page undo log page
259@param[in] page_no undo log header page number
260@param[in] offset undo log header offset on page
261@param[in] mode latch mode: RW_S_LATCH or RW_X_LATCH
262@param[in,out] mtr mini-transaction
263@return undo log record, the page latched, NULL if none */
264static
265trx_undo_rec_t*
266trx_undo_get_next_rec_from_next_page(
267 ulint space,
268 const page_t* undo_page,
269 ulint page_no,
270 ulint offset,
271 ulint mode,
272 mtr_t* mtr)
273{
274 const trx_ulogf_t* log_hdr;
275 ulint next_page_no;
276 page_t* next_page;
277 ulint next;
278
279 if (page_no == page_get_page_no(undo_page)) {
280
281 log_hdr = undo_page + offset;
282 next = mach_read_from_2(log_hdr + TRX_UNDO_NEXT_LOG);
283
284 if (next != 0) {
285
286 return(NULL);
287 }
288 }
289
290 next_page_no = flst_get_next_addr(undo_page + TRX_UNDO_PAGE_HDR
291 + TRX_UNDO_PAGE_NODE, mtr)
292 .page;
293 if (next_page_no == FIL_NULL) {
294
295 return(NULL);
296 }
297
298 const page_id_t next_page_id(space, next_page_no);
299
300 if (mode == RW_S_LATCH) {
301 next_page = trx_undo_page_get_s_latched(
302 next_page_id, mtr);
303 } else {
304 ut_ad(mode == RW_X_LATCH);
305 next_page = trx_undo_page_get(next_page_id, mtr);
306 }
307
308 return(trx_undo_page_get_first_rec(next_page, page_no, offset));
309}
310
311/***********************************************************************//**
312Gets the next record in an undo log.
313@return undo log record, the page s-latched, NULL if none */
314trx_undo_rec_t*
315trx_undo_get_next_rec(
316/*==================*/
317 trx_undo_rec_t* rec, /*!< in: undo record */
318 ulint page_no,/*!< in: undo log header page number */
319 ulint offset, /*!< in: undo log header offset on page */
320 mtr_t* mtr) /*!< in: mtr */
321{
322 ulint space;
323 trx_undo_rec_t* next_rec;
324
325 next_rec = trx_undo_page_get_next_rec(rec, page_no, offset);
326
327 if (next_rec) {
328 return(next_rec);
329 }
330
331 space = page_get_space_id(page_align(rec));
332
333 return(trx_undo_get_next_rec_from_next_page(space,
334 page_align(rec),
335 page_no, offset,
336 RW_S_LATCH, mtr));
337}
338
339/** Gets the first record in an undo log.
340@param[in] space undo log header space
341@param[in] page_no undo log header page number
342@param[in] offset undo log header offset on page
343@param[in] mode latching mode: RW_S_LATCH or RW_X_LATCH
344@param[in,out] mtr mini-transaction
345@return undo log record, the page latched, NULL if none */
346trx_undo_rec_t*
347trx_undo_get_first_rec(
348 fil_space_t* space,
349 ulint page_no,
350 ulint offset,
351 ulint mode,
352 mtr_t* mtr)
353{
354 page_t* undo_page;
355 trx_undo_rec_t* rec;
356
357 const page_id_t page_id(space->id, page_no);
358
359 if (mode == RW_S_LATCH) {
360 undo_page = trx_undo_page_get_s_latched(page_id, mtr);
361 } else {
362 undo_page = trx_undo_page_get(page_id, mtr);
363 }
364
365 rec = trx_undo_page_get_first_rec(undo_page, page_no, offset);
366
367 if (rec) {
368 return(rec);
369 }
370
371 return(trx_undo_get_next_rec_from_next_page(space->id,
372 undo_page, page_no, offset,
373 mode, mtr));
374}
375
376/*============== UNDO LOG FILE COPY CREATION AND FREEING ==================*/
377
378/** Parse MLOG_UNDO_INIT.
379@param[in] ptr log record
380@param[in] end_ptr end of log record buffer
381@param[in,out] page page or NULL
382@return end of log record
383@retval NULL if the log record is incomplete */
384byte*
385trx_undo_parse_page_init(const byte* ptr, const byte* end_ptr, page_t* page)
386{
387 if (end_ptr <= ptr) {
388 return NULL;
389 }
390
391 const ulint type = *ptr++;
392
393 if (type > TRX_UNDO_UPDATE) {
394 recv_sys->found_corrupt_log = true;
395 } else if (page) {
396 /* Starting with MDEV-12288 in MariaDB 10.3.1, we use
397 type=0 for the combined insert/update undo log
398 pages. MariaDB 10.2 would use TRX_UNDO_INSERT or
399 TRX_UNDO_UPDATE. */
400 mach_write_to_2(FIL_PAGE_TYPE + page, FIL_PAGE_UNDO_LOG);
401 mach_write_to_2(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_TYPE + page,
402 type);
403 mach_write_to_2(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_START + page,
404 TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE);
405 mach_write_to_2(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE + page,
406 TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE);
407 }
408
409 return(const_cast<byte*>(ptr));
410}
411
412/** Parse MLOG_UNDO_HDR_REUSE for crash-upgrade from MariaDB 10.2.
413@param[in] ptr redo log record
414@param[in] end_ptr end of log buffer
415@param[in,out] page undo log page or NULL
416@return end of log record or NULL */
417byte*
418trx_undo_parse_page_header_reuse(
419 const byte* ptr,
420 const byte* end_ptr,
421 page_t* undo_page)
422{
423 trx_id_t trx_id = mach_u64_parse_compressed(&ptr, end_ptr);
424
425 if (!ptr || !undo_page) {
426 return(const_cast<byte*>(ptr));
427 }
428
429 compile_time_assert(TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE
430 + TRX_UNDO_LOG_XA_HDR_SIZE
431 < UNIV_PAGE_SIZE_MIN - 100);
432
433 const ulint new_free = TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE
434 + TRX_UNDO_LOG_OLD_HDR_SIZE;
435
436 /* Insert undo data is not needed after commit: we may free all
437 the space on the page */
438
439 ut_ad(mach_read_from_2(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_TYPE
440 + undo_page)
441 == TRX_UNDO_INSERT);
442
443 byte* page_hdr = undo_page + TRX_UNDO_PAGE_HDR;
444 mach_write_to_2(page_hdr + TRX_UNDO_PAGE_START, new_free);
445 mach_write_to_2(page_hdr + TRX_UNDO_PAGE_FREE, new_free);
446 mach_write_to_2(TRX_UNDO_SEG_HDR + TRX_UNDO_STATE + undo_page,
447 TRX_UNDO_ACTIVE);
448
449 byte* log_hdr = undo_page + TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE;
450
451 mach_write_to_8(log_hdr + TRX_UNDO_TRX_ID, trx_id);
452 mach_write_to_2(log_hdr + TRX_UNDO_LOG_START, new_free);
453
454 mach_write_to_1(log_hdr + TRX_UNDO_XID_EXISTS, FALSE);
455 mach_write_to_1(log_hdr + TRX_UNDO_DICT_TRANS, FALSE);
456
457 return(const_cast<byte*>(ptr));
458}
459
460/** Initialize the fields in an undo log segment page.
461@param[in,out] undo_block undo page
462@param[in,out] mtr mini-transaction */
463static void trx_undo_page_init(buf_block_t* undo_block, mtr_t* mtr)
464{
465 page_t* page = undo_block->frame;
466 mach_write_to_2(FIL_PAGE_TYPE + page, FIL_PAGE_UNDO_LOG);
467 mach_write_to_2(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_TYPE + page, 0);
468 mach_write_to_2(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_START + page,
469 TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE);
470 mach_write_to_2(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE + page,
471 TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE);
472
473 mtr->set_modified();
474 switch (mtr->get_log_mode()) {
475 case MTR_LOG_NONE:
476 case MTR_LOG_NO_REDO:
477 return;
478 case MTR_LOG_SHORT_INSERTS:
479 ut_ad(0);
480 /* fall through */
481 case MTR_LOG_ALL:
482 break;
483 }
484
485 byte* log_ptr = mtr->get_log()->open(11 + 1);
486 log_ptr = mlog_write_initial_log_record_low(
487 MLOG_UNDO_INIT,
488 undo_block->page.id.space(),
489 undo_block->page.id.page_no(),
490 log_ptr, mtr);
491 *log_ptr++ = 0;
492 mlog_close(mtr, log_ptr);
493}
494
495/** Create an undo log segment.
496@param[in,out] space tablespace
497@param[in,out] rseg_hdr rollback segment header (x-latched)
498@param[out] id undo slot number
499@param[out] err error code
500@param[in,out] mtr mini-transaction
501@return undo log block
502@retval NULL on failure */
503static MY_ATTRIBUTE((nonnull, warn_unused_result))
504buf_block_t*
505trx_undo_seg_create(fil_space_t* space, trx_rsegf_t* rseg_hdr, ulint* id,
506 dberr_t* err, mtr_t* mtr)
507{
508 ulint slot_no;
509 buf_block_t* block;
510 ulint n_reserved;
511 bool success;
512
513 slot_no = trx_rsegf_undo_find_free(rseg_hdr);
514
515 if (slot_no == ULINT_UNDEFINED) {
516 ib::warn() << "Cannot find a free slot for an undo log. Do"
517 " you have too many active transactions running"
518 " concurrently?";
519
520 *err = DB_TOO_MANY_CONCURRENT_TRXS;
521 return NULL;
522 }
523
524 success = fsp_reserve_free_extents(&n_reserved, space, 2, FSP_UNDO,
525 mtr);
526 if (!success) {
527 *err = DB_OUT_OF_FILE_SPACE;
528 return NULL;
529 }
530
531 /* Allocate a new file segment for the undo log */
532 block = fseg_create(space, 0, TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER,
533 mtr, true);
534
535 space->release_free_extents(n_reserved);
536
537 if (block == NULL) {
538 *err = DB_OUT_OF_FILE_SPACE;
539 return NULL;
540 }
541
542 buf_block_dbg_add_level(block, SYNC_TRX_UNDO_PAGE);
543
544 trx_undo_page_init(block, mtr);
545
546 mlog_write_ulint(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE + block->frame,
547 TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE,
548 MLOG_2BYTES, mtr);
549
550 mlog_write_ulint(TRX_UNDO_SEG_HDR + TRX_UNDO_LAST_LOG + block->frame,
551 0, MLOG_2BYTES, mtr);
552
553 flst_init(TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST + block->frame, mtr);
554
555 flst_add_last(TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST + block->frame,
556 TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE + block->frame,
557 mtr);
558
559 *id = slot_no;
560 trx_rsegf_set_nth_undo(rseg_hdr, slot_no, block->page.id.page_no(),
561 mtr);
562
563 MONITOR_INC(MONITOR_NUM_UNDO_SLOT_USED);
564
565 *err = DB_SUCCESS;
566 return block;
567}
568
569/**********************************************************************//**
570Writes the mtr log entry of an undo log header initialization. */
571UNIV_INLINE
572void
573trx_undo_header_create_log(
574/*=======================*/
575 const page_t* undo_page, /*!< in: undo log header page */
576 trx_id_t trx_id, /*!< in: transaction id */
577 mtr_t* mtr) /*!< in: mtr */
578{
579 mlog_write_initial_log_record(undo_page, MLOG_UNDO_HDR_CREATE, mtr);
580
581 mlog_catenate_ull_compressed(mtr, trx_id);
582}
583
584/***************************************************************//**
585Creates a new undo log header in file. NOTE that this function has its own
586log record type MLOG_UNDO_HDR_CREATE. You must NOT change the operation of
587this function!
588@return header byte offset on page */
589static
590ulint
591trx_undo_header_create(
592/*===================*/
593 page_t* undo_page, /*!< in/out: undo log segment
594 header page, x-latched; it is
595 assumed that there is
596 TRX_UNDO_LOG_XA_HDR_SIZE bytes
597 free space on it */
598 trx_id_t trx_id, /*!< in: transaction id */
599 mtr_t* mtr) /*!< in: mtr */
600{
601 trx_upagef_t* page_hdr;
602 trx_usegf_t* seg_hdr;
603 trx_ulogf_t* log_hdr;
604 ulint prev_log;
605 ulint free;
606 ulint new_free;
607
608 ut_ad(mtr && undo_page);
609
610 page_hdr = undo_page + TRX_UNDO_PAGE_HDR;
611 seg_hdr = undo_page + TRX_UNDO_SEG_HDR;
612
613 free = mach_read_from_2(page_hdr + TRX_UNDO_PAGE_FREE);
614
615 log_hdr = undo_page + free;
616
617 new_free = free + TRX_UNDO_LOG_OLD_HDR_SIZE;
618
619 ut_a(free + TRX_UNDO_LOG_XA_HDR_SIZE < srv_page_size - 100);
620
621 mach_write_to_2(page_hdr + TRX_UNDO_PAGE_START, new_free);
622
623 mach_write_to_2(page_hdr + TRX_UNDO_PAGE_FREE, new_free);
624
625 mach_write_to_2(seg_hdr + TRX_UNDO_STATE, TRX_UNDO_ACTIVE);
626
627 prev_log = mach_read_from_2(seg_hdr + TRX_UNDO_LAST_LOG);
628
629 if (prev_log != 0) {
630 trx_ulogf_t* prev_log_hdr;
631
632 prev_log_hdr = undo_page + prev_log;
633
634 mach_write_to_2(prev_log_hdr + TRX_UNDO_NEXT_LOG, free);
635 }
636
637 mach_write_to_2(seg_hdr + TRX_UNDO_LAST_LOG, free);
638
639 log_hdr = undo_page + free;
640
641 mach_write_to_2(log_hdr + TRX_UNDO_NEEDS_PURGE, 1);
642
643 mach_write_to_8(log_hdr + TRX_UNDO_TRX_ID, trx_id);
644 mach_write_to_2(log_hdr + TRX_UNDO_LOG_START, new_free);
645
646 mach_write_to_1(log_hdr + TRX_UNDO_XID_EXISTS, FALSE);
647 mach_write_to_1(log_hdr + TRX_UNDO_DICT_TRANS, FALSE);
648
649 mach_write_to_2(log_hdr + TRX_UNDO_NEXT_LOG, 0);
650 mach_write_to_2(log_hdr + TRX_UNDO_PREV_LOG, prev_log);
651
652 /* Write the log record about the header creation */
653 trx_undo_header_create_log(undo_page, trx_id, mtr);
654
655 return(free);
656}
657
658/********************************************************************//**
659Write X/Open XA Transaction Identification (XID) to undo log header */
660static
661void
662trx_undo_write_xid(
663/*===============*/
664 trx_ulogf_t* log_hdr,/*!< in: undo log header */
665 const XID* xid, /*!< in: X/Open XA Transaction Identification */
666 mtr_t* mtr) /*!< in: mtr */
667{
668 mlog_write_ulint(log_hdr + TRX_UNDO_XA_FORMAT,
669 static_cast<ulint>(xid->formatID),
670 MLOG_4BYTES, mtr);
671
672 mlog_write_ulint(log_hdr + TRX_UNDO_XA_TRID_LEN,
673 static_cast<ulint>(xid->gtrid_length),
674 MLOG_4BYTES, mtr);
675
676 mlog_write_ulint(log_hdr + TRX_UNDO_XA_BQUAL_LEN,
677 static_cast<ulint>(xid->bqual_length),
678 MLOG_4BYTES, mtr);
679
680 mlog_write_string(log_hdr + TRX_UNDO_XA_XID,
681 reinterpret_cast<const byte*>(xid->data),
682 XIDDATASIZE, mtr);
683}
684
685/********************************************************************//**
686Read X/Open XA Transaction Identification (XID) from undo log header */
687static
688void
689trx_undo_read_xid(const trx_ulogf_t* log_hdr, XID* xid)
690{
691 xid->formatID=static_cast<long>(mach_read_from_4(
692 log_hdr + TRX_UNDO_XA_FORMAT));
693
694 xid->gtrid_length=static_cast<long>(mach_read_from_4(
695 log_hdr + TRX_UNDO_XA_TRID_LEN));
696
697 xid->bqual_length=static_cast<long>(mach_read_from_4(
698 log_hdr + TRX_UNDO_XA_BQUAL_LEN));
699
700 memcpy(xid->data, log_hdr + TRX_UNDO_XA_XID, XIDDATASIZE);
701}
702
703/***************************************************************//**
704Adds space for the XA XID after an undo log old-style header. */
705static
706void
707trx_undo_header_add_space_for_xid(
708/*==============================*/
709 page_t* undo_page,/*!< in: undo log segment header page */
710 trx_ulogf_t* log_hdr,/*!< in: undo log header */
711 mtr_t* mtr) /*!< in: mtr */
712{
713 trx_upagef_t* page_hdr;
714 ulint free;
715 ulint new_free;
716
717 page_hdr = undo_page + TRX_UNDO_PAGE_HDR;
718
719 free = mach_read_from_2(page_hdr + TRX_UNDO_PAGE_FREE);
720
721 /* free is now the end offset of the old style undo log header */
722
723 ut_a(free == (ulint)(log_hdr - undo_page) + TRX_UNDO_LOG_OLD_HDR_SIZE);
724
725 new_free = free + (TRX_UNDO_LOG_XA_HDR_SIZE
726 - TRX_UNDO_LOG_OLD_HDR_SIZE);
727
728 /* Add space for a XID after the header, update the free offset
729 fields on the undo log page and in the undo log header */
730
731 mlog_write_ulint(page_hdr + TRX_UNDO_PAGE_START, new_free,
732 MLOG_2BYTES, mtr);
733
734 mlog_write_ulint(page_hdr + TRX_UNDO_PAGE_FREE, new_free,
735 MLOG_2BYTES, mtr);
736
737 mlog_write_ulint(log_hdr + TRX_UNDO_LOG_START, new_free,
738 MLOG_2BYTES, mtr);
739}
740
741/** Parse the redo log entry of an undo log page header create.
742@param[in] ptr redo log record
743@param[in] end_ptr end of log buffer
744@param[in,out] page page frame or NULL
745@param[in,out] mtr mini-transaction or NULL
746@return end of log record or NULL */
747byte*
748trx_undo_parse_page_header(
749 const byte* ptr,
750 const byte* end_ptr,
751 page_t* page,
752 mtr_t* mtr)
753{
754 trx_id_t trx_id = mach_u64_parse_compressed(&ptr, end_ptr);
755
756 if (ptr != NULL && page != NULL) {
757 trx_undo_header_create(page, trx_id, mtr);
758 return(const_cast<byte*>(ptr));
759 }
760
761 return(const_cast<byte*>(ptr));
762}
763
764/** Allocate an undo log page.
765@param[in,out] undo undo log
766@param[in,out] mtr mini-transaction that does not hold any page latch
767@return X-latched block if success
768@retval NULL on failure */
769buf_block_t* trx_undo_add_page(trx_undo_t* undo, mtr_t* mtr)
770{
771 trx_rseg_t* rseg = undo->rseg;
772 buf_block_t* new_block = NULL;
773 ulint n_reserved;
774 page_t* header_page;
775
776 /* When we add a page to an undo log, this is analogous to
777 a pessimistic insert in a B-tree, and we must reserve the
778 counterpart of the tree latch, which is the rseg mutex. */
779
780 mutex_enter(&rseg->mutex);
781
782 header_page = trx_undo_page_get(
783 page_id_t(undo->rseg->space->id, undo->hdr_page_no), mtr);
784
785 if (!fsp_reserve_free_extents(&n_reserved, undo->rseg->space, 1,
786 FSP_UNDO, mtr)) {
787 goto func_exit;
788 }
789
790 new_block = fseg_alloc_free_page_general(
791 TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER
792 + header_page,
793 undo->top_page_no + 1, FSP_UP, TRUE, mtr, mtr);
794
795 rseg->space->release_free_extents(n_reserved);
796
797 if (!new_block) {
798 goto func_exit;
799 }
800
801 ut_ad(rw_lock_get_x_lock_count(&new_block->lock) == 1);
802 buf_block_dbg_add_level(new_block, SYNC_TRX_UNDO_PAGE);
803 undo->last_page_no = new_block->page.id.page_no();
804
805 trx_undo_page_init(new_block, mtr);
806
807 flst_add_last(TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST
808 + header_page,
809 TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE
810 + new_block->frame,
811 mtr);
812 undo->size++;
813 rseg->curr_size++;
814
815func_exit:
816 mutex_exit(&rseg->mutex);
817 return(new_block);
818}
819
820/********************************************************************//**
821Frees an undo log page that is not the header page.
822@return last page number in remaining log */
823static
824ulint
825trx_undo_free_page(
826/*===============*/
827 trx_rseg_t* rseg, /*!< in: rollback segment */
828 bool in_history, /*!< in: TRUE if the undo log is in the history
829 list */
830 ulint hdr_page_no, /*!< in: header page number */
831 ulint page_no, /*!< in: page number to free: must not be the
832 header page */
833 mtr_t* mtr) /*!< in: mtr which does not have a latch to any
834 undo log page; the caller must have reserved
835 the rollback segment mutex */
836{
837 page_t* header_page;
838 page_t* undo_page;
839 fil_addr_t last_addr;
840 trx_rsegf_t* rseg_header;
841 ulint hist_size;
842 const ulint space = rseg->space->id;
843
844 ut_a(hdr_page_no != page_no);
845 ut_ad(mutex_own(&(rseg->mutex)));
846
847 undo_page = trx_undo_page_get(page_id_t(space, page_no), mtr);
848
849 header_page = trx_undo_page_get(page_id_t(space, hdr_page_no), mtr);
850
851 flst_remove(header_page + TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST,
852 undo_page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE, mtr);
853
854 fseg_free_page(header_page + TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER,
855 space, page_no, false, mtr);
856
857 last_addr = flst_get_last(header_page + TRX_UNDO_SEG_HDR
858 + TRX_UNDO_PAGE_LIST, mtr);
859 rseg->curr_size--;
860
861 if (in_history) {
862 rseg_header = trx_rsegf_get(rseg->space, rseg->page_no, mtr);
863
864 hist_size = mtr_read_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE,
865 MLOG_4BYTES, mtr);
866 ut_ad(hist_size > 0);
867 mlog_write_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE,
868 hist_size - 1, MLOG_4BYTES, mtr);
869 }
870
871 return(last_addr.page);
872}
873
874/** Free the last undo log page. The caller must hold the rseg mutex.
875@param[in,out] undo undo log
876@param[in,out] mtr mini-transaction that does not hold any undo log page
877 or that has allocated the undo log page */
878void
879trx_undo_free_last_page(trx_undo_t* undo, mtr_t* mtr)
880{
881 ut_ad(undo->hdr_page_no != undo->last_page_no);
882 ut_ad(undo->size > 0);
883
884 undo->last_page_no = trx_undo_free_page(
885 undo->rseg, false, undo->hdr_page_no, undo->last_page_no, mtr);
886
887 undo->size--;
888}
889
890/** Truncate the tail of an undo log during rollback.
891@param[in,out] undo undo log
892@param[in] limit all undo logs after this limit will be discarded
893@param[in] is_temp whether this is temporary undo log */
894void
895trx_undo_truncate_end(trx_undo_t* undo, undo_no_t limit, bool is_temp)
896{
897 ut_ad(mutex_own(&undo->rseg->mutex));
898 ut_ad(is_temp == !undo->rseg->is_persistent());
899
900 for (;;) {
901 mtr_t mtr;
902 mtr.start();
903 if (is_temp) {
904 mtr.set_log_mode(MTR_LOG_NO_REDO);
905 }
906
907 trx_undo_rec_t* trunc_here = NULL;
908 page_t* undo_page = trx_undo_page_get(
909 page_id_t(undo->rseg->space->id, undo->last_page_no),
910 &mtr);
911 trx_undo_rec_t* rec = trx_undo_page_get_last_rec(
912 undo_page, undo->hdr_page_no, undo->hdr_offset);
913 while (rec) {
914 if (trx_undo_rec_get_undo_no(rec) >= limit) {
915 /* Truncate at least this record off, maybe
916 more */
917 trunc_here = rec;
918 } else {
919 goto function_exit;
920 }
921
922 rec = trx_undo_page_get_prev_rec(rec,
923 undo->hdr_page_no,
924 undo->hdr_offset);
925 }
926
927 if (undo->last_page_no == undo->hdr_page_no) {
928function_exit:
929 if (trunc_here) {
930 mlog_write_ulint(undo_page + TRX_UNDO_PAGE_HDR
931 + TRX_UNDO_PAGE_FREE,
932 ulint(trunc_here - undo_page),
933 MLOG_2BYTES, &mtr);
934 }
935
936 mtr.commit();
937 return;
938 }
939
940 trx_undo_free_last_page(undo, &mtr);
941 mtr.commit();
942 }
943}
944
945/** Truncate the head of an undo log.
946NOTE that only whole pages are freed; the header page is not
947freed, but emptied, if all the records there are below the limit.
948@param[in,out] rseg rollback segment
949@param[in] hdr_page_no header page number
950@param[in] hdr_offset header offset on the page
951@param[in] limit first undo number to preserve
952(everything below the limit will be truncated) */
953void
954trx_undo_truncate_start(
955 trx_rseg_t* rseg,
956 ulint hdr_page_no,
957 ulint hdr_offset,
958 undo_no_t limit)
959{
960 page_t* undo_page;
961 trx_undo_rec_t* rec;
962 trx_undo_rec_t* last_rec;
963 ulint page_no;
964 mtr_t mtr;
965
966 ut_ad(mutex_own(&(rseg->mutex)));
967
968 if (!limit) {
969 return;
970 }
971loop:
972 mtr_start(&mtr);
973
974 if (!rseg->is_persistent()) {
975 mtr.set_log_mode(MTR_LOG_NO_REDO);
976 }
977
978 rec = trx_undo_get_first_rec(rseg->space, hdr_page_no, hdr_offset,
979 RW_X_LATCH, &mtr);
980 if (rec == NULL) {
981 /* Already empty */
982
983 mtr_commit(&mtr);
984
985 return;
986 }
987
988 undo_page = page_align(rec);
989
990 last_rec = trx_undo_page_get_last_rec(undo_page, hdr_page_no,
991 hdr_offset);
992 if (trx_undo_rec_get_undo_no(last_rec) >= limit) {
993
994 mtr_commit(&mtr);
995
996 return;
997 }
998
999 page_no = page_get_page_no(undo_page);
1000
1001 if (page_no == hdr_page_no) {
1002 uint16_t end = mach_read_from_2(hdr_offset + TRX_UNDO_NEXT_LOG
1003 + undo_page);
1004 if (end == 0) {
1005 end = mach_read_from_2(TRX_UNDO_PAGE_HDR
1006 + TRX_UNDO_PAGE_FREE
1007 + undo_page);
1008 }
1009
1010 mlog_write_ulint(undo_page + hdr_offset + TRX_UNDO_LOG_START,
1011 end, MLOG_2BYTES, &mtr);
1012 } else {
1013 trx_undo_free_page(rseg, true, hdr_page_no, page_no, &mtr);
1014 }
1015
1016 mtr_commit(&mtr);
1017
1018 goto loop;
1019}
1020
1021/** Frees an undo log segment which is not in the history list.
1022@param[in] undo undo log
1023@param[in] noredo whether the undo tablespace is redo logged */
1024static
1025void
1026trx_undo_seg_free(
1027 const trx_undo_t* undo,
1028 bool noredo)
1029{
1030 trx_rseg_t* rseg;
1031 fseg_header_t* file_seg;
1032 trx_rsegf_t* rseg_header;
1033 trx_usegf_t* seg_header;
1034 ibool finished;
1035 mtr_t mtr;
1036
1037 rseg = undo->rseg;
1038
1039 do {
1040
1041 mtr_start(&mtr);
1042
1043 if (noredo) {
1044 mtr.set_log_mode(MTR_LOG_NO_REDO);
1045 }
1046
1047 mutex_enter(&(rseg->mutex));
1048
1049 seg_header = trx_undo_page_get(page_id_t(undo->rseg->space->id,
1050 undo->hdr_page_no),
1051 &mtr)
1052 + TRX_UNDO_SEG_HDR;
1053
1054 file_seg = seg_header + TRX_UNDO_FSEG_HEADER;
1055
1056 finished = fseg_free_step(file_seg, false, &mtr);
1057
1058 if (finished) {
1059 /* Update the rseg header */
1060 rseg_header = trx_rsegf_get(
1061 rseg->space, rseg->page_no, &mtr);
1062 trx_rsegf_set_nth_undo(rseg_header, undo->id, FIL_NULL,
1063 &mtr);
1064
1065 MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_USED);
1066 }
1067
1068 mutex_exit(&(rseg->mutex));
1069 mtr_commit(&mtr);
1070 } while (!finished);
1071}
1072
1073/*========== UNDO LOG MEMORY COPY INITIALIZATION =====================*/
1074
1075/** Read an undo log when starting up the database.
1076@param[in,out] rseg rollback segment
1077@param[in] id rollback segment slot
1078@param[in] page_no undo log segment page number
1079@param[in,out] max_trx_id the largest observed transaction ID
1080@return size of the undo log in pages */
1081ulint
1082trx_undo_mem_create_at_db_start(trx_rseg_t* rseg, ulint id, ulint page_no,
1083 trx_id_t& max_trx_id)
1084{
1085 mtr_t mtr;
1086 XID xid;
1087
1088 ut_ad(id < TRX_RSEG_N_SLOTS);
1089
1090 mtr.start();
1091 const page_t* undo_page = trx_undo_page_get(
1092 page_id_t(rseg->space->id, page_no), &mtr);
1093 const ulint type = mach_read_from_2(
1094 TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_TYPE + undo_page);
1095 ut_ad(type == 0 || type == TRX_UNDO_INSERT || type == TRX_UNDO_UPDATE);
1096
1097 uint state = mach_read_from_2(TRX_UNDO_SEG_HDR + TRX_UNDO_STATE
1098 + undo_page);
1099 uint offset = mach_read_from_2(TRX_UNDO_SEG_HDR + TRX_UNDO_LAST_LOG
1100 + undo_page);
1101
1102 const trx_ulogf_t* undo_header = undo_page + offset;
1103
1104 /* Read X/Open XA transaction identification if it exists, or
1105 set it to NULL. */
1106
1107 if (undo_header[TRX_UNDO_XID_EXISTS]) {
1108 trx_undo_read_xid(undo_header, &xid);
1109 } else {
1110 xid.null();
1111 }
1112
1113 trx_id_t trx_id = mach_read_from_8(undo_header + TRX_UNDO_TRX_ID);
1114 if (trx_id > max_trx_id) {
1115 max_trx_id = trx_id;
1116 }
1117
1118 mutex_enter(&rseg->mutex);
1119 trx_undo_t* undo = trx_undo_mem_create(
1120 rseg, id, trx_id, &xid, page_no, offset);
1121 mutex_exit(&rseg->mutex);
1122
1123 undo->dict_operation = undo_header[TRX_UNDO_DICT_TRANS];
1124 undo->table_id = mach_read_from_8(undo_header + TRX_UNDO_TABLE_ID);
1125 undo->size = flst_get_len(TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST
1126 + undo_page);
1127
1128 if (UNIV_UNLIKELY(state == TRX_UNDO_TO_FREE)) {
1129 /* This is an old-format insert_undo log segment that
1130 is being freed. The page list is inconsistent. */
1131 ut_ad(type == TRX_UNDO_INSERT);
1132 state = TRX_UNDO_TO_PURGE;
1133 } else {
1134 if (state == TRX_UNDO_TO_PURGE
1135 || state == TRX_UNDO_CACHED) {
1136 trx_id_t id = mach_read_from_8(TRX_UNDO_TRX_NO
1137 + undo_header);
1138 if (id > max_trx_id) {
1139 max_trx_id = id;
1140 }
1141 }
1142
1143 fil_addr_t last_addr = flst_get_last(
1144 TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST + undo_page,
1145 &mtr);
1146
1147 undo->last_page_no = last_addr.page;
1148 undo->top_page_no = last_addr.page;
1149
1150 page_t* last_page = trx_undo_page_get(
1151 page_id_t(rseg->space->id, undo->last_page_no), &mtr);
1152
1153 if (const trx_undo_rec_t* rec = trx_undo_page_get_last_rec(
1154 last_page, page_no, offset)) {
1155 undo->top_offset = ulint(rec - last_page);
1156 undo->top_undo_no = trx_undo_rec_get_undo_no(rec);
1157 ut_ad(!undo->empty());
1158 } else {
1159 undo->top_undo_no = IB_ID_MAX;
1160 ut_ad(undo->empty());
1161 }
1162 }
1163
1164 undo->state = state;
1165
1166 if (state != TRX_UNDO_CACHED) {
1167 UT_LIST_ADD_LAST(type == TRX_UNDO_INSERT
1168 ? rseg->old_insert_list
1169 : rseg->undo_list, undo);
1170 } else {
1171 UT_LIST_ADD_LAST(rseg->undo_cached, undo);
1172 MONITOR_INC(MONITOR_NUM_UNDO_SLOT_CACHED);
1173 }
1174
1175 mtr.commit();
1176 return undo->size;
1177}
1178
1179/********************************************************************//**
1180Creates and initializes an undo log memory object.
1181@return own: the undo log memory object */
1182static
1183trx_undo_t*
1184trx_undo_mem_create(
1185/*================*/
1186 trx_rseg_t* rseg, /*!< in: rollback segment memory object */
1187 ulint id, /*!< in: slot index within rseg */
1188 trx_id_t trx_id, /*!< in: id of the trx for which the undo log
1189 is created */
1190 const XID* xid, /*!< in: X/Open transaction identification */
1191 ulint page_no,/*!< in: undo log header page number */
1192 ulint offset) /*!< in: undo log header byte offset on page */
1193{
1194 trx_undo_t* undo;
1195
1196 ut_ad(mutex_own(&(rseg->mutex)));
1197
1198 ut_a(id < TRX_RSEG_N_SLOTS);
1199
1200 undo = static_cast<trx_undo_t*>(ut_malloc_nokey(sizeof(*undo)));
1201
1202 if (undo == NULL) {
1203
1204 return(NULL);
1205 }
1206
1207 undo->id = id;
1208 undo->state = TRX_UNDO_ACTIVE;
1209 undo->trx_id = trx_id;
1210 undo->xid = *xid;
1211
1212 undo->dict_operation = FALSE;
1213
1214 undo->rseg = rseg;
1215
1216 undo->hdr_page_no = page_no;
1217 undo->hdr_offset = offset;
1218 undo->last_page_no = page_no;
1219 undo->size = 1;
1220
1221 undo->top_undo_no = IB_ID_MAX;
1222 undo->top_page_no = page_no;
1223 undo->guess_block = NULL;
1224 undo->withdraw_clock = 0;
1225 ut_ad(undo->empty());
1226
1227 return(undo);
1228}
1229
1230/********************************************************************//**
1231Initializes a cached undo log object for new use. */
1232static
1233void
1234trx_undo_mem_init_for_reuse(
1235/*========================*/
1236 trx_undo_t* undo, /*!< in: undo log to init */
1237 trx_id_t trx_id, /*!< in: id of the trx for which the undo log
1238 is created */
1239 const XID* xid, /*!< in: X/Open XA transaction identification*/
1240 ulint offset) /*!< in: undo log header byte offset on page */
1241{
1242 ut_ad(mutex_own(&((undo->rseg)->mutex)));
1243
1244 ut_a(undo->id < TRX_RSEG_N_SLOTS);
1245
1246 undo->state = TRX_UNDO_ACTIVE;
1247 undo->trx_id = trx_id;
1248 undo->xid = *xid;
1249
1250 undo->dict_operation = FALSE;
1251
1252 undo->hdr_offset = offset;
1253 undo->top_undo_no = IB_ID_MAX;
1254 ut_ad(undo->empty());
1255}
1256
1257/** Create an undo log.
1258@param[in,out] trx transaction
1259@param[in,out] rseg rollback segment
1260@param[out] undo undo log object
1261@param[out] err error code
1262@param[in,out] mtr mini-transaction
1263@return undo log block
1264@retval NULL on failure */
1265static MY_ATTRIBUTE((nonnull, warn_unused_result))
1266buf_block_t*
1267trx_undo_create(trx_t* trx, trx_rseg_t* rseg, trx_undo_t** undo,
1268 dberr_t* err, mtr_t* mtr)
1269{
1270 ulint id;
1271
1272 ut_ad(mutex_own(&(rseg->mutex)));
1273
1274 buf_block_t* block = trx_undo_seg_create(
1275 rseg->space,
1276 trx_rsegf_get(rseg->space, rseg->page_no, mtr), &id, err, mtr);
1277
1278 if (!block) {
1279 return NULL;
1280 }
1281
1282 rseg->curr_size++;
1283
1284 ulint offset = trx_undo_header_create(block->frame, trx->id, mtr);
1285
1286 trx_undo_header_add_space_for_xid(block->frame, block->frame + offset,
1287 mtr);
1288
1289 *undo = trx_undo_mem_create(rseg, id, trx->id, trx->xid,
1290 block->page.id.page_no(), offset);
1291 if (*undo == NULL) {
1292 *err = DB_OUT_OF_MEMORY;
1293 /* FIXME: this will not free the undo block to the file */
1294 return NULL;
1295 } else if (rseg != trx->rsegs.m_redo.rseg) {
1296 return block;
1297 }
1298
1299 switch (trx_get_dict_operation(trx)) {
1300 case TRX_DICT_OP_NONE:
1301 break;
1302 case TRX_DICT_OP_INDEX:
1303 /* Do not discard the table on recovery. */
1304 trx->table_id = 0;
1305 /* fall through */
1306 case TRX_DICT_OP_TABLE:
1307 (*undo)->table_id = trx->table_id;
1308 (*undo)->dict_operation = TRUE;
1309 mlog_write_ulint(block->frame + offset + TRX_UNDO_DICT_TRANS,
1310 TRUE, MLOG_1BYTE, mtr);
1311 mlog_write_ull(block->frame + offset + TRX_UNDO_TABLE_ID,
1312 trx->table_id, mtr);
1313 }
1314
1315 *err = DB_SUCCESS;
1316 return block;
1317}
1318
1319/*================ UNDO LOG ASSIGNMENT AND CLEANUP =====================*/
1320
1321/** Reuse a cached undo log block.
1322@param[in,out] trx transaction
1323@param[in,out] rseg rollback segment
1324@param[out] pundo the undo log memory object
1325@param[in,out] mtr mini-transaction
1326@return the undo log block
1327@retval NULL if none cached */
1328static
1329buf_block_t*
1330trx_undo_reuse_cached(trx_t* trx, trx_rseg_t* rseg, trx_undo_t** pundo,
1331 mtr_t* mtr)
1332{
1333 ut_ad(mutex_own(&rseg->mutex));
1334
1335 trx_undo_t* undo = UT_LIST_GET_FIRST(rseg->undo_cached);
1336 if (!undo) {
1337 return NULL;
1338 }
1339
1340 ut_ad(undo->size == 1);
1341 ut_ad(undo->id < TRX_RSEG_N_SLOTS);
1342
1343 buf_block_t* block = buf_page_get(page_id_t(undo->rseg->space->id,
1344 undo->hdr_page_no),
1345 univ_page_size, RW_X_LATCH, mtr);
1346 if (!block) {
1347 return NULL;
1348 }
1349
1350 buf_block_dbg_add_level(block, SYNC_TRX_UNDO_PAGE);
1351
1352 UT_LIST_REMOVE(rseg->undo_cached, undo);
1353 MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_CACHED);
1354
1355 *pundo = undo;
1356
1357 ulint offset = trx_undo_header_create(block->frame, trx->id, mtr);
1358
1359 trx_undo_header_add_space_for_xid(block->frame, block->frame + offset,
1360 mtr);
1361
1362 trx_undo_mem_init_for_reuse(undo, trx->id, trx->xid, offset);
1363
1364 if (rseg != trx->rsegs.m_redo.rseg) {
1365 return block;
1366 }
1367
1368 switch (trx_get_dict_operation(trx)) {
1369 case TRX_DICT_OP_NONE:
1370 return block;
1371 case TRX_DICT_OP_INDEX:
1372 /* Do not discard the table on recovery. */
1373 trx->table_id = 0;
1374 /* fall through */
1375 case TRX_DICT_OP_TABLE:
1376 undo->table_id = trx->table_id;
1377 undo->dict_operation = TRUE;
1378 mlog_write_ulint(block->frame + offset + TRX_UNDO_DICT_TRANS,
1379 TRUE, MLOG_1BYTE, mtr);
1380 mlog_write_ull(block->frame + offset + TRX_UNDO_TABLE_ID,
1381 trx->table_id, mtr);
1382 }
1383
1384 return block;
1385}
1386
1387/** Assign an undo log for a persistent transaction.
1388A new undo log is created or a cached undo log reused.
1389@param[in,out] trx transaction
1390@param[out] err error code
1391@param[in,out] mtr mini-transaction
1392@return the undo log block
1393@retval NULL on error */
1394buf_block_t*
1395trx_undo_assign(trx_t* trx, dberr_t* err, mtr_t* mtr)
1396{
1397 ut_ad(mtr->get_log_mode() == MTR_LOG_ALL);
1398
1399 trx_undo_t* undo = trx->rsegs.m_redo.undo;
1400
1401 if (undo) {
1402 return buf_page_get_gen(
1403 page_id_t(undo->rseg->space->id, undo->last_page_no),
1404 univ_page_size, RW_X_LATCH,
1405 buf_pool_is_obsolete(undo->withdraw_clock)
1406 ? NULL : undo->guess_block,
1407 BUF_GET, __FILE__, __LINE__, mtr, err);
1408 }
1409
1410 trx_rseg_t* rseg = trx->rsegs.m_redo.rseg;
1411
1412 mutex_enter(&rseg->mutex);
1413 buf_block_t* block = trx_undo_reuse_cached(
1414 trx, rseg, &trx->rsegs.m_redo.undo, mtr);
1415
1416 if (!block) {
1417 block = trx_undo_create(trx, rseg, &trx->rsegs.m_redo.undo,
1418 err, mtr);
1419 ut_ad(!block == (*err != DB_SUCCESS));
1420 if (!block) {
1421 goto func_exit;
1422 }
1423 } else {
1424 *err = DB_SUCCESS;
1425 }
1426
1427 UT_LIST_ADD_FIRST(rseg->undo_list, trx->rsegs.m_redo.undo);
1428
1429func_exit:
1430 mutex_exit(&rseg->mutex);
1431 return block;
1432}
1433
1434/** Assign an undo log for a transaction.
1435A new undo log is created or a cached undo log reused.
1436@param[in,out] trx transaction
1437@param[in] rseg rollback segment
1438@param[out] undo the undo log
1439@param[out] err error code
1440@param[in,out] mtr mini-transaction
1441@return the undo log block
1442@retval NULL on error */
1443buf_block_t*
1444trx_undo_assign_low(trx_t* trx, trx_rseg_t* rseg, trx_undo_t** undo,
1445 dberr_t* err, mtr_t* mtr)
1446{
1447 const bool is_temp = rseg == trx->rsegs.m_noredo.rseg;
1448
1449 ut_ad(rseg == trx->rsegs.m_redo.rseg
1450 || rseg == trx->rsegs.m_noredo.rseg);
1451 ut_ad(undo == (is_temp
1452 ? &trx->rsegs.m_noredo.undo
1453 : &trx->rsegs.m_redo.undo));
1454 ut_ad(mtr->get_log_mode()
1455 == (is_temp ? MTR_LOG_NO_REDO : MTR_LOG_ALL));
1456
1457 if (*undo) {
1458 return buf_page_get_gen(
1459 page_id_t(rseg->space->id, (*undo)->last_page_no),
1460 univ_page_size, RW_X_LATCH,
1461 buf_pool_is_obsolete((*undo)->withdraw_clock)
1462 ? NULL : (*undo)->guess_block,
1463 BUF_GET, __FILE__, __LINE__, mtr, err);
1464 }
1465
1466 DBUG_EXECUTE_IF(
1467 "ib_create_table_fail_too_many_trx",
1468 *err = DB_TOO_MANY_CONCURRENT_TRXS; return NULL;
1469 );
1470
1471 mutex_enter(&rseg->mutex);
1472
1473 buf_block_t* block = trx_undo_reuse_cached(trx, rseg, undo, mtr);
1474
1475 if (!block) {
1476 block = trx_undo_create(trx, rseg, undo, err, mtr);
1477 ut_ad(!block == (*err != DB_SUCCESS));
1478 if (!block) {
1479 goto func_exit;
1480 }
1481 } else {
1482 *err = DB_SUCCESS;
1483 }
1484
1485 UT_LIST_ADD_FIRST(rseg->undo_list, *undo);
1486
1487func_exit:
1488 mutex_exit(&rseg->mutex);
1489 return block;
1490}
1491
1492/******************************************************************//**
1493Sets the state of the undo log segment at a transaction finish.
1494@return undo log segment header page, x-latched */
1495page_t*
1496trx_undo_set_state_at_finish(
1497/*=========================*/
1498 trx_undo_t* undo, /*!< in: undo log memory copy */
1499 mtr_t* mtr) /*!< in: mtr */
1500{
1501 trx_usegf_t* seg_hdr;
1502 trx_upagef_t* page_hdr;
1503 page_t* undo_page;
1504 ulint state;
1505
1506 ut_a(undo->id < TRX_RSEG_N_SLOTS);
1507
1508 undo_page = trx_undo_page_get(
1509 page_id_t(undo->rseg->space->id, undo->hdr_page_no), mtr);
1510
1511 seg_hdr = undo_page + TRX_UNDO_SEG_HDR;
1512 page_hdr = undo_page + TRX_UNDO_PAGE_HDR;
1513
1514 if (undo->size == 1
1515 && mach_read_from_2(page_hdr + TRX_UNDO_PAGE_FREE)
1516 < TRX_UNDO_PAGE_REUSE_LIMIT) {
1517
1518 state = TRX_UNDO_CACHED;
1519 } else {
1520 state = TRX_UNDO_TO_PURGE;
1521 }
1522
1523 undo->state = state;
1524
1525 mlog_write_ulint(seg_hdr + TRX_UNDO_STATE, state, MLOG_2BYTES, mtr);
1526
1527 return(undo_page);
1528}
1529
1530/** Set the state of the undo log segment at a XA PREPARE or XA ROLLBACK.
1531@param[in,out] trx transaction
1532@param[in,out] undo undo log
1533@param[in] rollback false=XA PREPARE, true=XA ROLLBACK
1534@param[in,out] mtr mini-transaction
1535@return undo log segment header page, x-latched */
1536page_t*
1537trx_undo_set_state_at_prepare(
1538 trx_t* trx,
1539 trx_undo_t* undo,
1540 bool rollback,
1541 mtr_t* mtr)
1542{
1543 trx_usegf_t* seg_hdr;
1544 trx_ulogf_t* undo_header;
1545 page_t* undo_page;
1546 ulint offset;
1547
1548 ut_ad(trx && undo && mtr);
1549
1550 ut_a(undo->id < TRX_RSEG_N_SLOTS);
1551
1552 undo_page = trx_undo_page_get(
1553 page_id_t(undo->rseg->space->id, undo->hdr_page_no), mtr);
1554
1555 seg_hdr = undo_page + TRX_UNDO_SEG_HDR;
1556
1557 if (rollback) {
1558 ut_ad(undo->state == TRX_UNDO_PREPARED);
1559 mlog_write_ulint(seg_hdr + TRX_UNDO_STATE, TRX_UNDO_ACTIVE,
1560 MLOG_2BYTES, mtr);
1561 return(undo_page);
1562 }
1563
1564 /*------------------------------*/
1565 ut_ad(undo->state == TRX_UNDO_ACTIVE);
1566 undo->state = TRX_UNDO_PREPARED;
1567 undo->xid = *trx->xid;
1568 /*------------------------------*/
1569
1570 mlog_write_ulint(seg_hdr + TRX_UNDO_STATE, undo->state,
1571 MLOG_2BYTES, mtr);
1572
1573 offset = mach_read_from_2(seg_hdr + TRX_UNDO_LAST_LOG);
1574 undo_header = undo_page + offset;
1575
1576 mlog_write_ulint(undo_header + TRX_UNDO_XID_EXISTS,
1577 TRUE, MLOG_1BYTE, mtr);
1578
1579 trx_undo_write_xid(undo_header, &undo->xid, mtr);
1580
1581 return(undo_page);
1582}
1583
1584/** Free an old insert or temporary undo log after commit or rollback.
1585The information is not needed after a commit or rollback, therefore
1586the data can be discarded.
1587@param[in,out] undo undo log
1588@param[in] is_temp whether this is temporary undo log */
1589void
1590trx_undo_commit_cleanup(trx_undo_t* undo, bool is_temp)
1591{
1592 trx_rseg_t* rseg = undo->rseg;
1593 ut_ad(is_temp == !rseg->is_persistent());
1594 ut_ad(!is_temp || 0 == UT_LIST_GET_LEN(rseg->old_insert_list));
1595
1596 mutex_enter(&rseg->mutex);
1597
1598 UT_LIST_REMOVE(is_temp ? rseg->undo_list : rseg->old_insert_list,
1599 undo);
1600
1601 if (undo->state == TRX_UNDO_CACHED) {
1602 UT_LIST_ADD_FIRST(rseg->undo_cached, undo);
1603 MONITOR_INC(MONITOR_NUM_UNDO_SLOT_CACHED);
1604 } else {
1605 ut_ad(undo->state == TRX_UNDO_TO_PURGE);
1606
1607 /* Delete first the undo log segment in the file */
1608 mutex_exit(&rseg->mutex);
1609 trx_undo_seg_free(undo, true);
1610 mutex_enter(&rseg->mutex);
1611
1612 ut_ad(rseg->curr_size > undo->size);
1613 rseg->curr_size -= undo->size;
1614
1615 ut_free(undo);
1616 }
1617
1618 mutex_exit(&rseg->mutex);
1619}
1620
1621/** At shutdown, frees the undo logs of a transaction. */
1622void
1623trx_undo_free_at_shutdown(trx_t *trx)
1624{
1625 if (trx_undo_t*& undo = trx->rsegs.m_redo.undo) {
1626 switch (undo->state) {
1627 case TRX_UNDO_PREPARED:
1628 break;
1629 case TRX_UNDO_CACHED:
1630 case TRX_UNDO_TO_FREE:
1631 case TRX_UNDO_TO_PURGE:
1632 ut_ad(trx_state_eq(trx,
1633 TRX_STATE_COMMITTED_IN_MEMORY));
1634 /* fall through */
1635 case TRX_UNDO_ACTIVE:
1636 /* lock_trx_release_locks() assigns
1637 trx->state = TRX_STATE_COMMITTED_IN_MEMORY. */
1638 ut_a(!srv_was_started
1639 || srv_read_only_mode
1640 || srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO
1641 || srv_fast_shutdown);
1642 break;
1643 default:
1644 ut_error;
1645 }
1646
1647 UT_LIST_REMOVE(trx->rsegs.m_redo.rseg->undo_list, undo);
1648 ut_free(undo);
1649 undo = NULL;
1650 }
1651
1652 if (trx_undo_t*& undo = trx->rsegs.m_redo.old_insert) {
1653 switch (undo->state) {
1654 case TRX_UNDO_PREPARED:
1655 break;
1656 case TRX_UNDO_CACHED:
1657 case TRX_UNDO_TO_FREE:
1658 case TRX_UNDO_TO_PURGE:
1659 ut_ad(trx_state_eq(trx,
1660 TRX_STATE_COMMITTED_IN_MEMORY));
1661 /* fall through */
1662 case TRX_UNDO_ACTIVE:
1663 /* lock_trx_release_locks() assigns
1664 trx->state = TRX_STATE_COMMITTED_IN_MEMORY. */
1665 ut_a(!srv_was_started
1666 || srv_read_only_mode
1667 || srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO
1668 || srv_fast_shutdown);
1669 break;
1670 default:
1671 ut_error;
1672 }
1673
1674 UT_LIST_REMOVE(trx->rsegs.m_redo.rseg->old_insert_list, undo);
1675 ut_free(undo);
1676 undo = NULL;
1677 }
1678
1679 if (trx_undo_t*& undo = trx->rsegs.m_noredo.undo) {
1680 ut_a(undo->state == TRX_UNDO_PREPARED);
1681
1682 UT_LIST_REMOVE(trx->rsegs.m_noredo.rseg->undo_list, undo);
1683 ut_free(undo);
1684 undo = NULL;
1685 }
1686}
1687
1688/** Truncate UNDO tablespace, reinitialize header and rseg.
1689@param[in] undo_trunc UNDO tablespace handler
1690@return true if success else false. */
1691bool
1692trx_undo_truncate_tablespace(
1693 undo::Truncate* undo_trunc)
1694
1695{
1696 fil_space_t* space = fil_space_acquire(
1697 undo_trunc->get_marked_space_id());
1698 if (!space) return false;
1699
1700 /* Step-1: Truncate tablespace. */
1701 if (!fil_truncate_tablespace(
1702 space, SRV_UNDO_TABLESPACE_SIZE_IN_PAGES)) {
1703 space->release();
1704 return false;
1705 }
1706
1707 /* Step-2: Re-initialize tablespace header.
1708 Avoid REDO logging as we don't want to apply the action if server
1709 crashes. For fix-up we have UNDO-truncate-ddl-log. */
1710 mtr_t mtr;
1711 mtr_start(&mtr);
1712 mtr_set_log_mode(&mtr, MTR_LOG_NO_REDO);
1713 fsp_header_init(space, SRV_UNDO_TABLESPACE_SIZE_IN_PAGES, &mtr);
1714 mtr_commit(&mtr);
1715
1716 /* Step-3: Re-initialize rollback segment header that resides
1717 in truncated tablespaced. */
1718 mtr_start(&mtr);
1719 mtr_set_log_mode(&mtr, MTR_LOG_NO_REDO);
1720 mtr_x_lock(&space->latch, &mtr);
1721 buf_block_t* sys_header = trx_sysf_get(&mtr);
1722
1723 for (ulint i = 0; i < undo_trunc->rsegs_size(); ++i) {
1724 trx_rsegf_t* rseg_header;
1725
1726 trx_rseg_t* rseg = undo_trunc->get_ith_rseg(i);
1727
1728 rseg->page_no = trx_rseg_header_create(
1729 space, rseg->id, sys_header, &mtr);
1730
1731 rseg_header = trx_rsegf_get_new(space->id, rseg->page_no,
1732 &mtr);
1733
1734 /* Before re-initialization ensure that we free the existing
1735 structure. There can't be any active transactions. */
1736 ut_a(UT_LIST_GET_LEN(rseg->undo_list) == 0);
1737
1738 trx_undo_t* next_undo;
1739
1740 for (trx_undo_t* undo = UT_LIST_GET_FIRST(rseg->undo_cached);
1741 undo != NULL;
1742 undo = next_undo) {
1743
1744 next_undo = UT_LIST_GET_NEXT(undo_list, undo);
1745 UT_LIST_REMOVE(rseg->undo_cached, undo);
1746 MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_CACHED);
1747 ut_free(undo);
1748 }
1749
1750 UT_LIST_INIT(rseg->undo_list, &trx_undo_t::undo_list);
1751 UT_LIST_INIT(rseg->undo_cached, &trx_undo_t::undo_list);
1752
1753 /* Initialize the undo log lists according to the rseg header */
1754 rseg->curr_size = mtr_read_ulint(
1755 rseg_header + TRX_RSEG_HISTORY_SIZE, MLOG_4BYTES, &mtr)
1756 + 1;
1757
1758 ut_ad(rseg->curr_size == 1);
1759
1760 rseg->trx_ref_count = 0;
1761 rseg->last_page_no = FIL_NULL;
1762 rseg->last_offset = 0;
1763 rseg->last_commit = 0;
1764 rseg->needs_purge = false;
1765 }
1766 mtr_commit(&mtr);
1767 space->release();
1768
1769 return true;
1770}
1771