2204 lines
67 KiB
C++
2204 lines
67 KiB
C++
/* Copyright (c) 2018, 2021, Alibaba and/or its affiliates. All rights reserved.
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL/PolarDB-X Engine hereby grant you an
|
|
additional permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with
|
|
MySQL/PolarDB-X Engine.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License, version 2.0, for more details.
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
|
|
/** @file include/lizard0undo.h
|
|
Lizard transaction undo and purge types.
|
|
|
|
Created 2020-04-02 by Jianwei.zhao
|
|
*******************************************************/
|
|
|
|
#include "trx0rec.h"
|
|
#include "trx0undo.h"
|
|
#include "trx0rseg.h"
|
|
#include "page0types.h"
|
|
|
|
#include "sql_plugin_var.h"
|
|
#include "sql_error.h"
|
|
#include "sql_class.h"
|
|
|
|
#include "lizard0scn.h"
|
|
#include "lizard0sys.h"
|
|
#include "lizard0txn.h"
|
|
#include "lizard0undo.h"
|
|
#include "lizard0undo0types.h"
|
|
#include "lizard0mon.h"
|
|
#include "lizard0cleanout.h"
|
|
#include "lizard0row.h"
|
|
|
|
/**
|
|
SCN generation strategy:
|
|
|
|
1) Always assign txn undo log for every transaction.
|
|
|
|
2) All the records include temproary table, the undo log slot in the row point
|
|
to the same txn undo log header whatever the undo type.
|
|
|
|
3) The insert undo log didn't write the scn into the undo log header, since it
|
|
will purge directly after commit.
|
|
|
|
4) The temproary txn undo log scn number will be delayed written, it will be
|
|
ok since the vision of record didn't look up temporary txn undo log header.
|
|
|
|
...
|
|
|
|
*/
|
|
|
|
/**
|
|
The thread of SCN generation:
|
|
|
|
1) trx->state = TRX_PREPARED
|
|
|
|
2) hold rseg mutex
|
|
|
|
3) finish the txn undo log header
|
|
-- hold txn undo log header page X latch
|
|
|
|
4) generate SCN number and write into txn undo log header
|
|
|
|
5) cleanup the txn undo log header
|
|
-- hold rseg header page X latch
|
|
|
|
6) add rseg into purge queue
|
|
-- hold purge queue mutex
|
|
-- release mutex
|
|
|
|
7) release rseg mutex
|
|
|
|
8) mtr commit
|
|
-- release undo log header page X latch
|
|
-- release rseg header page X latch
|
|
|
|
9) commit in memory
|
|
|
|
...
|
|
*/
|
|
|
|
/**
|
|
Attention:
|
|
The transaction ordered by scn in history list only promise within a rollback
|
|
segment.
|
|
*/
|
|
|
|
#ifdef UNIV_PFS_MUTEX
|
|
/* Lizard undo retention start mutex PFS key */
|
|
mysql_pfs_key_t lizard_undo_retention_mutex_key;
|
|
#endif
|
|
|
|
namespace lizard {
|
|
|
|
/** The max percent of txn undo page that can be reused */
|
|
ulint txn_undo_page_reuse_max_percent = TXN_UNDO_PAGE_REUSE_MAX_PCT_DEF;
|
|
|
|
/**
|
|
Encode UBA into undo_ptr that need to copy into record
|
|
@param[in] undo addr
|
|
@param[out] undo ptr
|
|
*/
|
|
void undo_encode_undo_addr(const undo_addr_t &undo_addr, undo_ptr_t *undo_ptr) {
|
|
lizard_undo_addr_validation(&undo_addr, nullptr);
|
|
ulint rseg_id = undo::id2num(undo_addr.space_id);
|
|
/** Reserved UNDO_PTR didn't need encode, so assert here */
|
|
/** 1. assert temporary table undo ptr */
|
|
lizard_ut_ad(undo_addr.offset != UNDO_PTR_OFFSET_TEMP_TAB_REC);
|
|
|
|
*undo_ptr = (undo_ptr_t)(undo_addr.state) << UBA_POS_STATE |
|
|
(undo_ptr_t)rseg_id << UBA_POS_SPACE_ID |
|
|
(undo_ptr_t)(undo_addr.page_no) << UBA_POS_PAGE_NO |
|
|
undo_addr.offset;
|
|
}
|
|
|
|
/* Lizard transaction undo header operation */
|
|
/*-----------------------------------------------------------------------------*/
|
|
|
|
#if defined UNIV_DEBUG || defined LIZARD_DEBUG
|
|
|
|
/** Check the UBA validation */
|
|
bool undo_addr_validation(const undo_addr_t *undo_addr,
|
|
const dict_index_t *index) {
|
|
bool internal_dm_table = false;
|
|
if (index) {
|
|
internal_dm_table =
|
|
(my_strcasecmp(system_charset_info, index->table->name.m_name,
|
|
"mysql/innodb_dynamic_metadata") == 0
|
|
? true
|
|
: false);
|
|
}
|
|
|
|
if ((index && index->table->is_temporary())) {
|
|
ut_a(undo_addr->state == true);
|
|
ut_a(undo_addr->space_id == 0);
|
|
ut_a(undo_addr->page_no == 0);
|
|
ut_a(undo_addr->offset == UNDO_PTR_OFFSET_TEMP_TAB_REC);
|
|
} else if (internal_dm_table) {
|
|
ut_a(undo_addr->state == true);
|
|
ut_a(undo_addr->space_id == 0);
|
|
ut_a(undo_addr->page_no == 0);
|
|
ut_a(undo_addr->offset == UNDO_PTR_OFFSET_DYNAMIC_METADATA);
|
|
} else {
|
|
ut_a(fsp_is_txn_tablespace_by_id(undo_addr->space_id));
|
|
ut_a(undo_addr->page_no > 0);
|
|
/** TODO: offset must be align to TXN_UNDO_EXT */
|
|
ut_a(undo_addr->offset >= (TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
/**
|
|
Validate the page is undo page
|
|
|
|
@param[in] page undo page
|
|
@return true it's undo page
|
|
*/
|
|
bool trx_undo_page_validation(const page_t *page) {
|
|
const trx_upagef_t *page_hdr = nullptr;
|
|
page_type_t page_type;
|
|
ulint undo_type;
|
|
|
|
ut_a(page);
|
|
|
|
/** Valiate fil_page type */
|
|
page_type = fil_page_get_type(page);
|
|
if (page_type != FIL_PAGE_UNDO_LOG) return false;
|
|
|
|
/** Validate undo type */
|
|
page_hdr = page + TRX_UNDO_PAGE_HDR;
|
|
undo_type = mach_read_from_2(page_hdr + TRX_UNDO_PAGE_TYPE);
|
|
|
|
if (undo_type != TRX_UNDO_TXN && undo_type != TRX_UNDO_INSERT &&
|
|
undo_type != TRX_UNDO_UPDATE)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Confirm the consistent of scn, undo type, undo state. */
|
|
bool undo_scn_validation(const trx_undo_t *undo) {
|
|
commit_scn_t scn = undo->cmmt;
|
|
ulint type = undo->type;
|
|
ulint state = undo->state;
|
|
|
|
if (type == TRX_UNDO_INSERT) {
|
|
if (state == TRX_UNDO_CACHED || state == TRX_UNDO_TO_FREE) {
|
|
ut_a(commit_scn_state(scn) == SCN_STATE_INITIAL);
|
|
} else if (state == TRX_UNDO_ACTIVE || state == TRX_UNDO_PREPARED) {
|
|
ut_a(commit_scn_state(scn) == SCN_STATE_INITIAL);
|
|
} else {
|
|
ut_a(0);
|
|
}
|
|
} else if (type == TRX_UNDO_UPDATE) {
|
|
if (state == TRX_UNDO_CACHED || state == TRX_UNDO_TO_PURGE) {
|
|
/** The update undo log has put into history,
|
|
so commit scn must be valid */
|
|
ut_a(commit_scn_state(scn) == SCN_STATE_ALLOCATED);
|
|
} else if (state == TRX_UNDO_ACTIVE || state == TRX_UNDO_PREPARED) {
|
|
/** The transaction still be active or has been prepared, */
|
|
ut_a(commit_scn_state(scn) == SCN_STATE_INITIAL);
|
|
} else if (state == TRX_UNDO_TO_FREE) {
|
|
/** It's impossible to be FREE for update undo log */
|
|
ut_a(0);
|
|
} else {
|
|
ut_a(0);
|
|
}
|
|
} else if (type == TRX_UNDO_TXN) {
|
|
if (state == TRX_UNDO_CACHED || state == TRX_UNDO_TO_PURGE) {
|
|
/** The txn undo log has put into history,
|
|
so commit scn must be valid */
|
|
ut_a(commit_scn_state(scn) == SCN_STATE_ALLOCATED);
|
|
} else if (state == TRX_UNDO_ACTIVE || state == TRX_UNDO_PREPARED) {
|
|
/** The transaction still be active or has been prepared, */
|
|
ut_a(commit_scn_state(scn) == SCN_STATE_INITIAL);
|
|
} else if (state == TRX_UNDO_TO_FREE) {
|
|
/** It's impossible to be FREE for update undo log */
|
|
ut_a(0);
|
|
} else {
|
|
ut_a(0);
|
|
}
|
|
} else {
|
|
ut_a(0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** Comfirm the commit scn is uninited */
|
|
static bool trx_undo_hdr_scn_committed(trx_ulogf_t *log_hdr, mtr_t *mtr) {
|
|
commit_scn_t scn = trx_undo_hdr_read_scn(log_hdr, mtr);
|
|
if (commit_scn_state(scn) == SCN_STATE_ALLOCATED) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Confirm the UBA is valid in undo log header */
|
|
bool trx_undo_hdr_uba_validation(const trx_ulogf_t *log_hdr, mtr_t *mtr) {
|
|
undo_addr_t undo_addr;
|
|
undo_ptr_t undo_ptr = trx_undo_hdr_read_uba(log_hdr, mtr);
|
|
undo_decode_undo_ptr(undo_ptr, &undo_addr);
|
|
/**
|
|
Maybe the UBA in undo log header is fixed and predefined UBA
|
|
or a meaningful UBA.
|
|
*/
|
|
if (undo_ptr == UNDO_PTR_UNDO_HDR ||
|
|
(undo_addr.state == true &&
|
|
(fsp_is_txn_tablespace_by_id(undo_addr.space_id))))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Check if an update undo log has been marked as purged.
|
|
@param[in] rseg txn rseg
|
|
@param[in] page_size
|
|
@return true if purged */
|
|
bool txn_undo_log_has_purged(const trx_rseg_t *rseg,
|
|
const page_size_t &page_size) {
|
|
if (fsp_is_txn_tablespace_by_id(rseg->space_id)) {
|
|
ut_ad(!rseg->last_del_marks);
|
|
/* Txn rseg is considered to be purged */
|
|
return true;
|
|
}
|
|
|
|
page_t *page;
|
|
trx_ulogf_t *log_hdr;
|
|
ulint type, flag;
|
|
trx_id_t trx_id;
|
|
|
|
undo_addr_t undo_addr;
|
|
undo_ptr_t uba_ptr;
|
|
trx_id_t txn_trx_id;
|
|
ulint txn_state;
|
|
trx_ulogf_t *txn_hdr;
|
|
|
|
mtr_t mtr;
|
|
mtr_start(&mtr);
|
|
|
|
/* Get current undo log header */
|
|
page = trx_undo_page_get_s_latched(
|
|
page_id_t(rseg->space_id, rseg->last_page_no), page_size, &mtr);
|
|
|
|
log_hdr = page + rseg->last_offset;
|
|
type = mach_read_from_2(page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_TYPE);
|
|
flag = mach_read_from_1(log_hdr + TRX_UNDO_FLAGS);
|
|
trx_id = mach_read_from_8(log_hdr + TRX_UNDO_TRX_ID);
|
|
ut_ad(type == TRX_UNDO_UPDATE);
|
|
ut_ad(!(flag & TRX_UNDO_FLAG_TXN));
|
|
|
|
/* Get addr of the corresponding txn undo log header */
|
|
uba_ptr = trx_undo_hdr_read_uba(log_hdr, &mtr);
|
|
if (uba_ptr == UNDO_PTR_UNDO_HDR) goto no_txn;
|
|
|
|
/** The insert/update undo should be released first, otherwise
|
|
it will be deadlocked */
|
|
mtr_commit(&mtr);
|
|
|
|
undo_decode_undo_ptr(uba_ptr, &undo_addr);
|
|
ut_ad(undo_addr.state && fsp_is_txn_tablespace_by_id(undo_addr.space_id));
|
|
|
|
mtr_start(&mtr);
|
|
|
|
/* Get the txn undo log header */
|
|
txn_hdr = trx_undo_page_get_s_latched(
|
|
page_id_t(undo_addr.space_id, undo_addr.page_no),
|
|
univ_page_size, &mtr) +
|
|
undo_addr.offset;
|
|
|
|
txn_trx_id = mach_read_from_8(txn_hdr + TRX_UNDO_TRX_ID);
|
|
txn_state = mach_read_from_2(txn_hdr + TXN_UNDO_LOG_STATE);
|
|
|
|
no_txn:
|
|
mtr_commit(&mtr);
|
|
|
|
/* No txn, so it is a tempory rseg, no need to check. */
|
|
if (uba_ptr == UNDO_PTR_UNDO_HDR) return true;
|
|
|
|
/* State of the txn undo log should be PURGED if not reused yet. */
|
|
return (txn_trx_id != trx_id || txn_state == TXN_UNDO_LOG_PURGED);
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
Get txn undo state at trx finish.
|
|
|
|
@param[in] free_limit space left on txn undo page
|
|
@return TRX_UNDO_TO_PURGE or TRX_UNDO_CACHED
|
|
*/
|
|
ulint decide_txn_undo_state_at_finish(ulint free_limit) {
|
|
// 275 undo record + 100 safty margin.
|
|
// why 100 ? In trx_undo_header_create:
|
|
// ut_a(free + TRX_UNDO_LOG_GTID_HDR_SIZE < UNIV_PAGE_SIZE - 100);
|
|
static const ulint min_reserve = TXN_UNDO_LOG_EXT_HDR_SIZE + 100;
|
|
|
|
ulint reuse_limit = txn_undo_page_reuse_max_percent * UNIV_PAGE_SIZE / 100;
|
|
|
|
if (free_limit >= reuse_limit) {
|
|
return TRX_UNDO_TO_PURGE;
|
|
} else if (free_limit + min_reserve >= UNIV_PAGE_SIZE) {
|
|
return TRX_UNDO_TO_PURGE;
|
|
} else {
|
|
return TRX_UNDO_CACHED;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Initial the NULL value on SCN and UTC when create undo log header.
|
|
include all kinds of undo log header type.
|
|
The redo log logic is included in "MLOG_UNDO_HDR_CREATE";
|
|
|
|
@param[in] log_hdr undo log header
|
|
@param[in] mtr current mtr context
|
|
*/
|
|
void trx_undo_hdr_init_scn(trx_ulogf_t *log_hdr, mtr_t *mtr) {
|
|
ut_a(mtr && log_hdr);
|
|
|
|
/** Here must hold the SX/X lock on the page */
|
|
ut_ad(mtr_memo_contains_page_flagged(
|
|
mtr, log_hdr, MTR_MEMO_PAGE_SX_FIX | MTR_MEMO_PAGE_X_FIX));
|
|
|
|
/** Validate the undo page */
|
|
lizard_trx_undo_page_validation(page_align(log_hdr));
|
|
|
|
mach_write_to_8(log_hdr + TRX_UNDO_SCN, SCN_NULL);
|
|
mach_write_to_8(log_hdr + TRX_UNDO_UTC, UTC_NULL);
|
|
mach_write_to_8(log_hdr + TRX_UNDO_UBA, UNDO_PTR_NULL);
|
|
mach_write_to_8(log_hdr + TRX_UNDO_GCN, GCN_NULL);
|
|
}
|
|
|
|
/**
|
|
Write the scn and utc when commit.
|
|
Include the redo log
|
|
|
|
@param[in] log_hdr undo log header
|
|
@param[in] commit_scn commit scn number
|
|
@param[in] mtr current mtr context
|
|
*/
|
|
void trx_undo_hdr_write_scn(trx_ulogf_t *log_hdr, commit_scn_t &cmmt,
|
|
mtr_t *mtr) {
|
|
/** Here must hold the SX/X lock on the page */
|
|
ut_ad(mtr_memo_contains_page_flagged(
|
|
mtr, log_hdr, MTR_MEMO_PAGE_SX_FIX | MTR_MEMO_PAGE_X_FIX));
|
|
|
|
/** Validate the undo page */
|
|
lizard_trx_undo_page_validation(page_align(log_hdr));
|
|
|
|
mlog_write_ull(log_hdr + TRX_UNDO_SCN, cmmt.scn, mtr);
|
|
mlog_write_ull(log_hdr + TRX_UNDO_UTC, cmmt.utc, mtr);
|
|
mlog_write_ull(log_hdr + TRX_UNDO_GCN, cmmt.gcn, mtr);
|
|
}
|
|
|
|
/**
|
|
Read the scn and utc.
|
|
|
|
@param[in] log_hdr undo log header
|
|
@param[in] mtr current mtr context
|
|
*/
|
|
commit_scn_t trx_undo_hdr_read_scn(const trx_ulogf_t *log_hdr, mtr_t *mtr) {
|
|
/** Here must hold the S/SX/X lock on the page */
|
|
ut_ad(mtr_memo_contains_page_flagged(
|
|
mtr, log_hdr,
|
|
MTR_MEMO_PAGE_S_FIX | MTR_MEMO_PAGE_X_FIX | MTR_MEMO_PAGE_SX_FIX));
|
|
|
|
/** Validate the undo page */
|
|
lizard_trx_undo_page_validation(page_align(log_hdr));
|
|
|
|
commit_scn_t cmmt;
|
|
|
|
cmmt.scn = mach_read_from_8(log_hdr + TRX_UNDO_SCN);
|
|
cmmt.utc = mach_read_from_8(log_hdr + TRX_UNDO_UTC);
|
|
cmmt.gcn = mach_read_from_8(log_hdr + TRX_UNDO_GCN);
|
|
|
|
return cmmt;
|
|
}
|
|
|
|
/**
|
|
Read the scn, utc, gcn from prev image.
|
|
|
|
@param[in] log_hdr undo log header
|
|
@param[in] mtr current mtr context
|
|
*/
|
|
commit_scn_t txn_undo_hdr_read_prev_scn(const trx_ulogf_t *log_hdr,
|
|
mtr_t *mtr) {
|
|
/** Here must hold the S/SX/X lock on the page */
|
|
ut_ad(mtr_memo_contains_page_flagged(
|
|
mtr, log_hdr,
|
|
MTR_MEMO_PAGE_S_FIX | MTR_MEMO_PAGE_X_FIX | MTR_MEMO_PAGE_SX_FIX));
|
|
|
|
/** Validate the undo page */
|
|
lizard_trx_undo_page_validation(page_align(log_hdr));
|
|
|
|
commit_scn_t cmmt;
|
|
|
|
cmmt.scn = mach_read_from_8(log_hdr + TXN_UNDO_PREV_SCN);
|
|
cmmt.utc = mach_read_from_8(log_hdr + TXN_UNDO_PREV_UTC);
|
|
cmmt.gcn = mach_read_from_8(log_hdr + TXN_UNDO_PREV_GCN);
|
|
|
|
return cmmt;
|
|
}
|
|
|
|
/**
|
|
Add the space for the txn especially.
|
|
|
|
@param[in] undo_page undo log header page
|
|
@param[in] log_hdr undo log hdr
|
|
@param[in] mtr
|
|
*/
|
|
void trx_undo_hdr_add_space_for_txn(page_t *undo_page, trx_ulogf_t *log_hdr,
|
|
mtr_t *mtr) {
|
|
|
|
trx_upagef_t *page_hdr;
|
|
ulint free;
|
|
ulint new_free;
|
|
|
|
page_hdr = undo_page + TRX_UNDO_PAGE_HDR;
|
|
|
|
free = mach_read_from_2(page_hdr + TRX_UNDO_PAGE_FREE);
|
|
|
|
/* free is now the end offset of the old style undo log header */
|
|
ut_a(free == ((ulint)(log_hdr - undo_page) + TRX_UNDO_LOG_XA_HDR_SIZE));
|
|
|
|
new_free = free + (TXN_UNDO_LOG_EXT_HDR_SIZE - TRX_UNDO_LOG_XA_HDR_SIZE);
|
|
|
|
/* Add space for TXN extension after the header, update the free offset
|
|
fields on the undo log page and in the undo log header */
|
|
|
|
mlog_write_ulint(page_hdr + TRX_UNDO_PAGE_START, new_free, MLOG_2BYTES, mtr);
|
|
|
|
mlog_write_ulint(page_hdr + TRX_UNDO_PAGE_FREE, new_free, MLOG_2BYTES, mtr);
|
|
|
|
mlog_write_ulint(log_hdr + TRX_UNDO_LOG_START, new_free, MLOG_2BYTES, mtr);
|
|
}
|
|
/**
|
|
Init the txn extension information.
|
|
|
|
@param[in] undo undo memory struct
|
|
@param[in] undo_page undo log header page
|
|
@param[in] log_hdr undo log hdr
|
|
@param[in] prev_image prev scn/utc if the undo log header is reused
|
|
@param[in] mtr
|
|
*/
|
|
void trx_undo_hdr_init_for_txn(trx_undo_t *undo, page_t *undo_page,
|
|
trx_ulogf_t *log_hdr,
|
|
const commit_scn_t &prev_image, mtr_t *mtr) {
|
|
ut_ad(mach_read_from_2(undo_page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_TYPE) ==
|
|
TRX_UNDO_TXN);
|
|
|
|
/* Write the magic number */
|
|
mlog_write_ulint(log_hdr + TXN_UNDO_LOG_EXT_MAGIC, TXN_MAGIC_N, MLOG_4BYTES,
|
|
mtr);
|
|
|
|
assert_commit_scn_allocated(prev_image);
|
|
/* Write the prev scn */
|
|
mlog_write_ull(log_hdr + TXN_UNDO_PREV_SCN, prev_image.scn, mtr);
|
|
/* Write the prev utc */
|
|
mlog_write_ull(log_hdr + TXN_UNDO_PREV_UTC, prev_image.utc, mtr);
|
|
/* Write the prev gcn */
|
|
mlog_write_ull(log_hdr + TXN_UNDO_PREV_GCN, prev_image.gcn, mtr);
|
|
|
|
/* Write initial state */
|
|
txn_undo_set_state_at_init(log_hdr, mtr);
|
|
|
|
/* Write the txn undo extension flag */
|
|
mlog_write_ulint(log_hdr + TXN_UNDO_LOG_EXT_FLAG, 0, MLOG_1BYTE, mtr);
|
|
|
|
ut_a(undo->flag == 0);
|
|
|
|
undo->flag |= TRX_UNDO_FLAG_TXN;
|
|
|
|
/** Write the undo flag when create undo log header */
|
|
mlog_write_ulint(log_hdr + TRX_UNDO_FLAGS, undo->flag, MLOG_1BYTE, mtr);
|
|
|
|
/** Copy prev image into undo structure */
|
|
undo->prev_image = prev_image;
|
|
assert_commit_scn_allocated(undo->prev_image);
|
|
}
|
|
|
|
/**
|
|
Read UBA.
|
|
|
|
@param[in] log_hdr undo log header
|
|
@param[in] mtr current mtr context
|
|
*/
|
|
undo_ptr_t trx_undo_hdr_read_uba(const trx_ulogf_t *log_hdr, mtr_t *mtr) {
|
|
/** Here must hold the S/SX/X lock on the page */
|
|
ut_ad(mtr_memo_contains_page_flagged(
|
|
mtr, log_hdr,
|
|
MTR_MEMO_PAGE_S_FIX | MTR_MEMO_PAGE_X_FIX | MTR_MEMO_PAGE_SX_FIX));
|
|
|
|
/** Validate the undo page */
|
|
lizard_trx_undo_page_validation(page_align(log_hdr));
|
|
|
|
return mach_read_from_8(log_hdr + TRX_UNDO_UBA);
|
|
}
|
|
|
|
/**
|
|
Write the UBA address into undo log header
|
|
@param[in] undo log header
|
|
@param[in] UBA
|
|
@param[in] mtr
|
|
*/
|
|
void trx_undo_hdr_write_uba(trx_ulogf_t *log_hdr, const undo_addr_t &undo_addr,
|
|
mtr_t *mtr) {
|
|
/** Here must hold the SX/X lock on the page */
|
|
ut_ad(mtr_memo_contains_page_flagged(
|
|
mtr, log_hdr, MTR_MEMO_PAGE_SX_FIX | MTR_MEMO_PAGE_X_FIX));
|
|
|
|
undo_ptr_t undo_ptr;
|
|
undo_encode_undo_addr(undo_addr, &undo_ptr);
|
|
|
|
mlog_write_ull(log_hdr + TRX_UNDO_UBA, undo_ptr, mtr);
|
|
}
|
|
|
|
/**
|
|
Write the UBA address into undo log header
|
|
@param[in] undo log header
|
|
@param[in] trx
|
|
@param[in] mtr
|
|
*/
|
|
void trx_undo_hdr_write_uba(trx_ulogf_t *log_hdr, const trx_t *trx,
|
|
mtr_t *mtr) {
|
|
/** Here must hold the SX/X lock on the page */
|
|
ut_ad(mtr_memo_contains_page_flagged(
|
|
mtr, log_hdr, MTR_MEMO_PAGE_SX_FIX | MTR_MEMO_PAGE_X_FIX));
|
|
|
|
if (trx_is_txn_rseg_updated(trx)) {
|
|
assert_trx_undo_ptr_allocated(trx);
|
|
/** Modify the state as commit, then write into log header */
|
|
mlog_write_ull(log_hdr + TRX_UNDO_UBA,
|
|
trx->txn_desc.undo_ptr | (undo_ptr_t)1 << UBA_POS_STATE,
|
|
mtr);
|
|
} else {
|
|
/**
|
|
If it's temporary table, didn't have txn undo, but it will have
|
|
update/insert undo log header.
|
|
*/
|
|
mlog_write_ull(log_hdr + TRX_UNDO_UBA, UNDO_PTR_UNDO_HDR, mtr);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Read the txn undo log header extension information.
|
|
|
|
@param[in] undo page
|
|
@param[in] undo log header
|
|
@param[in] mtr
|
|
@param[out] txn_undo_hdr
|
|
*/
|
|
void trx_undo_hdr_read_txn(const page_t *undo_page,
|
|
const trx_ulogf_t *undo_header, mtr_t *mtr,
|
|
txn_undo_hdr_t *txn_undo_hdr) {
|
|
ulint type;
|
|
type = mtr_read_ulint(undo_page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_TYPE,
|
|
MLOG_2BYTES, mtr);
|
|
ut_a(type == TRX_UNDO_TXN);
|
|
|
|
auto flag = mtr_read_ulint(undo_header + TRX_UNDO_FLAGS, MLOG_1BYTE, mtr);
|
|
|
|
/** If in cleanout safe mode, */
|
|
ut_a((flag & TRX_UNDO_FLAG_TXN) != 0 || opt_cleanout_safe_mode);
|
|
|
|
/** read commit image in txn undo header */
|
|
txn_undo_hdr->image.scn = mach_read_from_8(undo_header + TRX_UNDO_SCN);
|
|
|
|
txn_undo_hdr->image.utc = mach_read_from_8(undo_header + TRX_UNDO_UTC);
|
|
|
|
txn_undo_hdr->image.gcn = mach_read_from_8(undo_header + TRX_UNDO_GCN);
|
|
|
|
txn_undo_hdr->undo_ptr = mach_read_from_8(undo_header + TRX_UNDO_UBA);
|
|
|
|
txn_undo_hdr->trx_id = mach_read_from_8(undo_header + TRX_UNDO_TRX_ID);
|
|
|
|
txn_undo_hdr->magic_n =
|
|
mtr_read_ulint(undo_header + TXN_UNDO_LOG_EXT_MAGIC, MLOG_4BYTES, mtr);
|
|
|
|
txn_undo_hdr->prev_image.scn =
|
|
mach_read_from_8(undo_header + TXN_UNDO_PREV_SCN);
|
|
|
|
txn_undo_hdr->prev_image.utc =
|
|
mach_read_from_8(undo_header + TXN_UNDO_PREV_UTC);
|
|
|
|
txn_undo_hdr->prev_image.gcn =
|
|
mach_read_from_8(undo_header + TXN_UNDO_PREV_GCN);
|
|
|
|
txn_undo_hdr->state = mtr_read_ulint(undo_header + TXN_UNDO_LOG_STATE,
|
|
MLOG_2BYTES, mtr);
|
|
|
|
txn_undo_hdr->ext_flag =
|
|
mtr_read_ulint(undo_header + TXN_UNDO_LOG_EXT_FLAG, MLOG_1BYTE, mtr);
|
|
|
|
ut_ad(txn_undo_hdr->magic_n == TXN_MAGIC_N && txn_undo_hdr->ext_flag == 0);
|
|
}
|
|
|
|
/* Lizard transaction rollback segment operation */
|
|
/*-----------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
Round-bin get the rollback segment from transaction tablespace
|
|
|
|
@retval rollback segment
|
|
*/
|
|
static trx_rseg_t *get_next_txn_rseg() {
|
|
static ulint rseg_counter = 0;
|
|
|
|
undo::Tablespace *undo_space;
|
|
trx_rseg_t *rseg = nullptr;
|
|
|
|
ulong n_rollback_segments = srv_rollback_segments;
|
|
/** Lizard : didn't support variable of rollback segment count */
|
|
ut_a(FSP_MAX_ROLLBACK_SEGMENTS == srv_rollback_segments);
|
|
|
|
ulint current = rseg_counter;
|
|
os_atomic_increment_ulint(&rseg_counter, 1);
|
|
|
|
/** Notes: didn't need undo::spaces->s_lock() */
|
|
ut_ad(txn_spaces.size() == FSP_IMPLICIT_TXN_TABLESPACES);
|
|
|
|
ulint target_undo_tablespaces = FSP_IMPLICIT_TXN_TABLESPACES;
|
|
while (rseg == nullptr) {
|
|
ulint window = current % (target_undo_tablespaces * n_rollback_segments);
|
|
ulint space_slot = window % target_undo_tablespaces;
|
|
ulint rseg_slot = window / target_undo_tablespaces;
|
|
current++;
|
|
|
|
undo_space = txn_spaces.at(space_slot);
|
|
ut_ad(undo_space->is_active());
|
|
|
|
rseg = undo_space->get_active(rseg_slot);
|
|
}
|
|
|
|
ut_ad(rseg);
|
|
ut_ad(rseg->trx_ref_count > 0);
|
|
return rseg;
|
|
}
|
|
|
|
/**
|
|
Always assign transaction rollback segment for trx
|
|
@param[in] trx
|
|
*/
|
|
void trx_assign_txn_rseg(trx_t *trx) {
|
|
ut_ad(trx->rsegs.m_txn.rseg == nullptr);
|
|
|
|
trx->rsegs.m_txn.rseg = srv_read_only_mode ? nullptr : get_next_txn_rseg();
|
|
}
|
|
|
|
/**
|
|
Whether the txn rollback segment has been assigned
|
|
@param[in] trx
|
|
*/
|
|
bool trx_is_txn_rseg_assigned(trx_t *trx) {
|
|
return trx->rsegs.m_txn.rseg != nullptr;
|
|
}
|
|
|
|
/**
|
|
Whether the txn undo log has modified.
|
|
*/
|
|
bool trx_is_txn_rseg_updated(const trx_t *trx) {
|
|
return trx->rsegs.m_txn.txn_undo != nullptr;
|
|
}
|
|
|
|
/**
|
|
Get undo log segment from free list
|
|
@param[in] trx transaction
|
|
@param[in] rseg rollback segment
|
|
@param[in] type undo type
|
|
@param[in] trx_id transaction id
|
|
@param[in] xid xid
|
|
@param[in/out] undo undo memory object
|
|
|
|
@retval DB_SUCCESS SUCCESS
|
|
*/
|
|
static dberr_t txn_undo_get_free(trx_t *trx, trx_rseg_t *rseg, ulint type,
|
|
trx_id_t trx_id, const XID *xid,
|
|
trx_undo_t **undo) {
|
|
page_t *undo_page = nullptr;
|
|
trx_rsegf_t *rseg_header;
|
|
trx_upagef_t *page_hdr;
|
|
trx_usegf_t *seg_hdr;
|
|
page_no_t page_no;
|
|
ulint offset;
|
|
ulint len;
|
|
ulint size;
|
|
flst_base_node_t *base;
|
|
fil_addr_t node_addr;
|
|
ulint seg_size;
|
|
ulint free_size;
|
|
undo_addr_t undo_addr;
|
|
commit_scn_t prev_image = COMMIT_SCN_LOST;
|
|
|
|
ulint slot_no = ULINT_UNDEFINED;
|
|
dberr_t err = DB_SUCCESS;
|
|
|
|
ut_ad(type == TRX_UNDO_TXN);
|
|
ut_ad(trx_is_txn_rseg_assigned(trx));
|
|
ut_ad(rseg == trx->rsegs.m_txn.rseg);
|
|
|
|
ut_ad(mutex_own(&rseg->mutex));
|
|
|
|
mtr_t mtr;
|
|
mtr.start();
|
|
|
|
/** Only transaction rollback segment have free list */
|
|
ut_ad(fsp_is_txn_tablespace_by_id(rseg->space_id));
|
|
|
|
/** Phase 1 : Find a free slot in rseg array */
|
|
rseg_header =
|
|
trx_rsegf_get(rseg->space_id, rseg->page_no, rseg->page_size, &mtr);
|
|
|
|
slot_no = trx_rsegf_undo_find_free(rseg_header, &mtr);
|
|
|
|
if (slot_no == ULINT_UNDEFINED) {
|
|
ib::error(ER_IB_MSG_1212)
|
|
<< "Cannot find a free slot for an txn undo log."
|
|
" You may have too many active transactions running concurrently."
|
|
" Please add more rollback segments or undo tablespaces.";
|
|
|
|
err = DB_TOO_MANY_CONCURRENT_TRXS;
|
|
*undo = nullptr;
|
|
goto func_exit;
|
|
}
|
|
|
|
/** Phase 2 : Find a undo log segment from free list */
|
|
base = rseg_header + TXN_RSEG_FREE_LIST;
|
|
len = flst_get_len(base);
|
|
size =
|
|
mtr_read_ulint(rseg_header + TXN_RSEG_FREE_LIST_SIZE, MLOG_4BYTES, &mtr);
|
|
|
|
/* txn undo log segment only have one page */
|
|
ut_a(len == size);
|
|
if (len == 0) {
|
|
*undo = nullptr;
|
|
goto func_exit;
|
|
}
|
|
|
|
node_addr = flst_get_first(base, &mtr);
|
|
|
|
/** The page node was used by free list */
|
|
ut_ad(node_addr.boffset == (TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE));
|
|
|
|
undo_page = trx_undo_page_get(page_id_t(rseg->space_id, node_addr.page),
|
|
rseg->page_size, &mtr);
|
|
|
|
page_no = page_get_page_no(undo_page);
|
|
page_hdr = undo_page + TRX_UNDO_PAGE_HDR;
|
|
seg_hdr = undo_page + TRX_UNDO_SEG_HDR;
|
|
|
|
seg_size = flst_get_len(seg_hdr + TRX_UNDO_PAGE_LIST);
|
|
ut_a(seg_size == 1);
|
|
|
|
/** Phase 3: Remove the node from free list. */
|
|
flst_remove(rseg_header + TXN_RSEG_FREE_LIST,
|
|
undo_page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE, &mtr);
|
|
free_size =
|
|
mtr_read_ulint(rseg_header + TXN_RSEG_FREE_LIST_SIZE, MLOG_4BYTES, &mtr);
|
|
|
|
mlog_write_ulint(rseg_header + TXN_RSEG_FREE_LIST_SIZE, free_size - seg_size,
|
|
MLOG_4BYTES, &mtr);
|
|
|
|
os_atomic_decrement_ulint(&lizard_sys->txn_undo_log_free_list_len, 1);
|
|
|
|
/** Phase 4 : Reinit the undo log segment header page */
|
|
trx_undo_page_init(undo_page, type, &mtr);
|
|
|
|
mlog_write_ulint(page_hdr + TRX_UNDO_PAGE_FREE,
|
|
TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE, MLOG_2BYTES, &mtr);
|
|
|
|
mlog_write_ulint(seg_hdr + TRX_UNDO_LAST_LOG, 0, MLOG_2BYTES, &mtr);
|
|
|
|
flst_init(seg_hdr + TRX_UNDO_PAGE_LIST, &mtr);
|
|
|
|
flst_add_last(seg_hdr + TRX_UNDO_PAGE_LIST, page_hdr + TRX_UNDO_PAGE_NODE,
|
|
&mtr);
|
|
|
|
ut_ad(mach_read_from_2(undo_page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_TYPE) ==
|
|
TRX_UNDO_TXN);
|
|
|
|
rseg->curr_size++;
|
|
|
|
offset = trx_undo_header_create(undo_page, trx_id, &prev_image, &mtr);
|
|
|
|
ut_ad(offset == TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE);
|
|
|
|
/** Lizard: add UBA into undo log header */
|
|
undo_addr = {rseg->space_id, page_no, offset, SCN_NULL, true, GCN_NULL};
|
|
|
|
/** Current undo log hdr is UBA */
|
|
lizard::trx_undo_hdr_write_uba(undo_page + offset, undo_addr, &mtr);
|
|
|
|
trx_undo_header_add_space_for_xid(undo_page, undo_page + offset, &mtr,
|
|
trx_undo_t::Gtid_storage::NONE);
|
|
|
|
trx_undo_hdr_add_space_for_txn(undo_page, undo_page + offset, &mtr);
|
|
|
|
/** Phase 5 : Set the undo log slot */
|
|
trx_rsegf_set_nth_undo(rseg_header, slot_no, page_no, &mtr);
|
|
|
|
/** Phase 6 : Create a memory object for txn undo */
|
|
*undo =
|
|
trx_undo_mem_create(rseg, slot_no, type, trx_id, xid, page_no, offset);
|
|
|
|
trx_undo_hdr_init_for_txn(*undo, undo_page, undo_page + offset, prev_image,
|
|
&mtr);
|
|
|
|
ut_ad((*undo)->flag == TRX_UNDO_FLAG_TXN);
|
|
|
|
assert_commit_scn_allocated((*undo)->prev_image);
|
|
|
|
if (*undo == NULL) {
|
|
err = DB_OUT_OF_MEMORY;
|
|
goto func_exit;
|
|
} else {
|
|
lizard_stats.txn_undo_log_free_list_get.inc();
|
|
}
|
|
|
|
func_exit:
|
|
mtr.commit();
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
Allocate a undo log segment for transaction from TXN space, it
|
|
only save the scn and trx state currently, so ignore other attributes.
|
|
|
|
Pls use trx_undo_assign_undo() for INSERT/UPDATE undo.
|
|
|
|
@param[in] trx
|
|
@param[in/out] undo_ptr
|
|
@param[in] TXN type
|
|
|
|
@retval DB_SUCCESS if assign successful
|
|
@retval DB_TOO_MANY_CONCURRENT_TRXS,
|
|
DB_OUT_OF_FILE_SPACE
|
|
DB_READ_ONLY
|
|
DB_OUT_OF_MEMORY
|
|
*/
|
|
static dberr_t txn_undo_assign_undo(trx_t *trx, txn_undo_ptr_t *undo_ptr,
|
|
ulint type) {
|
|
mtr_t mtr;
|
|
trx_rseg_t *rseg;
|
|
trx_undo_t *undo;
|
|
dberr_t err = DB_SUCCESS;
|
|
|
|
ut_ad(trx && type == TRX_UNDO_TXN);
|
|
ut_ad(trx_is_txn_rseg_assigned(trx));
|
|
ut_ad(undo_ptr == &(trx->rsegs.m_txn));
|
|
ut_ad(mutex_own(&(trx->undo_mutex)));
|
|
|
|
rseg = undo_ptr->rseg;
|
|
|
|
lizard_stats.txn_undo_log_request.inc();
|
|
|
|
mtr_start(&mtr);
|
|
|
|
mutex_enter(&rseg->mutex);
|
|
|
|
DBUG_EXECUTE_IF("ib_create_table_fail_too_many_trx",
|
|
err = DB_TOO_MANY_CONCURRENT_TRXS;
|
|
goto func_exit;);
|
|
undo =
|
|
#ifdef UNIV_DEBUG
|
|
srv_inject_too_many_concurrent_trxs
|
|
? nullptr
|
|
:
|
|
#endif
|
|
trx_undo_reuse_cached(trx, rseg, type, trx->id, trx->xid,
|
|
trx_undo_t::Gtid_storage::NONE, &mtr);
|
|
|
|
if (undo == nullptr) {
|
|
err = txn_undo_get_free(trx, rseg, type, trx->id, trx->xid, &undo);
|
|
|
|
if (err != DB_SUCCESS) {
|
|
goto func_exit;
|
|
}
|
|
}
|
|
|
|
if (undo == nullptr) {
|
|
err = trx_undo_create(trx, rseg, type, trx->id, trx->xid,
|
|
trx_undo_t::Gtid_storage::NONE, &undo, &mtr);
|
|
|
|
if (err != DB_SUCCESS) {
|
|
goto func_exit;
|
|
}
|
|
}
|
|
|
|
UT_LIST_ADD_FIRST(rseg->txn_undo_list, undo);
|
|
ut_ad(undo_ptr->txn_undo == nullptr);
|
|
undo_ptr->txn_undo = undo;
|
|
|
|
func_exit:
|
|
mutex_exit(&(rseg->mutex));
|
|
mtr_commit(&mtr);
|
|
|
|
return (err);
|
|
}
|
|
|
|
/**
|
|
Always assign a txn undo log for transaction.
|
|
|
|
@param[in] trx current transaction
|
|
|
|
@return DB_SUCCESS Success
|
|
*/
|
|
dberr_t trx_always_assign_txn_undo(trx_t *trx){
|
|
dberr_t err = DB_SUCCESS;
|
|
trx_undo_t *undo = nullptr;
|
|
txn_undo_ptr_t *undo_ptr = nullptr;
|
|
undo_addr_t undo_addr;
|
|
|
|
ut_ad(trx);
|
|
/** Txn rollback segment should have been allocated */
|
|
ut_ad(trx_is_txn_rseg_assigned(trx));
|
|
|
|
/** At least one of m_redo or m_noredo rollback segment has been allocated */
|
|
ut_ad(trx_is_rseg_assigned(trx));
|
|
|
|
ut_ad(mutex_own(&(trx->undo_mutex)));
|
|
|
|
undo_ptr = &trx->rsegs.m_txn;
|
|
ut_ad(undo_ptr);
|
|
|
|
if (undo_ptr->txn_undo == nullptr) {
|
|
/**
|
|
Update undo will allocated until prepared state for GTID persist,
|
|
But here we didn't allowed for txn undo.
|
|
*/
|
|
ut_ad(!(trx_state_eq(trx, TRX_STATE_PREPARED)));
|
|
assert_txn_desc_initial(trx);
|
|
err = txn_undo_assign_undo(trx, undo_ptr, TRX_UNDO_TXN);
|
|
undo = undo_ptr->txn_undo;
|
|
|
|
if (undo == nullptr) {
|
|
lizard_error(ER_LIZARD) << "Could not allocate transaction undo log";
|
|
ut_ad(err != DB_SUCCESS);
|
|
} else {
|
|
/** Only allocate log header, */
|
|
undo->empty = true;
|
|
|
|
undo_addr.state = false;
|
|
undo_addr.scn = SCN_NULL;
|
|
undo_addr.space_id = undo->space;
|
|
undo_addr.page_no = undo->hdr_page_no;
|
|
undo_addr.offset = undo->hdr_offset;
|
|
undo_addr.gcn = GCN_NULL;
|
|
|
|
undo_encode_undo_addr(undo_addr, &trx->txn_desc.undo_ptr);
|
|
|
|
assert_commit_scn_allocated(undo->prev_image);
|
|
trx->prev_image = undo->prev_image;
|
|
}
|
|
} else {
|
|
assert_trx_undo_ptr_allocated(trx);
|
|
assert_commit_scn_allocated(trx->prev_image);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
/*-----------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
Init the txn description as NULL initial value.
|
|
@param[in] trx current transaction
|
|
*/
|
|
void trx_init_txn_desc(trx_t *trx) { trx->txn_desc = TXN_DESC_NULL; }
|
|
|
|
/**
|
|
Assign a new commit scn for the transaction when commit
|
|
|
|
@param[in] trx current transaction
|
|
@param[in/out] cmmt_ptr Commit scn which was generated only once
|
|
@param[in] undo txn undo log
|
|
@param[in] undo page txn undo log header page
|
|
@param[in] offset txn undo log header offset
|
|
@param[in] mtr mini transaction
|
|
@param[out] serialised
|
|
|
|
@retval scn commit scn struture
|
|
*/
|
|
commit_scn_t trx_commit_scn(trx_t *trx, commit_scn_t *cmmt_ptr,
|
|
trx_undo_t *undo, page_t *undo_hdr_page,
|
|
ulint hdr_offset, bool *serialised, mtr_t *mtr) {
|
|
trx_usegf_t *seg_hdr;
|
|
trx_ulogf_t *undo_hdr;
|
|
commit_scn_t cmmt = COMMIT_SCN_NULL;
|
|
|
|
ut_ad(lizard_sys);
|
|
ut_ad(trx && undo && undo_hdr_page && mtr);
|
|
|
|
ut_ad((trx->rsegs.m_txn.rseg != nullptr &&
|
|
mutex_own(&trx->rsegs.m_txn.rseg->mutex)) ||
|
|
trx->rsegs.m_noredo.update_undo == undo);
|
|
/** Attention: Some transaction commit directly from ACTIVE */
|
|
|
|
/** TODO:
|
|
If it didn't have prepare state, then only flush redo log once when
|
|
commit, It maybe cause vision problem, other session has see the data,
|
|
but scn redo log is lost.
|
|
*/
|
|
ut_ad(trx_state_eq(trx, TRX_STATE_PREPARED) ||
|
|
trx_state_eq(trx, TRX_STATE_ACTIVE));
|
|
|
|
lizard_trx_undo_page_validation(undo_hdr_page);
|
|
|
|
/** Here we didn't hold trx_sys mutex */
|
|
ut_ad(!trx_sys_mutex_own());
|
|
|
|
ut_ad(!cmmt_ptr || commit_scn_state(*cmmt_ptr) == SCN_STATE_ALLOCATED);
|
|
|
|
/** Here must hold the X lock on the page */
|
|
ut_ad(mtr_memo_contains_page(mtr, undo_hdr_page, MTR_MEMO_PAGE_X_FIX));
|
|
|
|
seg_hdr = undo_hdr_page + TRX_UNDO_SEG_HDR;
|
|
ulint state = mach_read_from_2(seg_hdr + TRX_UNDO_STATE);
|
|
|
|
/** TXN undo log must be finished */
|
|
ut_a(state == TRX_UNDO_CACHED || state == TRX_UNDO_TO_PURGE);
|
|
|
|
/** Commit must be the last log hdr */
|
|
ut_ad(hdr_offset == mach_read_from_2(seg_hdr + TRX_UNDO_LAST_LOG));
|
|
|
|
undo_hdr = undo_hdr_page + hdr_offset;
|
|
ut_ad(!trx_undo_hdr_scn_committed(undo_hdr, mtr));
|
|
|
|
assert_lizard_min_safe_scn_valid();
|
|
|
|
/* Step 1: modify trx->scn */
|
|
if (cmmt_ptr == nullptr) {
|
|
lizard_sys_scn_mutex_enter();
|
|
|
|
DBUG_EXECUTE_IF("crash_before_gcn_commit",
|
|
ut_ad(trx->txn_desc.cmmt.gcn != GCN_NULL ? 0 : 1););
|
|
|
|
/** Generate a new scn */
|
|
std::pair<commit_scn_t, bool> cmmt_result =
|
|
lizard_sys->scn.new_commit_scn(trx->txn_desc.cmmt.gcn);
|
|
|
|
cmmt = cmmt_result.first;
|
|
ut_a(!cmmt_result.second);
|
|
|
|
assert_trx_scn_initial(trx);
|
|
/** We don't want to call **ut_time_system_us** within the scope
|
|
of the lizard_sys mutex protection. So we just only set
|
|
trx->txn_desc.scn.first here */
|
|
trx->txn_desc.cmmt.scn = cmmt.scn;
|
|
|
|
/** If a read only transaction (for example: start transaction read only),
|
|
temporary table can be also modified. It doesn't matter if purge_sys purges
|
|
them */
|
|
|
|
/** Revision:
|
|
Temp undo still need to purge/truncate, so delay it by adding into
|
|
serialisation list */
|
|
|
|
/** add to lizard_sys->serialisation_list_scn */
|
|
UT_LIST_ADD_LAST(lizard_sys->serialisation_list_scn, trx);
|
|
|
|
ut_ad(*serialised == false);
|
|
*serialised = true;
|
|
|
|
trx->txn_desc.cmmt.gcn = cmmt.gcn;
|
|
lizard_sys_scn_mutex_exit();
|
|
|
|
trx->txn_desc.cmmt.utc = cmmt.utc = ut_time_system_us();
|
|
|
|
} else {
|
|
assert_trx_scn_allocated(trx);
|
|
cmmt = *cmmt_ptr;
|
|
ut_ad(trx->txn_desc.cmmt.scn == cmmt.scn);
|
|
}
|
|
ut_ad(commit_scn_state(cmmt) == SCN_STATE_ALLOCATED);
|
|
|
|
/* Step 2: modify undo header. */
|
|
trx_undo_hdr_write_scn(undo_hdr, cmmt, mtr);
|
|
ut_ad(trx_undo_hdr_scn_committed(undo_hdr, mtr));
|
|
|
|
/* Step 3: modify undo->scn */
|
|
assert_undo_scn_initial(undo);
|
|
undo->cmmt = cmmt;
|
|
|
|
assert_lizard_min_safe_scn_valid();
|
|
|
|
return cmmt;
|
|
}
|
|
|
|
/**
|
|
Add the txn undo log header into history.
|
|
|
|
@param[in] trx transaction
|
|
@param[in/out] undo_ptr txn undo log structure
|
|
@param[in] undo_page txn undo log header page, x-latched
|
|
@param[in] update_rseg_history_len
|
|
if true: update rseg history
|
|
len else skip updating it.
|
|
@param[in] n_added_logs
|
|
number of logs added
|
|
@param[in] mtr
|
|
*/
|
|
static void trx_purge_add_txn_undo_to_history(trx_t *trx,
|
|
txn_undo_ptr_t *undo_ptr,
|
|
page_t *undo_page,
|
|
bool update_rseg_history_len,
|
|
ulint n_added_logs, mtr_t *mtr) {
|
|
trx_undo_t *undo;
|
|
trx_rseg_t *rseg;
|
|
trx_rsegf_t *rseg_header;
|
|
trx_ulogf_t *undo_header;
|
|
|
|
undo = undo_ptr->txn_undo;
|
|
rseg = undo->rseg;
|
|
ut_ad(rseg == undo_ptr->rseg);
|
|
|
|
rseg_header = trx_rsegf_get(undo->rseg->space_id, undo->rseg->page_no,
|
|
undo->rseg->page_size, mtr);
|
|
|
|
undo_header = undo_page + undo->hdr_offset;
|
|
|
|
lizard_trx_undo_hdr_uba_validation(undo_header, mtr);
|
|
|
|
if (undo->state != TRX_UNDO_CACHED) {
|
|
ulint hist_size;
|
|
#ifdef UNIV_DEBUG
|
|
trx_usegf_t *seg_header = undo_page + TRX_UNDO_SEG_HDR;
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
/* The undo log segment will not be reused */
|
|
|
|
if (UNIV_UNLIKELY(undo->id >= TRX_RSEG_N_SLOTS)) {
|
|
ib::fatal(ER_IB_MSG_1165) << "undo->id is " << undo->id;
|
|
}
|
|
|
|
trx_rsegf_set_nth_undo(rseg_header, undo->id, FIL_NULL, mtr);
|
|
|
|
MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_USED);
|
|
|
|
hist_size =
|
|
mtr_read_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE, MLOG_4BYTES, mtr);
|
|
|
|
ut_ad(undo->size == flst_get_len(seg_header + TRX_UNDO_PAGE_LIST));
|
|
|
|
/** Lizard: txn undo only has log header */
|
|
ut_a(undo->size == 1);
|
|
|
|
mlog_write_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE,
|
|
hist_size + undo->size, MLOG_4BYTES, mtr);
|
|
}
|
|
|
|
/** Here is my interpretation about the format of undo header page:
|
|
1. A undo header page can hold multiple undo headers, whose format can
|
|
be known in 'undo log header' in trx0undo.h
|
|
2. Only current transaction who uses the undo page can use **undo
|
|
log page hader**, the undo records from the transaction can be
|
|
placed in multiple pages. But the other pages are normal undo pages,
|
|
which only belong to the transaction.
|
|
3. A 'undo log header' represents a undo_t, only belongs to a trx.
|
|
4. The undo records of the other 'undo log header' can only be placed in
|
|
the undo page.
|
|
5. When added in history list, the TRX_UNDO_HISTORY_NODE are used to
|
|
form a linked history list.
|
|
*/
|
|
/* Add the log as the first in the history list */
|
|
flst_add_first(rseg_header + TRX_RSEG_HISTORY,
|
|
undo_header + TRX_UNDO_HISTORY_NODE, mtr);
|
|
|
|
if (update_rseg_history_len) {
|
|
os_atomic_increment_ulint(&trx_sys->rseg_history_len, n_added_logs);
|
|
srv_wake_purge_thread_if_not_active();
|
|
}
|
|
|
|
/* Update maximum transaction scn for this rollback segment. */
|
|
assert_trx_scn_allocated(trx);
|
|
mlog_write_ull(rseg_header + TRX_RSEG_MAX_TRX_SCN, trx->txn_desc.cmmt.scn,
|
|
mtr);
|
|
|
|
/* lizard: TRX_UNDO_TRX_NO is reserved */
|
|
//mlog_write_ull(undo_header + TRX_UNDO_TRX_NO, trx->no, mtr);
|
|
|
|
/* Write information about delete markings to the undo log header */
|
|
if (!undo->del_marks) {
|
|
mlog_write_ulint(undo_header + TRX_UNDO_DEL_MARKS, FALSE, MLOG_2BYTES, mtr);
|
|
} else {
|
|
/** Txn undo log didn't have any delete marked record to purge forever */
|
|
ut_a(0);
|
|
}
|
|
|
|
/* Lizard: txn undo didn't need gtid information */
|
|
|
|
/* Write GTID information if there. */
|
|
// trx_undo_gtid_write(trx, undo_header, undo, mtr);
|
|
|
|
if (rseg->last_page_no == FIL_NULL) {
|
|
rseg->last_page_no = undo->hdr_page_no;
|
|
rseg->last_offset = undo->hdr_offset;
|
|
rseg->last_del_marks = undo->del_marks;
|
|
|
|
/** trx->scn must be allocated */
|
|
assert_trx_scn_allocated(trx);
|
|
|
|
rseg->last_scn = trx->txn_desc.cmmt.scn;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Cleanup txn undo log segment when commit,
|
|
|
|
It will :
|
|
1) Add the UBA header into rseg->history
|
|
2) Reinit the rseg->slot as FIL_NULL
|
|
3) Destroy or reuse the undo mem object
|
|
|
|
@param[in] trx trx owning the txn undo log
|
|
@param[in/out] undo_ptr txn undo log structure
|
|
@param[in] undo_page txn undo log header page, x-latched
|
|
@param[in] update_rseg_history_len
|
|
if true: update rseg history
|
|
len else skip updating it.
|
|
@param[in] n_added_logs
|
|
number of logs added
|
|
@param[in] mtr
|
|
*/
|
|
|
|
void trx_txn_undo_cleanup(trx_t *trx, txn_undo_ptr_t *undo_ptr,
|
|
page_t *undo_page, bool update_rseg_history_len,
|
|
ulint n_added_logs, mtr_t *mtr) {
|
|
trx_rseg_t *rseg;
|
|
trx_undo_t *undo;
|
|
|
|
undo = undo_ptr->txn_undo;
|
|
rseg = undo_ptr->rseg;
|
|
|
|
ut_ad(mutex_own(&(rseg->mutex)));
|
|
assert_undo_scn_allocated(undo);
|
|
|
|
trx_purge_add_txn_undo_to_history(trx, undo_ptr, undo_page,
|
|
update_rseg_history_len, n_added_logs, mtr);
|
|
|
|
UT_LIST_REMOVE(rseg->txn_undo_list, undo);
|
|
|
|
undo_ptr->txn_undo = NULL;
|
|
|
|
if (undo->state == TRX_UNDO_CACHED) {
|
|
UT_LIST_ADD_FIRST(rseg->txn_undo_cached, undo);
|
|
|
|
MONITOR_INC(MONITOR_NUM_UNDO_SLOT_CACHED);
|
|
LIZARD_MONITOR_INC_TXN_CACHED(1);
|
|
} else {
|
|
ut_ad(undo->state == TRX_UNDO_TO_PURGE);
|
|
|
|
trx_undo_mem_free(undo);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Resurrect txn undo log segment,
|
|
Maybe the trx didn't have m_redo update/insert undo log.
|
|
|
|
There are three different state:
|
|
|
|
1) TXN_UNDO N INSERT_UNDO Y UPDATE_UNO N
|
|
: The transaction has committed, but rseg->slot of insert undo
|
|
didn't set FIL_NULL, since cleanup insert undo is in other mini
|
|
transaction;
|
|
But here it will not commit again, just cleanup.
|
|
|
|
2) TXN_UNDO Y UPDATE_UNDO N INSERT_UNDO N
|
|
: The transaction only allocate txn undo log header, then instance
|
|
crashed;
|
|
|
|
3) TXN_UNDO Y UPDATE_UNDO/INSERT_UNDO (one Y or two Y)
|
|
|
|
We didn't allowed only have UPDATE UNDO but didn't have txn undo;
|
|
Since the txn undo allocation is prior to undate undo;
|
|
|
|
*/
|
|
void trx_resurrect_txn(trx_t *trx, trx_undo_t *undo, trx_rseg_t *rseg) {
|
|
ut_ad(trx->rsegs.m_txn.rseg == nullptr);
|
|
ut_ad(undo->empty);
|
|
|
|
/** Already has update/insert undo */
|
|
if (trx->rsegs.m_redo.rseg != nullptr) {
|
|
ut_ad(undo->trx_id == trx->id);
|
|
ut_ad(trx->is_recovered);
|
|
if (trx->rsegs.m_redo.update_undo != nullptr &&
|
|
trx->state == TRX_STATE_COMMITTED_IN_MEMORY) {
|
|
assert_trx_scn_allocated(trx);
|
|
assert_trx_undo_ptr_initial(trx);
|
|
lizard_ut_ad(trx->txn_desc.cmmt == undo->cmmt);
|
|
} else {
|
|
assert_trx_scn_initial(trx);
|
|
}
|
|
} else {
|
|
/** It must be the case: MySQL crashed as soon as the txn undo is created.
|
|
Only temporary table will not create txn */
|
|
*trx->xid = undo->xid;
|
|
trx->id = undo->trx_id;
|
|
trx->is_recovered = true;
|
|
trx->ddl_operation = undo->dict_operation;
|
|
}
|
|
ut_ad(trx->rsegs.m_txn.txn_undo == nullptr);
|
|
rseg->trx_ref_count++;
|
|
trx->rsegs.m_txn.rseg = rseg;
|
|
trx->rsegs.m_txn.txn_undo = undo;
|
|
|
|
assert_commit_scn_allocated(undo->prev_image);
|
|
trx->prev_image = undo->prev_image;
|
|
|
|
/**
|
|
Currently it's impossible only have txn undo for normal transaction.
|
|
But if crashed just after allocated txn undo, here maybe possible.
|
|
*/
|
|
if (trx->rsegs.m_redo.rseg == nullptr) {
|
|
lizard_ut_ad(undo->state == TRX_UNDO_ACTIVE);
|
|
trx->state = TRX_STATE_ACTIVE;
|
|
|
|
/* A running transaction always has the number field inited to
|
|
TRX_ID_MAX */
|
|
|
|
//trx->no = TRX_ID_MAX;
|
|
|
|
assert_undo_scn_initial(undo);
|
|
assert_trx_scn_initial(trx);
|
|
assert_txn_desc_initial(trx);
|
|
|
|
} else {
|
|
/** trx state has been initialized */
|
|
ut_ad(!trx_state_eq(trx, TRX_STATE_NOT_STARTED));
|
|
if (trx->rsegs.m_redo.insert_undo != nullptr &&
|
|
trx->rsegs.m_redo.update_undo == nullptr) {
|
|
/** SCN info wasn't written in insert undo. */
|
|
if (trx->state == TRX_STATE_COMMITTED_IN_MEMORY) {
|
|
/** Since the insert undo didn't have valid scn number */
|
|
assert_undo_scn_allocated(undo);
|
|
trx->txn_desc.cmmt = undo->cmmt;
|
|
}
|
|
} else if (trx->rsegs.m_redo.update_undo != nullptr) {
|
|
/** Update undo scn must be equal with txn undo scn */
|
|
ut_ad(trx->rsegs.m_redo.update_undo->cmmt == undo->cmmt);
|
|
}
|
|
}
|
|
ut_ad(trx->txn_desc.cmmt == undo->cmmt);
|
|
}
|
|
|
|
/** Prepares a transaction for given rollback segment.
|
|
@return lsn_t: lsn assigned for commit of scheduled rollback segment */
|
|
lsn_t txn_prepare_low(
|
|
trx_t *trx, /*!< in/out: transaction */
|
|
txn_undo_ptr_t *undo_ptr, /*!< in/out: pointer to rollback
|
|
segment scheduled for prepare. */
|
|
mtr_t *mtr) {
|
|
ut_ad(mtr);
|
|
|
|
//trx_rseg_t *rseg = undo_ptr->rseg;
|
|
|
|
/* Change the undo log segment states from TRX_UNDO_ACTIVE to
|
|
TRX_UNDO_PREPARED: these modifications to the file data
|
|
structure define the transaction as prepared in the file-based
|
|
world, at the serialization point of lsn. */
|
|
|
|
//mutex_enter(&rseg->mutex);
|
|
|
|
ut_ad(undo_ptr->txn_undo);
|
|
/* It is not necessary to obtain trx->undo_mutex here
|
|
because only a single OS thread is allowed to do the
|
|
transaction prepare for this transaction. */
|
|
trx_undo_set_state_at_prepare(trx, undo_ptr->txn_undo, false, mtr);
|
|
|
|
//mutex_exit(&rseg->mutex);
|
|
|
|
/*--------------*/
|
|
/* This mtr commit makes the transaction prepared in
|
|
file-based world. */
|
|
|
|
// mtr_commit(&mtr);
|
|
/*--------------*/
|
|
|
|
/*
|
|
if (!noredo_logging) {
|
|
const lsn_t lsn = mtr.commit_lsn();
|
|
ut_ad(lsn > 0);
|
|
return lsn;
|
|
}
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Validate txn undo free list node and rseg free list
|
|
|
|
@param[in] rseg_hdr rollback segment header
|
|
@parma[in] undo_page undo page
|
|
@param[in] mtr mini transaction
|
|
*/
|
|
bool txn_undo_free_list_validate(trx_rsegf_t *rseg_hdr, page_t *undo_page,
|
|
mtr_t *mtr) {
|
|
trx_usegf_t *seg_hdr;
|
|
ulint len;
|
|
fil_addr_t addr;
|
|
|
|
seg_hdr = undo_page + TRX_UNDO_SEG_HDR;
|
|
|
|
/** Confirm the page list only include undo log header page */
|
|
len = flst_get_len(seg_hdr + TRX_UNDO_PAGE_LIST);
|
|
addr = flst_get_last(seg_hdr + TRX_UNDO_PAGE_LIST, mtr);
|
|
|
|
if (addr.page != page_get_page_no(undo_page) ||
|
|
addr.boffset != (TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE))
|
|
return false;
|
|
|
|
if (len != 1) return false;
|
|
|
|
/** Confirm the free list reuse PAGE_NODE of undo log header */
|
|
len = mtr_read_ulint(rseg_hdr + TXN_RSEG_FREE_LIST_SIZE, MLOG_4BYTES, mtr);
|
|
if (len != flst_get_len(rseg_hdr + TXN_RSEG_FREE_LIST)) return false;
|
|
|
|
addr = flst_get_first(rseg_hdr + TXN_RSEG_FREE_LIST, mtr);
|
|
|
|
if (addr.boffset != (TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Put the txn undo log segment into free list after purge all.
|
|
|
|
@param[in] rseg rollback segment
|
|
@param[in] hdr_addr txn log hdr address
|
|
*/
|
|
void txn_purge_segment_to_free_list(trx_rseg_t *rseg, fil_addr_t hdr_addr) {
|
|
mtr_t mtr;
|
|
trx_rsegf_t *rseg_hdr;
|
|
trx_ulogf_t *log_hdr;
|
|
trx_usegf_t *seg_hdr;
|
|
page_t *undo_page;
|
|
ulint seg_size;
|
|
ulint hist_size;
|
|
ulint free_size;
|
|
|
|
mtr_start(&mtr);
|
|
|
|
mutex_enter(&rseg->mutex);
|
|
rseg_hdr =
|
|
trx_rsegf_get(rseg->space_id, rseg->page_no, rseg->page_size, &mtr);
|
|
|
|
undo_page = trx_undo_page_get(page_id_t(rseg->space_id, hdr_addr.page),
|
|
rseg->page_size, &mtr);
|
|
|
|
seg_hdr = undo_page + TRX_UNDO_SEG_HDR;
|
|
log_hdr = undo_page + hdr_addr.boffset;
|
|
|
|
/** The page list always has only its self page */
|
|
seg_size = flst_get_len(seg_hdr + TRX_UNDO_PAGE_LIST);
|
|
ut_a(seg_size == 1);
|
|
|
|
/** Remove the undo log segment from history list */
|
|
trx_purge_remove_log_hdr(rseg_hdr, log_hdr, &mtr);
|
|
|
|
hist_size =
|
|
mtr_read_ulint(rseg_hdr + TRX_RSEG_HISTORY_SIZE, MLOG_4BYTES, &mtr);
|
|
|
|
ut_ad(hist_size >= seg_size);
|
|
|
|
mlog_write_ulint(rseg_hdr + TRX_RSEG_HISTORY_SIZE, hist_size - seg_size,
|
|
MLOG_4BYTES, &mtr);
|
|
|
|
ut_ad(rseg->curr_size >= seg_size);
|
|
|
|
/** Curr_size didn't include the free list undo log segment */
|
|
rseg->curr_size -= seg_size;
|
|
|
|
/** Add the undo log segment from history list */
|
|
free_size =
|
|
mtr_read_ulint(rseg_hdr + TXN_RSEG_FREE_LIST_SIZE, MLOG_4BYTES, &mtr);
|
|
|
|
mlog_write_ulint(rseg_hdr + TXN_RSEG_FREE_LIST_SIZE, free_size + seg_size,
|
|
MLOG_4BYTES, &mtr);
|
|
|
|
flst_add_first(rseg_hdr + TXN_RSEG_FREE_LIST,
|
|
undo_page + TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE, &mtr);
|
|
|
|
os_atomic_increment_ulint(&lizard_sys->txn_undo_log_free_list_len, 1);
|
|
|
|
lizard_txn_undo_free_list_validate(rseg_hdr, undo_page, &mtr);
|
|
|
|
mutex_exit(&(rseg->mutex));
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
lizard_stats.txn_undo_log_free_list_put.inc();
|
|
}
|
|
|
|
/**
|
|
Write the scn into the buffer
|
|
@param[in/out] ptr buffer
|
|
@param[in] txn_desc txn description
|
|
*/
|
|
void trx_write_scn(byte *ptr, const txn_desc_t *txn_desc) {
|
|
ut_ad(ptr && txn_desc);
|
|
assert_undo_ptr_allocated(txn_desc->undo_ptr);
|
|
trx_write_scn(ptr, txn_desc->cmmt.scn);
|
|
}
|
|
|
|
/**
|
|
Write the scn into the buffer
|
|
@param[in/out] ptr buffer
|
|
@param[in] scn scn id
|
|
*/
|
|
void trx_write_scn(byte *ptr, scn_id_t scn) {
|
|
ut_ad(ptr);
|
|
mach_write_to_8(ptr, scn);
|
|
}
|
|
|
|
/**
|
|
Write the UBA into the buffer
|
|
@param[in/out] ptr buffer
|
|
@param[in] txn_desc txn description
|
|
*/
|
|
void trx_write_undo_ptr(byte *ptr, const txn_desc_t *txn_desc) {
|
|
ut_ad(ptr && txn_desc);
|
|
assert_undo_ptr_allocated(txn_desc->undo_ptr);
|
|
trx_write_undo_ptr(ptr, txn_desc->undo_ptr);
|
|
}
|
|
|
|
/**
|
|
Write the UBA into the buffer
|
|
@param[in/out] ptr buffer
|
|
@param[in] undo_ptr UBA
|
|
*/
|
|
void trx_write_undo_ptr(byte *ptr, undo_ptr_t undo_ptr) {
|
|
ut_ad(ptr);
|
|
mach_write_to_8(ptr, undo_ptr);
|
|
}
|
|
|
|
/**
|
|
Write the gcn into the buffer
|
|
@param[in/out] ptr buffer
|
|
@param[in] txn_desc txn description
|
|
*/
|
|
void trx_write_gcn(byte *ptr, const txn_desc_t *txn_desc) {
|
|
ut_ad(ptr && txn_desc);
|
|
assert_undo_ptr_allocated(txn_desc->undo_ptr);
|
|
trx_write_gcn(ptr, txn_desc->cmmt.gcn);
|
|
}
|
|
|
|
/**
|
|
Write the gcn into the buffer
|
|
@param[in/out] ptr buffer
|
|
@param[in] scn scn id
|
|
*/
|
|
void trx_write_gcn(byte *ptr, gcn_t gcn) {
|
|
ut_ad(ptr);
|
|
mach_write_to_8(ptr, gcn);
|
|
}
|
|
|
|
/**
|
|
Read the scn
|
|
@param[in] ptr buffer
|
|
|
|
@return scn_id_t scn
|
|
*/
|
|
scn_id_t trx_read_scn(const byte *ptr) {
|
|
ut_ad(ptr);
|
|
return mach_read_from_8(ptr);
|
|
}
|
|
|
|
/**
|
|
Read the UBA
|
|
@param[in] ptr buffer
|
|
|
|
@return undo_ptr_t undo_ptr
|
|
*/
|
|
undo_ptr_t trx_read_undo_ptr(const byte *ptr) {
|
|
ut_ad(ptr);
|
|
return mach_read_from_8(ptr);
|
|
}
|
|
|
|
/**
|
|
Read the gcn
|
|
@param[in] ptr buffer
|
|
|
|
@return scn_id_t scn
|
|
*/
|
|
gcn_t trx_read_gcn(const byte *ptr) {
|
|
ut_ad(ptr);
|
|
return mach_read_from_8(ptr);
|
|
}
|
|
|
|
/**
|
|
Decode the undo_ptr into UBA
|
|
@param[in] undo ptr
|
|
@param[out] undo addr
|
|
*/
|
|
void undo_decode_undo_ptr(const undo_ptr_t uba, undo_addr_t *undo_addr) {
|
|
ulint rseg_id;
|
|
undo_ptr_t undo_ptr = uba;
|
|
ut_ad(undo_addr);
|
|
|
|
undo_addr->offset = (ulint)undo_ptr & 0xFFFF;
|
|
undo_ptr >>= UBA_WIDTH_OFSET;
|
|
undo_addr->page_no = (ulint)undo_ptr & 0xFFFFFFFF;
|
|
undo_ptr >>= UBA_WIDTH_PAGE_NO;
|
|
rseg_id = (ulint)undo_ptr & 0x7F;
|
|
|
|
/* Confirm the reserved bits */
|
|
ut_ad(((ulint)undo_ptr & 0x7f80) == 0);
|
|
|
|
undo_ptr >>= (UBA_WIDTH_SPACE_ID + UBA_WIDTH_UNUSED);
|
|
undo_addr->state = (bool)undo_ptr;
|
|
|
|
/**
|
|
It should not be trx_sys tablespace for normal table except
|
|
of temporary table/LOG_DDL/DYNAMIC_METADATA/DDL in-process table */
|
|
|
|
/**
|
|
Revision:
|
|
We give a fixed UBA in undo log header if didn't allocate txn undo
|
|
for temporary table.
|
|
*/
|
|
if (rseg_id == 0) {
|
|
lizard_ut_ad(undo_addr->offset == UNDO_PTR_OFFSET_TEMP_TAB_REC ||
|
|
undo_addr->offset == UNDO_PTR_OFFSET_DYNAMIC_METADATA ||
|
|
undo_addr->offset == UNDO_PTR_OFFSET_LOG_DDL ||
|
|
undo_addr->offset == UNDO_PTR_OFFSET_UNDO_HDR);
|
|
}
|
|
/** It's always redo txn undo log */
|
|
undo_addr->space_id = trx_rseg_id_to_space_id(rseg_id, false);
|
|
}
|
|
|
|
/**
|
|
Try to lookup the real scn of given records. Address directly to the
|
|
corresponding txn undo header by UBA.
|
|
|
|
@param[in/out] txn_rec txn info of the records.
|
|
@param[out] txn_lookup txn lookup result, nullptr if don't care.
|
|
|
|
@return bool whether corresponding trx is active.
|
|
*/
|
|
static bool txn_undo_hdr_lookup_func(txn_rec_t *txn_rec,
|
|
txn_lookup_t *txn_lookup, mtr_t *txn_mtr) {
|
|
undo_addr_t undo_addr;
|
|
page_t *undo_page;
|
|
ulint fil_type;
|
|
ulint undo_page_start;
|
|
trx_upagef_t *page_hdr;
|
|
ulint undo_page_type;
|
|
ulint real_trx_state;
|
|
trx_id_t real_trx_id;
|
|
trx_usegf_t *seg_hdr;
|
|
trx_ulogf_t *undo_hdr;
|
|
txn_undo_hdr_t txn_undo_hdr;
|
|
ulint hdr_flag;
|
|
bool have_mtr = false;
|
|
mtr_t temp_mtr;
|
|
mtr_t *mtr;
|
|
|
|
have_mtr = (txn_mtr != nullptr);
|
|
|
|
mtr = have_mtr ? txn_mtr : &temp_mtr;
|
|
|
|
ut_ad(mtr);
|
|
|
|
/** ----------------------------------------------------------*/
|
|
/** Phase 1: Read the undo header page */
|
|
undo_decode_undo_ptr(txn_rec->undo_ptr, &undo_addr);
|
|
ut_ad(undo_addr.offset <= UNIV_PAGE_SIZE_MAX);
|
|
|
|
const page_id_t page_id(undo_addr.space_id, undo_addr.page_no);
|
|
|
|
if (!have_mtr) mtr_start(mtr);
|
|
|
|
/** Undo tablespace always univ_page_size */
|
|
undo_page = trx_undo_page_get_s_latched(page_id, univ_page_size, mtr);
|
|
|
|
/** transaction tablespace didn't allowed to be truncated */
|
|
ut_a(undo_page);
|
|
|
|
/** ----------------------------------------------------------*/
|
|
/** Phase 2: Judge the fil page */
|
|
fil_type = fil_page_get_type(undo_page);
|
|
/** The type of undo log segment must be FIL_PAGE_UNDO_LOG */
|
|
ut_a(fil_type == FIL_PAGE_UNDO_LOG);
|
|
|
|
/** ----------------------------------------------------------*/
|
|
/** Phase 3: judge whether it's undo log header or undo log data */
|
|
page_hdr = undo_page + TRX_UNDO_PAGE_HDR;
|
|
undo_page_start = mach_read_from_2(page_hdr + TRX_UNDO_PAGE_START);
|
|
|
|
/** If the undo record start from undo segment header, it's normal
|
|
undo log data page.
|
|
*/
|
|
ut_a(undo_page_start != (TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE));
|
|
|
|
/** ----------------------------------------------------------*/
|
|
/** Phase 4: judge whether it's txn undo */
|
|
undo_page_type = mach_read_from_2(page_hdr + TRX_UNDO_PAGE_TYPE);
|
|
|
|
ut_a(undo_page_type == TRX_UNDO_TXN);
|
|
|
|
/** ----------------------------------------------------------*/
|
|
/** Phase 5: check the undo segment state */
|
|
seg_hdr = undo_page + TRX_UNDO_SEG_HDR;
|
|
real_trx_state = mach_read_from_2(seg_hdr + TRX_UNDO_STATE);
|
|
|
|
/** real_trx_state should only be the following states */
|
|
ut_a(real_trx_state == TRX_UNDO_ACTIVE ||
|
|
real_trx_state == TRX_UNDO_CACHED ||
|
|
real_trx_state == TRX_UNDO_PREPARED ||
|
|
real_trx_state == TRX_UNDO_TO_PURGE);
|
|
|
|
/** ----------------------------------------------------------*/
|
|
/** Phase 6: The offset (minus TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE)
|
|
is a fixed multiple of TRX_UNDO_LOG_GTID_HDR_SIZE */
|
|
lizard_ut_ad(undo_addr.offset >= TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE);
|
|
lizard_ut_ad((undo_addr.offset
|
|
- (TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE))
|
|
% TRX_UNDO_LOG_GTID_HDR_SIZE == 0);
|
|
|
|
/** ----------------------------------------------------------*/
|
|
/** Phase 7: Check the flag in undo hdr, should be TRX_UNDO_FLAG_TXN,
|
|
unless it's in cleanout_safe_mode. */
|
|
undo_hdr = undo_page + undo_addr.offset;
|
|
hdr_flag = mtr_read_ulint(undo_hdr + TRX_UNDO_FLAGS, MLOG_1BYTE, mtr);
|
|
if (!(hdr_flag & TRX_UNDO_FLAG_TXN)) {
|
|
goto undo_corrupted;
|
|
}
|
|
|
|
/** ----------------------------------------------------------*/
|
|
/** Phase 8: check the txn extension fields in txn undo header */
|
|
trx_undo_hdr_read_txn(undo_page, undo_hdr, mtr, &txn_undo_hdr);
|
|
if (txn_undo_hdr.magic_n != TXN_MAGIC_N) {
|
|
/** The header might be raw */
|
|
lizard_stats.txn_undo_lost_magic_number_wrong.inc();
|
|
goto undo_corrupted;
|
|
}
|
|
|
|
/** NOTES: If the extent flag is used, there might be some records's flag
|
|
that is equal to 0, and there also might be other records's flag that's not
|
|
equal to 0 at the same time. */
|
|
if (txn_undo_hdr.ext_flag != 0) {
|
|
/** The header might be raw */
|
|
lizard_stats.txn_undo_lost_ext_flag_wrong.inc();
|
|
goto undo_corrupted;
|
|
}
|
|
|
|
/** ----------------------------------------------------------*/
|
|
/** Phase 9: check the trx_id in txn undo header */
|
|
real_trx_id = txn_undo_hdr.trx_id;
|
|
if (real_trx_id != txn_rec->trx_id) {
|
|
lizard_stats.txn_undo_lost_trx_id_mismatch.inc();
|
|
goto undo_reuse;
|
|
}
|
|
|
|
/** Revision:
|
|
We don't check txn_undo_lost_page_offset_overflow again, because
|
|
it's a normal case: old UBAs point at the page that was reused,
|
|
but the remain txn hdrs might still be valid. */
|
|
|
|
/** ----------------------------------------------------------*/
|
|
/** Phase 10: Here the txn header is the exactly header belongs to the
|
|
record. Then, we get txn state in txn undo header to determine what's
|
|
the real state of the transaction. */
|
|
if (txn_undo_hdr.state == TXN_UNDO_LOG_ACTIVE) {
|
|
lizard_ut_ad(mach_read_from_2(seg_hdr + TRX_UNDO_LAST_LOG)
|
|
== undo_addr.offset);
|
|
lizard_ut_ad(real_trx_state == TRX_UNDO_ACTIVE ||
|
|
real_trx_state == TRX_UNDO_PREPARED);
|
|
goto still_active;
|
|
} else if (txn_undo_hdr.state == TXN_UNDO_LOG_COMMITED) {
|
|
goto already_commit;
|
|
} else {
|
|
lizard_ut_ad(txn_undo_hdr.state == TXN_UNDO_LOG_PURGED);
|
|
goto undo_purged;
|
|
}
|
|
|
|
still_active:
|
|
assert_commit_scn_initial(txn_undo_hdr.image);
|
|
txn_lookup_t_set(txn_lookup, txn_undo_hdr, txn_undo_hdr.image,
|
|
txn_state_t::TXN_STATE_ACTIVE);
|
|
if (!have_mtr) mtr_commit(mtr);
|
|
return true;
|
|
|
|
already_commit:
|
|
assert_commit_scn_allocated(txn_undo_hdr.image);
|
|
txn_rec->scn = txn_undo_hdr.image.scn;
|
|
txn_rec->gcn = txn_undo_hdr.image.gcn;
|
|
lizard_undo_ptr_set_commit(&txn_rec->undo_ptr);
|
|
txn_lookup_t_set(txn_lookup, txn_undo_hdr, txn_undo_hdr.image,
|
|
txn_state_t::TXN_STATE_COMMITTED);
|
|
if (!have_mtr) mtr_commit(mtr);
|
|
return false;
|
|
|
|
undo_purged:
|
|
assert_commit_scn_allocated(txn_undo_hdr.image);
|
|
txn_rec->scn = txn_undo_hdr.image.scn;
|
|
txn_rec->gcn = txn_undo_hdr.image.gcn;
|
|
lizard_undo_ptr_set_commit(&txn_rec->undo_ptr);
|
|
txn_lookup_t_set(txn_lookup, txn_undo_hdr, txn_undo_hdr.image,
|
|
txn_state_t::TXN_STATE_PURGED);
|
|
if (!have_mtr) mtr_commit(mtr);
|
|
return false;
|
|
|
|
undo_reuse:
|
|
assert_commit_scn_allocated(txn_undo_hdr.prev_image);
|
|
txn_rec->scn = txn_undo_hdr.prev_image.scn;
|
|
txn_rec->gcn = txn_undo_hdr.prev_image.gcn;
|
|
lizard_undo_ptr_set_commit(&txn_rec->undo_ptr);
|
|
txn_lookup_t_set(txn_lookup, txn_undo_hdr, txn_undo_hdr.prev_image,
|
|
txn_state_t::TXN_STATE_REUSE);
|
|
if (!have_mtr) mtr_commit(mtr);
|
|
return false;
|
|
|
|
undo_corrupted:
|
|
/** Can't never be lost if cleanout_safe_mode isn't taken into
|
|
consideration */
|
|
ut_a(opt_cleanout_safe_mode);
|
|
txn_rec->scn = SCN_UNDO_CORRUPTED;
|
|
txn_rec->gcn = GCN_UNDO_CORRUPTED;
|
|
lizard_undo_ptr_set_commit(&txn_rec->undo_ptr);
|
|
txn_lookup_t_set(txn_lookup, txn_undo_hdr,
|
|
{SCN_UNDO_CORRUPTED, UTC_UNDO_CORRUPTED, GCN_UNDO_CORRUPTED},
|
|
txn_state_t::TXN_STATE_UNDO_CORRUPTED);
|
|
if (!have_mtr) mtr_commit(mtr);
|
|
return false;
|
|
}
|
|
|
|
#if defined UNIV_DEBUG || defined LIZARD_DEBUG
|
|
/*
|
|
static bool txn_undo_hdr_lookup_strict(txn_rec_t *txn_rec) {
|
|
return false;
|
|
}
|
|
*/
|
|
#endif /* UNIV_DEBUG || LIZARD_DEBUG */
|
|
|
|
/**
|
|
Try to lookup the real scn of given records.
|
|
|
|
@param[in/out] txn_rec txn info of the records.
|
|
@param[out] txn_lookup txn lookup result, nullptr if don't care
|
|
|
|
@return bool whether corresponding trx is active.
|
|
*/
|
|
bool txn_undo_hdr_lookup_low(txn_rec_t *txn_rec,
|
|
txn_lookup_t *txn_lookup,
|
|
mtr_t *txn_mtr) {
|
|
bool ret;
|
|
undo_addr_t undo_addr;
|
|
bool exist;
|
|
|
|
/** In theory, lizard has to findout the real acutal scn (if have) by
|
|
uba */
|
|
lizard_stats.txn_undo_lookup_by_uba.inc();
|
|
|
|
if (opt_cleanout_safe_mode) {
|
|
undo_decode_undo_ptr(txn_rec->undo_ptr, &undo_addr);
|
|
exist = txn_undo_logs->exist({undo_addr.space_id, undo_addr.page_no});
|
|
if (!exist) {
|
|
txn_undo_hdr_t txn_undo_hdr = {
|
|
{SCN_UNDO_CORRUPTED, UTC_UNDO_CORRUPTED, GCN_UNDO_CORRUPTED},
|
|
/** txn_undo_hdr.undo_ptr should be from txn undo header, and it
|
|
must be active state when coming here */
|
|
txn_rec->undo_ptr,
|
|
txn_rec->trx_id,
|
|
TXN_MAGIC_N,
|
|
{SCN_UNDO_CORRUPTED, UTC_UNDO_CORRUPTED, GCN_UNDO_CORRUPTED},
|
|
TXN_UNDO_LOG_PURGED,
|
|
0,
|
|
};
|
|
txn_rec->scn = SCN_UNDO_CORRUPTED;
|
|
txn_rec->gcn = GCN_UNDO_CORRUPTED;
|
|
lizard_undo_ptr_set_commit(&txn_rec->undo_ptr);
|
|
lizard_stats.txn_undo_lost_page_miss_when_safe.inc();
|
|
txn_lookup_t_set(txn_lookup, txn_undo_hdr,
|
|
{SCN_UNDO_CORRUPTED, UTC_UNDO_CORRUPTED, GCN_UNDO_CORRUPTED},
|
|
txn_state_t::TXN_STATE_UNDO_CORRUPTED);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ret = txn_undo_hdr_lookup_func(txn_rec, txn_lookup, txn_mtr);
|
|
|
|
#if defined UNIV_DEBUG || defined LIZARD_DEBUG
|
|
/*
|
|
bool strict_ret;
|
|
txn_rec_t txn_strict;
|
|
memcpy(&txn_strict, txn_rec, sizeof(*txn_rec));
|
|
|
|
strict_ret = txn_undo_hdr_lookup_strict(&txn_strict, expected_id);
|
|
|
|
ut_a(ret == strict_ret);
|
|
ut_a(txn_rec->scn == txn_strict.scn);
|
|
ut_a(txn_rec->undo_ptr == txn_strict.undo_ptr);
|
|
*/
|
|
|
|
#endif /* UNIV_DEBUG || LIZARD_DEBUG */
|
|
return ret;
|
|
}
|
|
/** Add the rseg into the purge queue heap */
|
|
void trx_add_rsegs_for_purge(commit_scn_t &cmmt, TxnUndoRsegs *elem) {
|
|
ut_ad(cmmt.scn == elem->get_scn());
|
|
mutex_enter(&purge_sys->pq_mutex);
|
|
purge_sys->purge_heap->push(*elem);
|
|
lizard_purged_scn_validation();
|
|
mutex_exit(&purge_sys->pq_mutex);
|
|
}
|
|
|
|
/** Collect rsegs into the purge heap for the first time */
|
|
bool trx_collect_rsegs_for_purge(TxnUndoRsegs *elem,
|
|
trx_undo_ptr_t *redo_rseg_undo_ptr,
|
|
trx_undo_ptr_t *temp_rseg_undo_ptr,
|
|
txn_undo_ptr_t *txn_rseg_undo_ptr) {
|
|
bool has = false;
|
|
trx_rseg_t *redo_rseg = nullptr;
|
|
trx_rseg_t *temp_rseg = nullptr;
|
|
trx_rseg_t *txn_rseg = nullptr;
|
|
|
|
if (redo_rseg_undo_ptr != nullptr) {
|
|
redo_rseg = redo_rseg_undo_ptr->rseg;
|
|
ut_ad(mutex_own(&redo_rseg->mutex));
|
|
}
|
|
|
|
if (temp_rseg_undo_ptr != NULL) {
|
|
temp_rseg = temp_rseg_undo_ptr->rseg;
|
|
ut_ad(mutex_own(&temp_rseg->mutex));
|
|
}
|
|
|
|
if (txn_rseg_undo_ptr != nullptr) {
|
|
txn_rseg = txn_rseg_undo_ptr->rseg;
|
|
ut_ad(mutex_own(&txn_rseg->mutex));
|
|
}
|
|
|
|
if (redo_rseg != NULL && redo_rseg->last_page_no == FIL_NULL) {
|
|
elem->push_back(redo_rseg);
|
|
has = true;
|
|
}
|
|
|
|
if (temp_rseg != NULL && temp_rseg->last_page_no == FIL_NULL) {
|
|
elem->push_back(temp_rseg);
|
|
has = true;
|
|
}
|
|
|
|
if (txn_rseg != NULL && txn_rseg->last_page_no == FIL_NULL) {
|
|
elem->push_back(txn_rseg);
|
|
has = true;
|
|
}
|
|
return has;
|
|
}
|
|
|
|
/*
|
|
static members.
|
|
*/
|
|
ulint Undo_retention::retention_time = 0;
|
|
ulint Undo_retention::space_limit = 100 * 1024;
|
|
ulint Undo_retention::space_reserve = 0;
|
|
char Undo_retention::status[128] = {0};
|
|
Undo_retention Undo_retention::inst;
|
|
|
|
int Undo_retention::check_limit(THD *thd, SYS_VAR *var, void *save,
|
|
struct st_mysql_value *value) {
|
|
if (check_func_long(thd, var, save, value)) return 1;
|
|
|
|
if (*(ulong *)save < space_reserve) {
|
|
push_warning_printf(thd, Sql_condition::SL_WARNING, ER_WRONG_ARGUMENTS,
|
|
"InnoDB: innodb_undo_space_limit should more than"
|
|
" innodb_undo_space_reserve.");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Undo_retention::check_reserve(THD *thd, SYS_VAR *var, void *save,
|
|
struct st_mysql_value *value) {
|
|
if (check_func_long(thd, var, save, value)) return 1;
|
|
|
|
if (*(ulong *)save > space_limit) {
|
|
push_warning_printf(thd, Sql_condition::SL_WARNING, ER_WRONG_ARGUMENTS,
|
|
"InnoDB: innodb_undo_space_reserve should less than"
|
|
" innodb_undo_space_limit.");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Undo_retention::on_update(THD *, SYS_VAR *, void *var_ptr,
|
|
const void *save) {
|
|
*static_cast<ulong *>(var_ptr) = *static_cast<const ulong *>(save);
|
|
srv_purge_wakeup(); /* notify purge thread to try again */
|
|
}
|
|
|
|
void Undo_retention::on_update_and_start(THD *thd, SYS_VAR *var, void *var_ptr,
|
|
const void *save) {
|
|
ulong old_value = *static_cast<const ulong *>(var_ptr);
|
|
ulong new_value = *static_cast<const ulong *>(save);
|
|
on_update(thd, var, var_ptr, save);
|
|
|
|
/* If open the undo retention, refresh stat data synchronously. */
|
|
if (new_value > 0 && old_value == 0) {
|
|
instance()->refresh_stat_data();
|
|
}
|
|
}
|
|
|
|
/*
|
|
Collect latest undo space sizes periodically.
|
|
*/
|
|
void Undo_retention::refresh_stat_data() {
|
|
mutex_enter(&m_mutex);
|
|
ulint used_size = 0;
|
|
ulint file_size = 0;
|
|
std::vector<space_id_t> undo_spaces;
|
|
|
|
if (retention_time == 0) {
|
|
m_stat_done = false;
|
|
mutex_exit(&m_mutex);
|
|
return;
|
|
}
|
|
|
|
/* Actual used size */
|
|
undo::spaces->s_lock();
|
|
for (auto undo_space : undo::spaces->m_spaces) {
|
|
ulint size = 0;
|
|
|
|
for (auto rseg : *undo_space->rsegs()) {
|
|
size += rseg->curr_size;
|
|
}
|
|
|
|
used_size += size;
|
|
undo_spaces.push_back(undo_space->id());
|
|
}
|
|
undo::spaces->s_unlock();
|
|
|
|
/* Physical file size */
|
|
for (auto id : undo_spaces) {
|
|
auto size = fil_space_get_size(id);
|
|
file_size += size;
|
|
}
|
|
|
|
m_total_used_size = used_size;
|
|
|
|
m_stat_done = true;
|
|
|
|
snprintf(status, sizeof(status),
|
|
"space:{file:%lu, used:%lu, limit:%lu, reserved:%lu}, "
|
|
"time:{top:%lu, now:%lu}", file_size, used_size,
|
|
mb_to_pages(space_limit), mb_to_pages(space_reserve),
|
|
m_last_top_utc, current_utc());
|
|
mutex_exit(&m_mutex);
|
|
}
|
|
|
|
/*
|
|
Decide whether to block purge or not based on the current
|
|
undo tablespace size and retention configuration.
|
|
|
|
@return true if blocking purge
|
|
*/
|
|
bool Undo_retention::purge_advise() {
|
|
m_last_top_utc = (ulint)(purge_sys->top_undo_utc / 1000000);
|
|
|
|
/* Retention turned off or stating not done, can not advise */
|
|
if (retention_time == 0 || !m_stat_done) return false;
|
|
|
|
/* During recovery, purge_sys::top_undo_utc may be not initialized,
|
|
because txn_rseg of this transaction has been processed. */
|
|
if (m_last_top_utc == 0) return false;
|
|
|
|
ulint used_size = m_total_used_size.load();
|
|
|
|
if (space_limit > 0) {
|
|
/* Rule_1: reach space limit, do purge */
|
|
if (used_size > mb_to_pages(space_limit)) return false;
|
|
}
|
|
|
|
/* Rule_2: retention time not satisfied, block purge */
|
|
if ((m_last_top_utc + retention_time) > current_utc()) return true;
|
|
|
|
if (space_reserve > 0) {
|
|
/* Rule_3: below reserved size yet, can hold more history data */
|
|
if (used_size < mb_to_pages(space_reserve)) return true;
|
|
}
|
|
|
|
/* Rule_4: time satisfied and exceeded the reserved, just do purge */
|
|
return false;
|
|
}
|
|
|
|
/* Init undo_retention */
|
|
void undo_retention_init() {
|
|
|
|
/* Init the lizard undo retention mutex. */
|
|
Undo_retention::instance()->init_mutex();
|
|
|
|
/* Force to refrese once at starting */
|
|
Undo_retention::instance()->refresh_stat_data();
|
|
}
|
|
|
|
/**
|
|
Decide the real trx state when read current record.
|
|
1) Search tcn cache
|
|
2) Lookup txn undo
|
|
|
|
And try to collect cursor to cache txn and cleanout record.
|
|
|
|
|
|
@param[in/out] txn record
|
|
|
|
@retval true active
|
|
false committed
|
|
*/
|
|
bool txn_rec_cleanout_state_by_misc(txn_rec_t *txn_rec, btr_pcur_t *pcur,
|
|
const rec_t *rec, const dict_index_t *index,
|
|
const ulint *offsets) {
|
|
bool active = false;
|
|
bool cache_hit = false;
|
|
|
|
/** If record is not active, return false directly. */
|
|
if (!lizard_undo_ptr_is_active(txn_rec->undo_ptr)) {
|
|
lizard_ut_ad(txn_rec->scn > 0 && txn_rec->scn < SCN_MAX);
|
|
lizard_ut_ad(txn_rec->gcn > 0 && txn_rec->gcn < GCN_MAX);
|
|
return false;
|
|
}
|
|
|
|
/** Search tcn cache */
|
|
cache_hit = trx_search_tcn(txn_rec, pcur, nullptr);
|
|
if (cache_hit) {
|
|
ut_ad(!lizard_undo_ptr_is_active(txn_rec->undo_ptr));
|
|
lizard_ut_ad(txn_rec->scn > 0 && txn_rec->scn < SCN_MAX);
|
|
lizard_ut_ad(txn_rec->gcn > 0 && txn_rec->gcn < GCN_MAX);
|
|
|
|
/** Collect record to cleanout later. */
|
|
row_cleanout_collect(txn_rec->trx_id, *txn_rec, rec, index, offsets, pcur);
|
|
|
|
return false;
|
|
}
|
|
|
|
ut_ad(cache_hit == false);
|
|
|
|
active = txn_undo_hdr_lookup_low(txn_rec, nullptr, nullptr);
|
|
if (active) {
|
|
return active;
|
|
} else {
|
|
ut_ad(!lizard_undo_ptr_is_active(txn_rec->undo_ptr));
|
|
lizard_ut_ad(txn_rec->scn > 0 && txn_rec->scn < SCN_MAX);
|
|
lizard_ut_ad(txn_rec->gcn > 0 && txn_rec->gcn < GCN_MAX);
|
|
|
|
/** Collect record to cleanout later.*/
|
|
row_cleanout_collect(txn_rec->trx_id, *txn_rec, rec, index, offsets, pcur);
|
|
/** Cache txn info into tcn. */
|
|
trx_cache_tcn(txn_rec->trx_id, *txn_rec, rec, index, offsets, pcur);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
} // namespace lizard
|