mirror of
https://github.com/MariaDB/server.git
synced 2025-12-24 11:21:21 +03:00
MDEV-24402: InnoDB CHECK TABLE ... EXTENDED
Until now, the attribute EXTENDED of CHECK TABLE was ignored by InnoDB, and InnoDB only counted the records in each index according to the current read view. Unless the attribute QUICK was specified, the function btr_validate_index() would be invoked to validate the B-tree structure (the sibling and child links between index pages). The EXTENDED check will not only count all index records according to the current read view, but also ensure that any delete-marked records in the clustered index are waiting for the purge of history, and that all secondary index records point to a version of the clustered index record that is waiting for the purge of history. In other words, no index may contain orphan records. Normal MVCC reads and the non-EXTENDED version of CHECK TABLE would ignore these orphans. Unpurged records merely result in warnings (at most one per index), not errors, and no indexes will be flagged as corrupted due to such garbage. It will remain possible to SELECT data from such indexes or tables (which will skip such records) or to rebuild the table to reclaim some space. We introduce purge_sys.end_view that will be (almost) a copy of purge_sys.view at the end of a batch of purging committed transaction history. It is not an exact copy, because if the size of a purge batch is limited by innodb_purge_batch_size, some records that purge_sys.view would allow to be purged will be left over for subsequent batches. The purge_sys.view is relevant in the purge of committed transaction history, to determine if records are safe to remove. The new purge_sys.end_view is relevant in MVCC operations and in CHECK TABLE ... EXTENDED. It tells which undo log records are safe to access (have not been discarded at the end of a purge batch). purge_sys.clone_oldest_view<true>(): In trx_lists_init_at_db_start(), clone the oldest read view similar to purge_sys_t::clone_end_view() so that CHECK TABLE ... EXTENDED will not report bogus failures between InnoDB restart and the completed purge of committed transaction history. purge_sys_t::is_purgeable(): Replaces purge_sys_t::changes_visible() in the case that purge_sys.latch will not be held by the caller. Among other things, this guards access to BLOBs. It is not safe to dereference any BLOBs of a delete-marked purgeable record, because they may have already been freed. purge_sys_t::view_guard::view(): Return a reference to purge_sys.view that will be protected by purge_sys.latch, held by purge_sys_t::view_guard. purge_sys_t::end_view_guard::view(): Return a reference to purge_sys.end_view while it is protected by purge_sys.end_latch. Whenever a thread needs to retrieve an older version of a clustered index record, it will hold a page latch on the clustered index page and potentially also on a secondary index page that points to the clustered index page. If these pages contain purgeable records that would be accessed by a currently running purge batch, the progress of the purge batch would be blocked by the page latches. Hence, it is safe to make a copy of purge_sys.end_view while holding an index page latch, and consult the copy of the view to determine whether a record should already have been purged. btr_validate_index(): Remove a redundant check. row_check_index_match(): Check if a secondary index record and a version of a clustered index record match each other. row_check_index(): Replaces row_scan_index_for_mysql(). Count the records in each index directly, duplicating the relevant logic from row_search_mvcc(). Initialize check_table_extended_view for CHECK ... EXTENDED while holding an index leaf page latch. If we encounter an orphan record, the copy of purge_sys.end_view that we make is safe for visibility checks, and trx_undo_get_undo_rec() will check for the safety to access each undo log record. Should that check fail, we should return DB_MISSING_HISTORY to report a corrupted index. The EXTENDED check tries to match each secondary index record with every available clustered index record version, by duplicating the logic of row_vers_build_for_consistent_read() and invoking trx_undo_prev_version_build() directly. Before invoking row_check_index_match() on delete-marked clustered index record versions, we will consult purge_sys.is_purgeable() in order to avoid accessing freed BLOBs. We will always check that the DB_TRX_ID or PAGE_MAX_TRX_ID does not exceed the global maximum. Orphan secondary index records will be flagged only if everything up to PAGE_MAX_TRX_ID has been purged. We warn also about clustered index records whose nonzero DB_TRX_ID should have been reset in purge or rollback. trx_set_rw_mode(): Move an assertion from ReadView::set_creator_trx_id(). trx_undo_prev_version_build(): Remove two debug-only parameters, and return an error code instead of a Boolean. trx_undo_get_undo_rec(): Return a pointer to the undo log record, or nullptr if one cannot be retrieved. Instead of consulting the purge_sys.view, consult the purge_sys.end_view to determine which records can be accessed. trx_undo_get_rec_if_purgeable(): A variant of trx_undo_get_undo_rec() that will consult purge_sys.view instead of purge_sys.end_view. TRX_UNDO_CHECK_PURGEABILITY: A new parameter to trx_undo_prev_version_build(), passed by row_vers_old_has_index_entry() so that purge_sys.view instead of purge_sys.end_view will be consulted to determine whether a secondary index record may be safely purged. row_upd_changes_disowned_external(): Remove. This should be more expensive than briefly latching purge_sys in trx_undo_prev_version_build() (which may make use of transactional memory). row_sel_reset_old_vers_heap(): New function, split from row_sel_build_prev_vers_for_mysql(). row_sel_build_prev_vers_for_mysql(): Reorder some parameters to simplify the call to row_sel_reset_old_vers_heap(). row_search_for_mysql(): Replaced with direct calls to row_search_mvcc(). sel_node_get_nth_plan(): Define inline in row0sel.h open_step(): Define at the call site, in simplified form. sel_node_reset_cursor(): Merged with the only caller open_step(). --- ReadViewBase::check_trx_id_sanity(): Remove. Let us handle "future" DB_TRX_ID in a more meaningful way: row_sel_clust_sees(): Return DB_SUCCESS if the record is visible, DB_SUCCESS_LOCKED_REC if it is invisible, and DB_CORRUPTION if the DB_TRX_ID is in the future. row_undo_mod_must_purge(), row_undo_mod_clust(): Silently ignore corrupted DB_TRX_ID. We are in ROLLBACK, and we should have noticed that corruption when we were about to modify the record in the first place (leading us to refuse the operation). row_vers_build_for_consistent_read(): Return DB_CORRUPTION if DB_TRX_ID is in the future. Tested by: Matthias Leich Reviewed by: Vladislav Lesin
This commit is contained in:
@@ -24,7 +24,7 @@ COMMIT;
|
||||
UPDATE t1 SET a=1;
|
||||
connection default;
|
||||
InnoDB 0 transactions not purged
|
||||
CHECK TABLE t1;
|
||||
CHECK TABLE t1 EXTENDED;
|
||||
Table Op Msg_type Msg_text
|
||||
test.t1 check status OK
|
||||
SELECT b1 FROM t1;
|
||||
@@ -123,7 +123,7 @@ COMMIT;
|
||||
disconnect con1;
|
||||
connection default;
|
||||
InnoDB 0 transactions not purged
|
||||
CHECK TABLE t1;
|
||||
CHECK TABLE t1 EXTENDED;
|
||||
Table Op Msg_type Msg_text
|
||||
test.t1 check status OK
|
||||
SELECT b1 FROM t1;
|
||||
@@ -134,7 +134,7 @@ SELECT * FROM t1;
|
||||
a b b1 a1 a4 b3
|
||||
100 10 10 100 90 100
|
||||
100 10 10 100 90 100
|
||||
CHECK TABLE t2;
|
||||
CHECK TABLE t2 EXTENDED;
|
||||
Table Op Msg_type Msg_text
|
||||
test.t2 check status OK
|
||||
DROP TABLE t2, t1, t0;
|
||||
|
||||
@@ -38,7 +38,7 @@ UPDATE t1 SET a=1;
|
||||
connection default;
|
||||
--source ../../innodb/include/wait_all_purged.inc
|
||||
|
||||
CHECK TABLE t1;
|
||||
CHECK TABLE t1 EXTENDED;
|
||||
SELECT b1 FROM t1;
|
||||
|
||||
|
||||
@@ -123,11 +123,11 @@ disconnect con1;
|
||||
connection default;
|
||||
--source ../../innodb/include/wait_all_purged.inc
|
||||
|
||||
CHECK TABLE t1;
|
||||
CHECK TABLE t1 EXTENDED;
|
||||
SELECT b1 FROM t1;
|
||||
|
||||
SELECT * FROM t1;
|
||||
CHECK TABLE t2;
|
||||
CHECK TABLE t2 EXTENDED;
|
||||
DROP TABLE t2, t1, t0;
|
||||
|
||||
CREATE TABLE t1 (a VARCHAR(30), b INT, a2 VARCHAR(30) GENERATED ALWAYS AS (a) VIRTUAL);
|
||||
|
||||
@@ -6,13 +6,9 @@ SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
|
||||
CREATE TABLE t1(a INT) row_format=redundant engine=innoDB;
|
||||
INSERT INTO t1 VALUES(1);
|
||||
InnoDB 0 transactions not purged
|
||||
NOT FOUND /\[Warning\] InnoDB: A transaction id in a record of table `test`\.`t1` is newer than the system-wide maximum/ in mysqld.1.err
|
||||
call mtr.add_suppression("\\[Warning\\] InnoDB: A transaction id in a record of table `test`\\.`t1` is newer than the system-wide maximum");
|
||||
SET @save_count = @@max_error_count;
|
||||
SET max_error_count = 1;
|
||||
call mtr.add_suppression("\\[ERROR\\] InnoDB: We detected index corruption");
|
||||
call mtr.add_suppression("Index for table 't1' is corrupt; try to repair it");
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
Warnings:
|
||||
Warning 1642 InnoDB: Transaction id in a record of table `test`.`t1` is newer than system-wide maximum.
|
||||
SET max_error_count = @save_count;
|
||||
ERROR HY000: Index for table 't1' is corrupt; try to repair it
|
||||
DROP TABLE t1;
|
||||
|
||||
@@ -57,19 +57,11 @@ syswrite(FILE, $page, $ps)==$ps || die "Unable to write $file\n";
|
||||
close(FILE) || die "Unable to close $file";
|
||||
EOF
|
||||
|
||||
# Debug assertions would fail due to the injected corruption.
|
||||
--let $restart_parameters= --loose-skip-debug-assert
|
||||
--source include/start_mysqld.inc
|
||||
|
||||
let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err;
|
||||
let SEARCH_PATTERN= \[Warning\] InnoDB: A transaction id in a record of table `test`\.`t1` is newer than the system-wide maximum;
|
||||
--source include/search_pattern_in_file.inc
|
||||
|
||||
call mtr.add_suppression("\\[Warning\\] InnoDB: A transaction id in a record of table `test`\\.`t1` is newer than the system-wide maximum");
|
||||
call mtr.add_suppression("\\[ERROR\\] InnoDB: We detected index corruption");
|
||||
call mtr.add_suppression("Index for table 't1' is corrupt; try to repair it");
|
||||
|
||||
# A debug assertion would cause a duplicated message to be output.
|
||||
SET @save_count = @@max_error_count;
|
||||
SET max_error_count = 1;
|
||||
--error ER_NOT_KEYFILE
|
||||
SELECT * FROM t1;
|
||||
SET max_error_count = @save_count;
|
||||
DROP TABLE t1;
|
||||
|
||||
@@ -338,7 +338,6 @@ SET(INNOBASE_SOURCES
|
||||
include/row0row.h
|
||||
include/row0row.inl
|
||||
include/row0sel.h
|
||||
include/row0sel.inl
|
||||
include/row0types.h
|
||||
include/row0uins.h
|
||||
include/row0umod.h
|
||||
|
||||
@@ -5276,11 +5276,6 @@ btr_validate_index(
|
||||
dict_index_t* index, /*!< in: index */
|
||||
const trx_t* trx) /*!< in: transaction or NULL */
|
||||
{
|
||||
/* Full Text index are implemented by auxiliary tables, not the B-tree */
|
||||
if (index->online_status != ONLINE_INDEX_COMPLETE ||
|
||||
(index->type & (DICT_FTS | DICT_CORRUPT)))
|
||||
return DB_SUCCESS;
|
||||
|
||||
const bool lockout= index->is_spatial();
|
||||
|
||||
mtr_t mtr;
|
||||
|
||||
@@ -33,6 +33,7 @@ Created Jan 06, 2010 Vasil Dimov
|
||||
#include <mysql_com.h>
|
||||
#include "log.h"
|
||||
#include "btr0btr.h"
|
||||
#include "que0que.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*****************************************************************************
|
||||
|
||||
Copyright (c) 2007, 2020, Oracle and/or its affiliates. All Rights Reserved.
|
||||
Copyright (c) 2018, MariaDB Corporation.
|
||||
Copyright (c) 2018, 2022, MariaDB Corporation.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
@@ -28,6 +28,7 @@ Created 2007/3/16 Sunny Bains.
|
||||
#include "fts0ast.h"
|
||||
#include "fts0pars.h"
|
||||
#include "fts0fts.h"
|
||||
#include "trx0trx.h"
|
||||
|
||||
/* The FTS ast visit pass. */
|
||||
enum fts_ast_visit_pass_t {
|
||||
|
||||
@@ -3662,7 +3662,7 @@ ha_innobase::init_table_handle_for_HANDLER(void)
|
||||
innobase_register_trx(ht, m_user_thd, m_prebuilt->trx);
|
||||
|
||||
/* We did the necessary inits in this function, no need to repeat them
|
||||
in row_search_for_mysql */
|
||||
in row_search_mvcc() */
|
||||
|
||||
m_prebuilt->sql_stat_start = FALSE;
|
||||
|
||||
@@ -7411,7 +7411,7 @@ ha_innobase::build_template(
|
||||
/* We must at least fetch all primary key cols. Note
|
||||
that if the clustered index was internally generated
|
||||
by InnoDB on the row id (no primary key was
|
||||
defined), then row_search_for_mysql() will always
|
||||
defined), then row_search_mvcc() will always
|
||||
retrieve the row id to a special buffer in the
|
||||
m_prebuilt struct. */
|
||||
|
||||
@@ -8943,7 +8943,7 @@ statement issued by the user. We also increment trx->n_mysql_tables_in_use.
|
||||
instructions to m_prebuilt->template of the table handle instance in
|
||||
::index_read. The template is used to save CPU time in large joins.
|
||||
|
||||
3) In row_search_for_mysql, if m_prebuilt->sql_stat_start is true, we
|
||||
3) In row_search_mvcc(), if m_prebuilt->sql_stat_start is true, we
|
||||
allocate a new consistent read view for the trx if it does not yet have one,
|
||||
or in the case of a locking read, set an InnoDB 'intention' table level
|
||||
lock on the table.
|
||||
@@ -9245,7 +9245,7 @@ ha_innobase::change_active_index(
|
||||
}
|
||||
|
||||
/* The caller seems to ignore this. Thus, we must check
|
||||
this again in row_search_for_mysql(). */
|
||||
this again in row_search_mvcc(). */
|
||||
DBUG_RETURN(convert_error_code_to_mysql(DB_MISSING_HISTORY,
|
||||
0, NULL));
|
||||
}
|
||||
@@ -9845,9 +9845,9 @@ next_record:
|
||||
|
||||
int error;
|
||||
|
||||
switch (dberr_t ret = row_search_for_mysql(buf, PAGE_CUR_GE,
|
||||
m_prebuilt,
|
||||
ROW_SEL_EXACT, 0)) {
|
||||
switch (dberr_t ret = row_search_mvcc(buf, PAGE_CUR_GE,
|
||||
m_prebuilt,
|
||||
ROW_SEL_EXACT, 0)) {
|
||||
case DB_SUCCESS:
|
||||
error = 0;
|
||||
table->status = 0;
|
||||
@@ -15186,8 +15186,10 @@ ha_innobase::check(
|
||||
|
||||
DBUG_ENTER("ha_innobase::check");
|
||||
DBUG_ASSERT(thd == ha_thd());
|
||||
DBUG_ASSERT(thd == m_user_thd);
|
||||
ut_a(m_prebuilt->trx->magic_n == TRX_MAGIC_N);
|
||||
ut_a(m_prebuilt->trx == thd_to_trx(thd));
|
||||
ut_ad(m_prebuilt->trx->mysql_thd == thd);
|
||||
|
||||
if (m_prebuilt->mysql_template == NULL) {
|
||||
/* Build the template; we will use a dummy template
|
||||
@@ -15197,7 +15199,6 @@ ha_innobase::check(
|
||||
}
|
||||
|
||||
if (!m_prebuilt->table->space) {
|
||||
|
||||
ib_senderrf(
|
||||
thd,
|
||||
IB_LOG_LEVEL_ERROR,
|
||||
@@ -15205,10 +15206,7 @@ ha_innobase::check(
|
||||
table->s->table_name.str);
|
||||
|
||||
DBUG_RETURN(HA_ADMIN_CORRUPT);
|
||||
|
||||
} else if (!m_prebuilt->table->is_readable() &&
|
||||
!m_prebuilt->table->space) {
|
||||
|
||||
} else if (!m_prebuilt->table->is_readable()) {
|
||||
ib_senderrf(
|
||||
thd, IB_LOG_LEVEL_ERROR,
|
||||
ER_TABLESPACE_MISSING,
|
||||
@@ -15229,6 +15227,9 @@ ha_innobase::check(
|
||||
? TRX_ISO_READ_UNCOMMITTED
|
||||
: TRX_ISO_REPEATABLE_READ;
|
||||
|
||||
trx_start_if_not_started(m_prebuilt->trx, false);
|
||||
m_prebuilt->trx->read_view.open(m_prebuilt->trx);
|
||||
|
||||
for (dict_index_t* index
|
||||
= dict_table_get_first_index(m_prebuilt->table);
|
||||
index;
|
||||
@@ -15237,25 +15238,22 @@ ha_innobase::check(
|
||||
if (!index->is_committed()) {
|
||||
continue;
|
||||
}
|
||||
if (index->type & DICT_FTS) {
|
||||
/* We do not check any FULLTEXT INDEX. */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(check_opt->flags & T_QUICK)
|
||||
&& !index->is_corrupted()) {
|
||||
|
||||
dberr_t err = btr_validate_index(
|
||||
index, m_prebuilt->trx);
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
is_ok = false;
|
||||
|
||||
push_warning_printf(
|
||||
thd,
|
||||
Sql_condition::WARN_LEVEL_WARN,
|
||||
ER_NOT_KEYFILE,
|
||||
"InnoDB: The B-tree of"
|
||||
" index %s is corrupted.",
|
||||
index->name());
|
||||
continue;
|
||||
}
|
||||
if ((check_opt->flags & T_QUICK) || index->is_corrupted()) {
|
||||
} else if (btr_validate_index(index, m_prebuilt->trx)
|
||||
!= DB_SUCCESS) {
|
||||
is_ok = false;
|
||||
push_warning_printf(thd,
|
||||
Sql_condition::WARN_LEVEL_WARN,
|
||||
ER_NOT_KEYFILE,
|
||||
"InnoDB: The B-tree of"
|
||||
" index %s is corrupted.",
|
||||
index->name());
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Instead of invoking change_active_index(), set up
|
||||
@@ -15277,7 +15275,7 @@ ha_innobase::check(
|
||||
if (UNIV_UNLIKELY(!m_prebuilt->index_usable)) {
|
||||
if (index->is_corrupted()) {
|
||||
push_warning_printf(
|
||||
m_user_thd,
|
||||
thd,
|
||||
Sql_condition::WARN_LEVEL_WARN,
|
||||
HA_ERR_INDEX_CORRUPT,
|
||||
"InnoDB: Index %s is marked as"
|
||||
@@ -15286,7 +15284,7 @@ ha_innobase::check(
|
||||
is_ok = false;
|
||||
} else {
|
||||
push_warning_printf(
|
||||
m_user_thd,
|
||||
thd,
|
||||
Sql_condition::WARN_LEVEL_WARN,
|
||||
HA_ERR_TABLE_DEF_CHANGED,
|
||||
"InnoDB: Insufficient history for"
|
||||
@@ -15299,18 +15297,22 @@ ha_innobase::check(
|
||||
m_prebuilt->sql_stat_start = TRUE;
|
||||
m_prebuilt->template_type = ROW_MYSQL_DUMMY_TEMPLATE;
|
||||
m_prebuilt->n_template = 0;
|
||||
m_prebuilt->need_to_access_clustered = FALSE;
|
||||
m_prebuilt->read_just_key = 0;
|
||||
m_prebuilt->autoinc_error = DB_SUCCESS;
|
||||
m_prebuilt->need_to_access_clustered =
|
||||
!!(check_opt->flags & T_EXTEND);
|
||||
|
||||
dtuple_set_n_fields(m_prebuilt->search_tuple, 0);
|
||||
|
||||
m_prebuilt->select_lock_type = LOCK_NONE;
|
||||
|
||||
/* Scan this index. */
|
||||
if (dict_index_is_spatial(index)) {
|
||||
if (index->is_spatial()) {
|
||||
ret = row_count_rtree_recs(m_prebuilt, &n_rows);
|
||||
} else if (index->type & DICT_FTS) {
|
||||
ret = DB_SUCCESS;
|
||||
} else {
|
||||
ret = row_scan_index_for_mysql(
|
||||
m_prebuilt, index, &n_rows);
|
||||
ret = row_check_index(m_prebuilt, &n_rows);
|
||||
}
|
||||
|
||||
DBUG_EXECUTE_IF(
|
||||
@@ -15319,11 +15321,18 @@ ha_innobase::check(
|
||||
ret = DB_CORRUPTION;
|
||||
});
|
||||
|
||||
if (ret == DB_INTERRUPTED || thd_killed(m_user_thd)) {
|
||||
if (ret == DB_INTERRUPTED || thd_killed(thd)) {
|
||||
/* Do not report error since this could happen
|
||||
during shutdown */
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret == DB_SUCCESS
|
||||
&& m_prebuilt->autoinc_error != DB_MISSING_HISTORY) {
|
||||
/* See if any non-fatal errors were reported. */
|
||||
ret = m_prebuilt->autoinc_error;
|
||||
}
|
||||
|
||||
if (ret != DB_SUCCESS) {
|
||||
/* Assume some kind of corruption. */
|
||||
push_warning_printf(
|
||||
|
||||
@@ -121,19 +121,6 @@ loop:
|
||||
inline void snapshot(trx_t *trx);
|
||||
|
||||
|
||||
/**
|
||||
Check whether transaction id is valid.
|
||||
@param[in] id transaction id to check
|
||||
@param[in] name table name
|
||||
|
||||
@todo changes_visible() was an unfortunate choice for this check.
|
||||
It should be moved towards the functions that load trx id like
|
||||
trx_read_trx_id(). No need to issue a warning, error log message should
|
||||
be enough. Although statement should ideally fail if it sees corrupt
|
||||
data.
|
||||
*/
|
||||
static void check_trx_id_sanity(trx_id_t id, const table_name_t &name);
|
||||
|
||||
/**
|
||||
Check whether the changes by id are visible.
|
||||
@param[in] id transaction id to check against the view
|
||||
@@ -149,26 +136,6 @@ loop:
|
||||
!std::binary_search(m_ids.begin(), m_ids.end(), id);
|
||||
}
|
||||
|
||||
/**
|
||||
Check whether the changes by id are visible.
|
||||
@param[in] id transaction id to check against the view
|
||||
@param[in] name table name
|
||||
@return whether the view sees the modifications of id.
|
||||
*/
|
||||
bool changes_visible(trx_id_t id, const table_name_t &name) const
|
||||
MY_ATTRIBUTE((warn_unused_result))
|
||||
{
|
||||
if (id >= m_low_limit_id)
|
||||
{
|
||||
check_trx_id_sanity(id, name);
|
||||
return false;
|
||||
}
|
||||
return id < m_up_limit_id ||
|
||||
m_ids.empty() ||
|
||||
!std::binary_search(m_ids.begin(), m_ids.end(), id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@param id transaction to check
|
||||
@return true if view sees transaction id
|
||||
@@ -180,6 +147,13 @@ loop:
|
||||
|
||||
/** @return the low limit id */
|
||||
trx_id_t low_limit_id() const { return m_low_limit_id; }
|
||||
|
||||
/** Clamp the low limit id for purge_sys.end_view */
|
||||
void clamp_low_limit_id(trx_id_t limit)
|
||||
{
|
||||
if (m_low_limit_id > limit)
|
||||
m_low_limit_id= limit;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -250,7 +224,6 @@ public:
|
||||
*/
|
||||
void set_creator_trx_id(trx_id_t id)
|
||||
{
|
||||
ut_ad(id > 0);
|
||||
ut_ad(m_creator_trx_id == 0);
|
||||
m_creator_trx_id= id;
|
||||
}
|
||||
@@ -275,8 +248,6 @@ public:
|
||||
A wrapper around ReadViewBase::changes_visible().
|
||||
Intended to be called by the ReadView owner thread.
|
||||
*/
|
||||
bool changes_visible(trx_id_t id, const table_name_t &name) const
|
||||
{ return id == m_creator_trx_id || ReadViewBase::changes_visible(id, name); }
|
||||
bool changes_visible(trx_id_t id) const
|
||||
{ return id == m_creator_trx_id || ReadViewBase::changes_visible(id); }
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*****************************************************************************
|
||||
|
||||
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All Rights Reserved.
|
||||
Copyright (c) 2017, 2021, MariaDB Corporation.
|
||||
Copyright (c) 2017, 2022, MariaDB Corporation.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
@@ -263,7 +263,7 @@ row_update_for_mysql(
|
||||
|
||||
/** This can only be used when the current transaction is at
|
||||
READ COMMITTED or READ UNCOMMITTED isolation level.
|
||||
Before calling this function row_search_for_mysql() must have
|
||||
Before calling this function row_search_mvcc() must have
|
||||
initialized prebuilt->new_rec_locks to store the information which new
|
||||
record locks really were set. This function removes a newly set
|
||||
clustered index record lock under prebuilt->pcur or
|
||||
@@ -382,22 +382,6 @@ row_rename_table_for_mysql(
|
||||
FOREIGN KEY constraints */
|
||||
MY_ATTRIBUTE((nonnull, warn_unused_result));
|
||||
|
||||
/*********************************************************************//**
|
||||
Scans an index for either COOUNT(*) or CHECK TABLE.
|
||||
If CHECK TABLE; Checks that the index contains entries in an ascending order,
|
||||
unique constraint is not broken, and calculates the number of index entries
|
||||
in the read view of the current transaction.
|
||||
@return DB_SUCCESS or other error */
|
||||
dberr_t
|
||||
row_scan_index_for_mysql(
|
||||
/*=====================*/
|
||||
row_prebuilt_t* prebuilt, /*!< in: prebuilt struct
|
||||
in MySQL handle */
|
||||
const dict_index_t* index, /*!< in: index */
|
||||
ulint* n_rows) /*!< out: number of entries
|
||||
seen in the consistent read */
|
||||
MY_ATTRIBUTE((warn_unused_result));
|
||||
|
||||
/* A struct describing a place for an individual column in the MySQL
|
||||
row format which is presented to the table handler in ha_innobase.
|
||||
This template struct is used to speed up row transformations between
|
||||
@@ -606,7 +590,7 @@ struct row_prebuilt_t {
|
||||
ROW_READ_TRY_SEMI_CONSISTENT and
|
||||
to simply skip the row. If
|
||||
the row matches, the next call to
|
||||
row_search_for_mysql() will lock
|
||||
row_search_mvcc() will lock
|
||||
the row.
|
||||
This eliminates lock waits in some
|
||||
cases; note that this breaks
|
||||
@@ -615,7 +599,7 @@ struct row_prebuilt_t {
|
||||
the session is using READ
|
||||
COMMITTED or READ UNCOMMITTED
|
||||
isolation level, set in
|
||||
row_search_for_mysql() if we set a new
|
||||
row_search_mvcc() if we set a new
|
||||
record lock on the secondary
|
||||
or clustered index; this is
|
||||
used in row_unlock_for_mysql()
|
||||
@@ -847,7 +831,7 @@ innobase_rename_vc_templ(
|
||||
#define ROW_MYSQL_REC_FIELDS 1
|
||||
#define ROW_MYSQL_NO_TEMPLATE 2
|
||||
#define ROW_MYSQL_DUMMY_TEMPLATE 3 /* dummy template used in
|
||||
row_scan_and_check_index */
|
||||
row_check_index() */
|
||||
|
||||
/* Values for hint_need_to_fetch_extra_cols */
|
||||
#define ROW_RETRIEVE_PRIMARY_KEY 1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*****************************************************************************
|
||||
|
||||
Copyright (c) 1997, 2017, Oracle and/or its affiliates.
|
||||
Copyright (c) 2017, MariaDB Corporation.
|
||||
Copyright (c) 2017, 2022, MariaDB Corporation.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
@@ -24,8 +24,7 @@ Select
|
||||
Created 12/19/1997 Heikki Tuuri
|
||||
*******************************************************/
|
||||
|
||||
#ifndef row0sel_h
|
||||
#define row0sel_h
|
||||
#pragma once
|
||||
|
||||
#include "data0data.h"
|
||||
#include "que0types.h"
|
||||
@@ -58,15 +57,6 @@ void
|
||||
sel_col_prefetch_buf_free(
|
||||
/*======================*/
|
||||
sel_buf_t* prefetch_buf); /*!< in, own: prefetch buffer */
|
||||
/*********************************************************************//**
|
||||
Gets the plan node for the nth table in a join.
|
||||
@return plan node */
|
||||
UNIV_INLINE
|
||||
plan_t*
|
||||
sel_node_get_nth_plan(
|
||||
/*==================*/
|
||||
sel_node_t* node, /*!< in: select node */
|
||||
ulint i); /*!< in: get ith plan node */
|
||||
/**********************************************************************//**
|
||||
Performs a select step. This is a high-level function used in SQL execution
|
||||
graphs.
|
||||
@@ -76,14 +66,6 @@ row_sel_step(
|
||||
/*=========*/
|
||||
que_thr_t* thr); /*!< in: query thread */
|
||||
/**********************************************************************//**
|
||||
Performs an execution step of an open or close cursor statement node.
|
||||
@return query thread to run next or NULL */
|
||||
UNIV_INLINE
|
||||
que_thr_t*
|
||||
open_step(
|
||||
/*======*/
|
||||
que_thr_t* thr); /*!< in: query thread */
|
||||
/**********************************************************************//**
|
||||
Performs a fetch for a cursor.
|
||||
@return query thread to run next or NULL */
|
||||
que_thr_t*
|
||||
@@ -136,37 +118,7 @@ row_sel_convert_mysql_key_to_innobase(
|
||||
ulint key_len); /*!< in: MySQL key value length */
|
||||
|
||||
|
||||
/** Searches for rows in the database. This is used in the interface to
|
||||
MySQL. This function opens a cursor, and also implements fetch next
|
||||
and fetch prev. NOTE that if we do a search with a full key value
|
||||
from a unique index (ROW_SEL_EXACT), then we will not store the cursor
|
||||
position and fetch next or fetch prev must not be tried to the cursor!
|
||||
|
||||
@param[out] buf buffer for the fetched row in MySQL format
|
||||
@param[in] mode search mode PAGE_CUR_L
|
||||
@param[in,out] prebuilt prebuilt struct for the table handler;
|
||||
this contains the info to search_tuple,
|
||||
index; if search tuple contains 0 field then
|
||||
we position the cursor at start or the end of
|
||||
index, depending on 'mode'
|
||||
@param[in] match_mode 0 or ROW_SEL_EXACT or ROW_SEL_EXACT_PREFIX
|
||||
@param[in] direction 0 or ROW_SEL_NEXT or ROW_SEL_PREV;
|
||||
Note: if this is != 0, then prebuilt must has a
|
||||
pcur with stored position! In opening of a
|
||||
cursor 'direction' should be 0.
|
||||
@return DB_SUCCESS, DB_RECORD_NOT_FOUND, DB_END_OF_INDEX, DB_DEADLOCK,
|
||||
DB_LOCK_TABLE_FULL, DB_CORRUPTION, or DB_TOO_BIG_RECORD */
|
||||
UNIV_INLINE
|
||||
dberr_t
|
||||
row_search_for_mysql(
|
||||
byte* buf,
|
||||
page_cur_mode_t mode,
|
||||
row_prebuilt_t* prebuilt,
|
||||
ulint match_mode,
|
||||
ulint direction)
|
||||
MY_ATTRIBUTE((warn_unused_result));
|
||||
|
||||
/** Searches for rows in the database using cursor.
|
||||
/** Search for rows in the database using cursor.
|
||||
Function is mainly used for tables that are shared across connections and
|
||||
so it employs technique that can help re-construct the rows that
|
||||
transaction is suppose to see.
|
||||
@@ -184,7 +136,8 @@ It also has optimization such as pre-caching the rows, using AHI, etc.
|
||||
Note: if this is != 0, then prebuilt must has a
|
||||
pcur with stored position! In opening of a
|
||||
cursor 'direction' should be 0.
|
||||
@return DB_SUCCESS or error code */
|
||||
@return DB_SUCCESS, DB_RECORD_NOT_FOUND, DB_END_OF_INDEX, DB_DEADLOCK,
|
||||
DB_LOCK_TABLE_FULL, DB_CORRUPTION, or DB_TOO_BIG_RECORD */
|
||||
dberr_t
|
||||
row_search_mvcc(
|
||||
byte* buf,
|
||||
@@ -210,6 +163,21 @@ row_count_rtree_recs(
|
||||
ulint* n_rows); /*!< out: number of entries
|
||||
seen in the consistent read */
|
||||
|
||||
/**
|
||||
Check the index records in CHECK TABLE.
|
||||
The index must contain entries in an ascending order,
|
||||
unique constraint must not be violated by duplicated keys,
|
||||
and the number of index entries is counted in according to the
|
||||
current read view.
|
||||
|
||||
@param prebuilt index and transaction
|
||||
@param n_rows number of records counted
|
||||
|
||||
@return error code
|
||||
@retval DB_SUCCESS if no error was found */
|
||||
dberr_t row_check_index(row_prebuilt_t *prebuilt, ulint *n_rows)
|
||||
MY_ATTRIBUTE((nonnull, warn_unused_result));
|
||||
|
||||
/** Read the max AUTOINC value from an index.
|
||||
@param[in] index index starting with an AUTO_INCREMENT column
|
||||
@return the largest AUTO_INCREMENT value
|
||||
@@ -382,6 +350,17 @@ struct sel_node_t{
|
||||
fetches */
|
||||
};
|
||||
|
||||
/**
|
||||
Get the plan node for a table in a join.
|
||||
@param node query graph node for SELECT
|
||||
@param i plan node element
|
||||
@return ith plan node */
|
||||
inline plan_t *sel_node_get_nth_plan(sel_node_t *node, ulint i)
|
||||
{
|
||||
ut_ad(i < node->n_tables);
|
||||
return &node->plans[i];
|
||||
}
|
||||
|
||||
/** Fetch statement node */
|
||||
struct fetch_node_t{
|
||||
que_common_t common; /*!< type: QUE_NODE_FETCH */
|
||||
@@ -476,7 +455,3 @@ row_sel_field_store_in_mysql_format_func(
|
||||
#endif /* UNIV_DEBUG */
|
||||
const byte* data, /*!< in: data to store */
|
||||
ulint len); /*!< in: length of the data */
|
||||
|
||||
#include "row0sel.inl"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
/*****************************************************************************
|
||||
|
||||
Copyright (c) 1997, 2014, Oracle 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 as published by the Free Software
|
||||
Foundation; version 2 of the License.
|
||||
|
||||
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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA
|
||||
|
||||
*****************************************************************************/
|
||||
|
||||
/**************************************************//**
|
||||
@file include/row0sel.ic
|
||||
Select
|
||||
|
||||
Created 12/19/1997 Heikki Tuuri
|
||||
*******************************************************/
|
||||
|
||||
#include "que0que.h"
|
||||
|
||||
/*********************************************************************//**
|
||||
Gets the plan node for the nth table in a join.
|
||||
@return plan node */
|
||||
UNIV_INLINE
|
||||
plan_t*
|
||||
sel_node_get_nth_plan(
|
||||
/*==================*/
|
||||
sel_node_t* node, /*!< in: select node */
|
||||
ulint i) /*!< in: get ith plan node */
|
||||
{
|
||||
ut_ad(i < node->n_tables);
|
||||
|
||||
return(node->plans + i);
|
||||
}
|
||||
|
||||
/*********************************************************************//**
|
||||
Resets the cursor defined by sel_node to the SEL_NODE_OPEN state, which means
|
||||
that it will start fetching from the start of the result set again, regardless
|
||||
of where it was before, and it will set intention locks on the tables. */
|
||||
UNIV_INLINE
|
||||
void
|
||||
sel_node_reset_cursor(
|
||||
/*==================*/
|
||||
sel_node_t* node) /*!< in: select node */
|
||||
{
|
||||
node->state = SEL_NODE_OPEN;
|
||||
}
|
||||
|
||||
/**********************************************************************//**
|
||||
Performs an execution step of an open or close cursor statement node.
|
||||
@return query thread to run next or NULL */
|
||||
UNIV_INLINE
|
||||
que_thr_t*
|
||||
open_step(
|
||||
/*======*/
|
||||
que_thr_t* thr) /*!< in: query thread */
|
||||
{
|
||||
sel_node_t* sel_node;
|
||||
open_node_t* node;
|
||||
ulint err;
|
||||
|
||||
ut_ad(thr);
|
||||
|
||||
node = (open_node_t*) thr->run_node;
|
||||
ut_ad(que_node_get_type(node) == QUE_NODE_OPEN);
|
||||
|
||||
sel_node = node->cursor_def;
|
||||
|
||||
err = DB_SUCCESS;
|
||||
|
||||
if (node->op_type == ROW_SEL_OPEN_CURSOR) {
|
||||
|
||||
/* if (sel_node->state == SEL_NODE_CLOSED) { */
|
||||
|
||||
sel_node_reset_cursor(sel_node);
|
||||
/* } else {
|
||||
err = DB_ERROR;
|
||||
} */
|
||||
} else {
|
||||
if (sel_node->state != SEL_NODE_CLOSED) {
|
||||
|
||||
sel_node->state = SEL_NODE_CLOSED;
|
||||
} else {
|
||||
err = DB_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
/* SQL error detected */
|
||||
fprintf(stderr, "SQL error %lu\n", (ulong) err);
|
||||
|
||||
ut_error;
|
||||
}
|
||||
|
||||
thr->run_node = que_node_get_parent(node);
|
||||
|
||||
return(thr);
|
||||
}
|
||||
|
||||
|
||||
/** Searches for rows in the database. This is used in the interface to
|
||||
MySQL. This function opens a cursor, and also implements fetch next
|
||||
and fetch prev. NOTE that if we do a search with a full key value
|
||||
from a unique index (ROW_SEL_EXACT), then we will not store the cursor
|
||||
position and fetch next or fetch prev must not be tried to the cursor!
|
||||
|
||||
@param[out] buf buffer for the fetched row in MySQL format
|
||||
@param[in] mode search mode PAGE_CUR_L
|
||||
@param[in,out] prebuilt prebuilt struct for the table handler;
|
||||
this contains the info to search_tuple,
|
||||
index; if search tuple contains 0 field then
|
||||
we position the cursor at start or the end of
|
||||
index, depending on 'mode'
|
||||
@param[in] match_mode 0 or ROW_SEL_EXACT or ROW_SEL_EXACT_PREFIX
|
||||
@param[in] direction 0 or ROW_SEL_NEXT or ROW_SEL_PREV;
|
||||
Note: if this is != 0, then prebuilt must has a
|
||||
pcur with stored position! In opening of a
|
||||
cursor 'direction' should be 0.
|
||||
@return DB_SUCCESS, DB_RECORD_NOT_FOUND, DB_END_OF_INDEX, DB_DEADLOCK,
|
||||
DB_LOCK_TABLE_FULL, DB_CORRUPTION, or DB_TOO_BIG_RECORD */
|
||||
UNIV_INLINE
|
||||
dberr_t
|
||||
row_search_for_mysql(
|
||||
byte* buf,
|
||||
page_cur_mode_t mode,
|
||||
row_prebuilt_t* prebuilt,
|
||||
ulint match_mode,
|
||||
ulint direction)
|
||||
{
|
||||
return(row_search_mvcc(buf, mode, prebuilt, match_mode, direction));
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*****************************************************************************
|
||||
|
||||
Copyright (c) 1996, 2018, Oracle and/or its affiliates. All Rights Reserved.
|
||||
Copyright (c) 2017, 2020, MariaDB Corporation.
|
||||
Copyright (c) 2017, 2022, MariaDB Corporation.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
@@ -118,14 +118,6 @@ row_upd_changes_field_size_or_external(
|
||||
dict_index_t* index, /*!< in: index */
|
||||
const rec_offs* offsets,/*!< in: rec_get_offsets(rec, index) */
|
||||
const upd_t* update);/*!< in: update vector */
|
||||
/***********************************************************//**
|
||||
Returns true if row update contains disowned external fields.
|
||||
@return true if the update contains disowned external fields. */
|
||||
bool
|
||||
row_upd_changes_disowned_external(
|
||||
/*==============================*/
|
||||
const upd_t* update) /*!< in: update vector */
|
||||
MY_ATTRIBUTE((nonnull, warn_unused_result));
|
||||
|
||||
/***************************************************************//**
|
||||
Builds an update vector from those fields which in a secondary index entry
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*****************************************************************************
|
||||
|
||||
Copyright (c) 1997, 2016, Oracle and/or its affiliates. All Rights Reserved.
|
||||
Copyright (c) 2017, 2019, MariaDB Corporation.
|
||||
Copyright (c) 2017, 2022, MariaDB Corporation.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
@@ -55,7 +55,7 @@ row_vers_impl_x_locked(
|
||||
const rec_offs* offsets);
|
||||
|
||||
/** Finds out if a version of the record, where the version >= the current
|
||||
purge view, should have ientry as its secondary index entry. We check
|
||||
purge_sys.view, should have ientry as its secondary index entry. We check
|
||||
if there is any not delete marked version of the record where the trx
|
||||
id >= purge view, and the secondary index entry == ientry; exactly in
|
||||
this case we return TRUE.
|
||||
@@ -85,7 +85,9 @@ row_vers_old_has_index_entry(
|
||||
Constructs the version of a clustered index record which a consistent
|
||||
read should see. We assume that the trx id stored in rec is such that
|
||||
the consistent read should not see rec in its present version.
|
||||
@return DB_SUCCESS or DB_MISSING_HISTORY */
|
||||
@return error code
|
||||
@retval DB_SUCCESS if a previous version was fetched
|
||||
@retval DB_MISSING_HISTORY if the history is missing (a sign of corruption) */
|
||||
dberr_t
|
||||
row_vers_build_for_consistent_read(
|
||||
/*===============================*/
|
||||
|
||||
@@ -24,8 +24,7 @@ Purge old versions
|
||||
Created 3/26/1996 Heikki Tuuri
|
||||
*******************************************************/
|
||||
|
||||
#ifndef trx0purge_h
|
||||
#define trx0purge_h
|
||||
#pragma once
|
||||
|
||||
#include "trx0sys.h"
|
||||
#include "que0types.h"
|
||||
@@ -123,7 +122,8 @@ public:
|
||||
/** latch protecting view, m_enabled */
|
||||
alignas(CPU_LEVEL1_DCACHE_LINESIZE) mutable srw_spin_lock latch;
|
||||
private:
|
||||
/** The purge will not remove undo logs which are >= this view */
|
||||
/** Read view at the start of a purge batch. Any encountered index records
|
||||
that are older than view will be removed. */
|
||||
ReadViewBase view;
|
||||
/** whether purge is enabled; protected by latch and std::atomic */
|
||||
std::atomic<bool> m_enabled;
|
||||
@@ -133,6 +133,12 @@ private:
|
||||
Atomic_counter<uint32_t> m_SYS_paused;
|
||||
/** number of stop_FTS() calls without resume_FTS() */
|
||||
Atomic_counter<uint32_t> m_FTS_paused;
|
||||
|
||||
/** latch protecting end_view */
|
||||
alignas(CPU_LEVEL1_DCACHE_LINESIZE) srw_spin_lock_low end_latch;
|
||||
/** Read view at the end of a purge batch (copied from view). Any undo pages
|
||||
containing records older than end_view may be freed. */
|
||||
ReadViewBase end_view;
|
||||
public:
|
||||
que_t* query; /*!< The query graph which will do the
|
||||
parallelized purge operation */
|
||||
@@ -261,28 +267,56 @@ public:
|
||||
/** check stop_SYS() */
|
||||
void check_stop_FTS() { if (must_wait_FTS()) wait_FTS(); }
|
||||
|
||||
/** A wrapper around ReadView::changes_visible(). */
|
||||
bool changes_visible(trx_id_t id, const table_name_t &name) const
|
||||
{
|
||||
return view.changes_visible(id, name);
|
||||
}
|
||||
/** Determine if the history of a transaction is purgeable.
|
||||
@param trx_id transaction identifier
|
||||
@return whether the history is purgeable */
|
||||
TRANSACTIONAL_TARGET bool is_purgeable(trx_id_t trx_id) const;
|
||||
|
||||
/** A wrapper around ReadView::low_limit_no(). */
|
||||
trx_id_t low_limit_no() const
|
||||
{
|
||||
/* Other callers than purge_coordinator_callback() must be holding
|
||||
purge_sys.latch here. The purge coordinator task may call this
|
||||
without holding any latch, because it is the only thread that may
|
||||
modify purge_sys.view. */
|
||||
/* This function may only be called by purge_coordinator_callback().
|
||||
|
||||
The purge coordinator task may call this without holding any latch,
|
||||
because it is the only thread that may modify purge_sys.view.
|
||||
|
||||
Any other threads that access purge_sys.view must hold purge_sys.latch,
|
||||
typically via purge_sys_t::view_guard. */
|
||||
return view.low_limit_no();
|
||||
}
|
||||
/** A wrapper around trx_sys_t::clone_oldest_view(). */
|
||||
template<bool also_end_view= false>
|
||||
void clone_oldest_view()
|
||||
{
|
||||
latch.wr_lock(SRW_LOCK_CALL);
|
||||
trx_sys.clone_oldest_view(&view);
|
||||
if (also_end_view)
|
||||
(end_view= view).
|
||||
clamp_low_limit_id(head.trx_no ? head.trx_no : tail.trx_no);
|
||||
latch.wr_unlock();
|
||||
}
|
||||
|
||||
/** Update end_view at the end of a purge batch. */
|
||||
inline void clone_end_view();
|
||||
|
||||
struct view_guard
|
||||
{
|
||||
inline view_guard();
|
||||
inline ~view_guard();
|
||||
|
||||
/** @return purge_sys.view */
|
||||
inline const ReadViewBase &view() const;
|
||||
};
|
||||
|
||||
struct end_view_guard
|
||||
{
|
||||
inline end_view_guard();
|
||||
inline ~end_view_guard();
|
||||
|
||||
/** @return purge_sys.end_view */
|
||||
inline const ReadViewBase &view() const;
|
||||
};
|
||||
|
||||
/** Stop the purge thread and check n_ref_count of all auxiliary
|
||||
and common table associated with the fts table.
|
||||
@param table parent FTS table
|
||||
@@ -294,4 +328,20 @@ public:
|
||||
/** The global data structure coordinating a purge */
|
||||
extern purge_sys_t purge_sys;
|
||||
|
||||
#endif /* trx0purge_h */
|
||||
purge_sys_t::view_guard::view_guard()
|
||||
{ purge_sys.latch.rd_lock(SRW_LOCK_CALL); }
|
||||
|
||||
purge_sys_t::view_guard::~view_guard()
|
||||
{ purge_sys.latch.rd_unlock(); }
|
||||
|
||||
const ReadViewBase &purge_sys_t::view_guard::view() const
|
||||
{ return purge_sys.view; }
|
||||
|
||||
purge_sys_t::end_view_guard::end_view_guard()
|
||||
{ purge_sys.end_latch.rd_lock(); }
|
||||
|
||||
purge_sys_t::end_view_guard::~end_view_guard()
|
||||
{ purge_sys.end_latch.rd_unlock(); }
|
||||
|
||||
const ReadViewBase &purge_sys_t::end_view_guard::view() const
|
||||
{ return purge_sys.end_view; }
|
||||
|
||||
@@ -181,17 +181,17 @@ trx_undo_report_row_operation(
|
||||
is being called purge view and we would like to get the purge record
|
||||
even it is in the purge view (in normal case, it will return without
|
||||
fetching the purge record */
|
||||
#define TRX_UNDO_PREV_IN_PURGE 0x1
|
||||
static constexpr ulint TRX_UNDO_PREV_IN_PURGE = 1;
|
||||
|
||||
/** This tells trx_undo_prev_version_build() to fetch the old value in
|
||||
the undo log (which is the after image for an update) */
|
||||
#define TRX_UNDO_GET_OLD_V_VALUE 0x2
|
||||
static constexpr ulint TRX_UNDO_GET_OLD_V_VALUE = 2;
|
||||
|
||||
/** indicate a call from row_vers_old_has_index_entry() */
|
||||
static constexpr ulint TRX_UNDO_CHECK_PURGEABILITY = 4;
|
||||
|
||||
/** Build a previous version of a clustered index record. The caller
|
||||
must hold a latch on the index page of the clustered index record.
|
||||
@param index_rec clustered index record in the index tree
|
||||
@param index_mtr mtr which contains the latch to index_rec page
|
||||
and purge_view
|
||||
@param rec version of a clustered index record
|
||||
@param index clustered index
|
||||
@param offsets rec_get_offsets(rec, index)
|
||||
@@ -210,14 +210,12 @@ must hold a latch on the index page of the clustered index record.
|
||||
@param v_status status determine if it is going into this
|
||||
function by purge thread or not.
|
||||
And if we read "after image" of undo log
|
||||
@retval true if previous version was built, or if it was an insert
|
||||
or the table has been rebuilt
|
||||
@retval false if the previous version is earlier than purge_view,
|
||||
or being purged, which means that it may have been removed */
|
||||
bool
|
||||
@return error code
|
||||
@retval DB_SUCCESS if previous version was successfully built,
|
||||
or if it was an insert or the undo record refers to the table before rebuild
|
||||
@retval DB_MISSING_HISTORY if the history is missing */
|
||||
dberr_t
|
||||
trx_undo_prev_version_build(
|
||||
const rec_t *index_rec,
|
||||
mtr_t *index_mtr,
|
||||
const rec_t *rec,
|
||||
dict_index_t *index,
|
||||
rec_offs *offsets,
|
||||
|
||||
@@ -44,6 +44,7 @@ Created 5/7/1996 Heikki Tuuri
|
||||
#include "row0vers.h"
|
||||
#include "pars0pars.h"
|
||||
#include "srv0mon.h"
|
||||
#include "que0que.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*****************************************************************************
|
||||
|
||||
Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved.
|
||||
Copyright (c) 2017, 2021, MariaDB Corporation.
|
||||
Copyright (c) 2017, 2022, MariaDB Corporation.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
@@ -566,6 +566,27 @@ que_node_type_string(
|
||||
}
|
||||
#endif /* DBUG_TRACE */
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
Performs an execution step of an open or close cursor statement node.
|
||||
@param thr query thread */
|
||||
static void open_step(que_thr_t *thr)
|
||||
{
|
||||
open_node_t *node= static_cast<open_node_t*>(thr->run_node);
|
||||
ut_ad(que_node_get_type(node) == QUE_NODE_OPEN);
|
||||
sel_node_t *sel_node= node->cursor_def;
|
||||
|
||||
if (node->op_type == ROW_SEL_OPEN_CURSOR)
|
||||
sel_node->state= SEL_NODE_OPEN;
|
||||
else
|
||||
{
|
||||
ut_ad(sel_node->state != SEL_NODE_CLOSED);
|
||||
sel_node->state= SEL_NODE_CLOSED;
|
||||
}
|
||||
|
||||
thr->run_node= que_node_get_parent(node);
|
||||
}
|
||||
|
||||
/**********************************************************************//**
|
||||
Performs an execution step on a query thread.
|
||||
@return query thread to run next: it may differ from the input
|
||||
@@ -636,7 +657,7 @@ que_thr_step(
|
||||
} else if (type == QUE_NODE_FETCH) {
|
||||
thr = fetch_step(thr);
|
||||
} else if (type == QUE_NODE_OPEN) {
|
||||
thr = open_step(thr);
|
||||
open_step(thr);
|
||||
} else if (type == QUE_NODE_FUNC) {
|
||||
proc_eval_step(thr);
|
||||
|
||||
|
||||
@@ -3842,9 +3842,8 @@ UndorecApplier::get_old_rec(const dtuple_t &tuple, dict_index_t *index,
|
||||
ut_ad(len == DATA_ROLL_PTR_LEN);
|
||||
if (is_same(roll_ptr))
|
||||
return version;
|
||||
trx_undo_prev_version_build(*clust_rec, &mtr, version, index,
|
||||
*offsets, heap, &prev_version, nullptr,
|
||||
nullptr, 0);
|
||||
trx_undo_prev_version_build(version, index, *offsets, heap, &prev_version,
|
||||
nullptr, nullptr, 0);
|
||||
version= prev_version;
|
||||
}
|
||||
while (version);
|
||||
@@ -4014,9 +4013,8 @@ void UndorecApplier::log_update(const dtuple_t &tuple,
|
||||
if (match_rec == rec)
|
||||
copy_rec= rec_copy(mem_heap_alloc(
|
||||
heap, rec_offs_size(offsets)), match_rec, offsets);
|
||||
trx_undo_prev_version_build(rec, &mtr, match_rec, clust_index,
|
||||
offsets, heap, &prev_version, nullptr,
|
||||
nullptr, 0);
|
||||
trx_undo_prev_version_build(match_rec, clust_index, offsets, heap,
|
||||
&prev_version, nullptr, nullptr, 0);
|
||||
|
||||
prev_offsets= rec_get_offsets(prev_version, clust_index, prev_offsets,
|
||||
clust_index->n_core_fields,
|
||||
|
||||
@@ -2124,8 +2124,14 @@ end_of_index:
|
||||
ut_ad(trx->read_view.is_open());
|
||||
ut_ad(rec_trx_id != trx->id);
|
||||
|
||||
if (!trx->read_view.changes_visible(
|
||||
rec_trx_id, old_table->name)) {
|
||||
if (!trx->read_view.changes_visible(rec_trx_id)) {
|
||||
if (rec_trx_id
|
||||
>= trx->read_view.low_limit_id()
|
||||
&& rec_trx_id
|
||||
>= trx_sys.get_max_trx_id()) {
|
||||
goto corrupted_rec;
|
||||
}
|
||||
|
||||
rec_t* old_vers;
|
||||
|
||||
row_vers_build_for_consistent_read(
|
||||
@@ -4412,9 +4418,7 @@ row_merge_is_index_usable(
|
||||
&& (index->table->is_temporary() || index->table->no_rollback()
|
||||
|| index->trx_id == 0
|
||||
|| !trx->read_view.is_open()
|
||||
|| trx->read_view.changes_visible(
|
||||
index->trx_id,
|
||||
index->table->name)));
|
||||
|| trx->read_view.changes_visible(index->trx_id)));
|
||||
}
|
||||
|
||||
/** Build indexes on a table by reading a clustered index, creating a temporary
|
||||
|
||||
@@ -1766,7 +1766,7 @@ error:
|
||||
|
||||
/** This can only be used when the current transaction is at
|
||||
READ COMMITTED or READ UNCOMMITTED isolation level.
|
||||
Before calling this function row_search_for_mysql() must have
|
||||
Before calling this function row_search_mvcc() must have
|
||||
initialized prebuilt->new_rec_locks to store the information which new
|
||||
record locks really were set. This function removes a newly set
|
||||
clustered index record lock under prebuilt->pcur or
|
||||
@@ -2937,182 +2937,3 @@ funct_exit:
|
||||
|
||||
return(err);
|
||||
}
|
||||
|
||||
/*********************************************************************//**
|
||||
Scans an index for either COUNT(*) or CHECK TABLE.
|
||||
If CHECK TABLE; Checks that the index contains entries in an ascending order,
|
||||
unique constraint is not broken, and calculates the number of index entries
|
||||
in the read view of the current transaction.
|
||||
@return DB_SUCCESS or other error */
|
||||
dberr_t
|
||||
row_scan_index_for_mysql(
|
||||
/*=====================*/
|
||||
row_prebuilt_t* prebuilt, /*!< in: prebuilt struct
|
||||
in MySQL handle */
|
||||
const dict_index_t* index, /*!< in: index */
|
||||
ulint* n_rows) /*!< out: number of entries
|
||||
seen in the consistent read */
|
||||
{
|
||||
dtuple_t* prev_entry = NULL;
|
||||
ulint matched_fields;
|
||||
byte* buf;
|
||||
dberr_t ret;
|
||||
rec_t* rec;
|
||||
int cmp;
|
||||
ibool contains_null;
|
||||
ulint i;
|
||||
ulint cnt;
|
||||
mem_heap_t* heap = NULL;
|
||||
rec_offs offsets_[REC_OFFS_NORMAL_SIZE];
|
||||
rec_offs* offsets;
|
||||
rec_offs_init(offsets_);
|
||||
|
||||
*n_rows = 0;
|
||||
|
||||
/* Don't support RTree Leaf level scan */
|
||||
ut_ad(!dict_index_is_spatial(index));
|
||||
|
||||
if (dict_index_is_clust(index)) {
|
||||
/* The clustered index of a table is always available.
|
||||
During online ALTER TABLE that rebuilds the table, the
|
||||
clustered index in the old table will have
|
||||
index->online_log pointing to the new table. All
|
||||
indexes of the old table will remain valid and the new
|
||||
table will be unaccessible to MySQL until the
|
||||
completion of the ALTER TABLE. */
|
||||
} else if (dict_index_is_online_ddl(index)
|
||||
|| (index->type & DICT_FTS)) {
|
||||
/* Full Text index are implemented by auxiliary tables,
|
||||
not the B-tree. We also skip secondary indexes that are
|
||||
being created online. */
|
||||
return(DB_SUCCESS);
|
||||
}
|
||||
|
||||
ulint bufsize = std::max<ulint>(srv_page_size,
|
||||
prebuilt->mysql_row_len);
|
||||
buf = static_cast<byte*>(ut_malloc_nokey(bufsize));
|
||||
heap = mem_heap_create(100);
|
||||
|
||||
cnt = 1000;
|
||||
|
||||
ret = row_search_for_mysql(buf, PAGE_CUR_G, prebuilt, 0, 0);
|
||||
loop:
|
||||
/* Check thd->killed every 1,000 scanned rows */
|
||||
if (--cnt == 0) {
|
||||
if (trx_is_interrupted(prebuilt->trx)) {
|
||||
ret = DB_INTERRUPTED;
|
||||
goto func_exit;
|
||||
}
|
||||
cnt = 1000;
|
||||
}
|
||||
|
||||
switch (ret) {
|
||||
case DB_SUCCESS:
|
||||
break;
|
||||
case DB_DEADLOCK:
|
||||
case DB_LOCK_TABLE_FULL:
|
||||
case DB_LOCK_WAIT_TIMEOUT:
|
||||
case DB_INTERRUPTED:
|
||||
goto func_exit;
|
||||
default:
|
||||
ib::warn() << "CHECK TABLE on index " << index->name << " of"
|
||||
" table " << index->table->name << " returned " << ret;
|
||||
/* (this error is ignored by CHECK TABLE) */
|
||||
/* fall through */
|
||||
case DB_END_OF_INDEX:
|
||||
ret = DB_SUCCESS;
|
||||
func_exit:
|
||||
ut_free(buf);
|
||||
mem_heap_free(heap);
|
||||
|
||||
return(ret);
|
||||
}
|
||||
|
||||
*n_rows = *n_rows + 1;
|
||||
|
||||
/* else this code is doing handler::check() for CHECK TABLE */
|
||||
|
||||
/* row_search... returns the index record in buf, record origin offset
|
||||
within buf stored in the first 4 bytes, because we have built a dummy
|
||||
template */
|
||||
|
||||
rec = buf + mach_read_from_4(buf);
|
||||
|
||||
offsets = rec_get_offsets(rec, index, offsets_, index->n_core_fields,
|
||||
ULINT_UNDEFINED, &heap);
|
||||
|
||||
if (prev_entry != NULL) {
|
||||
matched_fields = 0;
|
||||
|
||||
cmp = cmp_dtuple_rec_with_match(prev_entry, rec, offsets,
|
||||
&matched_fields);
|
||||
contains_null = FALSE;
|
||||
|
||||
/* In a unique secondary index we allow equal key values if
|
||||
they contain SQL NULLs */
|
||||
|
||||
for (i = 0;
|
||||
i < dict_index_get_n_ordering_defined_by_user(index);
|
||||
i++) {
|
||||
if (UNIV_SQL_NULL == dfield_get_len(
|
||||
dtuple_get_nth_field(prev_entry, i))) {
|
||||
|
||||
contains_null = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const char* msg;
|
||||
|
||||
if (cmp > 0) {
|
||||
ret = DB_INDEX_CORRUPT;
|
||||
msg = "index records in a wrong order in ";
|
||||
not_ok:
|
||||
ib::error()
|
||||
<< msg << index->name
|
||||
<< " of table " << index->table->name
|
||||
<< ": " << *prev_entry << ", "
|
||||
<< rec_offsets_print(rec, offsets);
|
||||
/* Continue reading */
|
||||
} else if (dict_index_is_unique(index)
|
||||
&& !contains_null
|
||||
&& matched_fields
|
||||
>= dict_index_get_n_ordering_defined_by_user(
|
||||
index)) {
|
||||
ret = DB_DUPLICATE_KEY;
|
||||
msg = "duplicate key in ";
|
||||
goto not_ok;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
mem_heap_t* tmp_heap = NULL;
|
||||
|
||||
/* Empty the heap on each round. But preserve offsets[]
|
||||
for the row_rec_to_index_entry() call, by copying them
|
||||
into a separate memory heap when needed. */
|
||||
if (UNIV_UNLIKELY(offsets != offsets_)) {
|
||||
ulint size = rec_offs_get_n_alloc(offsets)
|
||||
* sizeof *offsets;
|
||||
|
||||
tmp_heap = mem_heap_create(size);
|
||||
|
||||
offsets = static_cast<rec_offs*>(
|
||||
mem_heap_dup(tmp_heap, offsets, size));
|
||||
}
|
||||
|
||||
mem_heap_empty(heap);
|
||||
|
||||
prev_entry = row_rec_to_index_entry(
|
||||
rec, index, offsets, heap);
|
||||
|
||||
if (UNIV_LIKELY_NULL(tmp_heap)) {
|
||||
mem_heap_free(tmp_heap);
|
||||
}
|
||||
}
|
||||
|
||||
ret = row_search_for_mysql(
|
||||
buf, PAGE_CUR_G, prebuilt, 0, ROW_SEL_NEXT);
|
||||
|
||||
goto loop;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -216,26 +216,23 @@ static ulint row_trx_id_offset(const rec_t* rec, const dict_index_t* index)
|
||||
}
|
||||
|
||||
/** Determine if rollback must execute a purge-like operation.
|
||||
@param[in,out] node row undo
|
||||
@param[in,out] mtr mini-transaction
|
||||
@param node row undo
|
||||
@return whether the record should be purged */
|
||||
static bool row_undo_mod_must_purge(undo_node_t* node, mtr_t* mtr)
|
||||
static bool row_undo_mod_must_purge(const undo_node_t &node)
|
||||
{
|
||||
ut_ad(node->rec_type == TRX_UNDO_UPD_DEL_REC);
|
||||
ut_ad(!node->table->is_temporary());
|
||||
ut_ad(node.rec_type == TRX_UNDO_UPD_DEL_REC);
|
||||
ut_ad(!node.table->is_temporary());
|
||||
|
||||
btr_cur_t* btr_cur = btr_pcur_get_btr_cur(&node->pcur);
|
||||
ut_ad(btr_cur->index->is_primary());
|
||||
DEBUG_SYNC_C("rollback_purge_clust");
|
||||
const btr_cur_t &btr_cur= node.pcur.btr_cur;
|
||||
ut_ad(btr_cur.index->is_primary());
|
||||
DEBUG_SYNC_C("rollback_purge_clust");
|
||||
|
||||
if (!purge_sys.changes_visible(node->new_trx_id, node->table->name)) {
|
||||
return false;
|
||||
}
|
||||
if (!purge_sys.is_purgeable(node.new_trx_id))
|
||||
return false;
|
||||
|
||||
const rec_t* rec = btr_cur_get_rec(btr_cur);
|
||||
|
||||
return trx_read_trx_id(rec + row_trx_id_offset(rec, btr_cur->index))
|
||||
== node->new_trx_id;
|
||||
const rec_t *rec= btr_cur_get_rec(&btr_cur);
|
||||
return trx_read_trx_id(rec + row_trx_id_offset(rec, btr_cur.index)) ==
|
||||
node.new_trx_id;
|
||||
}
|
||||
|
||||
/***********************************************************//**
|
||||
@@ -251,7 +248,6 @@ row_undo_mod_clust(
|
||||
{
|
||||
btr_pcur_t* pcur;
|
||||
mtr_t mtr;
|
||||
bool have_latch = false;
|
||||
dberr_t err;
|
||||
dict_index_t* index;
|
||||
|
||||
@@ -347,9 +343,7 @@ row_undo_mod_clust(
|
||||
btr_pcur_commit_specify_mtr(pcur, &mtr);
|
||||
} else {
|
||||
index->set_modified(mtr);
|
||||
have_latch = true;
|
||||
purge_sys.latch.rd_lock(SRW_LOCK_CALL);
|
||||
if (!row_undo_mod_must_purge(node, &mtr)) {
|
||||
if (!row_undo_mod_must_purge(*node)) {
|
||||
goto mtr_commit_exit;
|
||||
}
|
||||
err = btr_cur_optimistic_delete(&pcur->btr_cur, 0,
|
||||
@@ -358,9 +352,7 @@ row_undo_mod_clust(
|
||||
goto mtr_commit_exit;
|
||||
}
|
||||
err = DB_SUCCESS;
|
||||
purge_sys.latch.rd_unlock();
|
||||
btr_pcur_commit_specify_mtr(pcur, &mtr);
|
||||
have_latch = false;
|
||||
}
|
||||
|
||||
mtr.start();
|
||||
@@ -376,9 +368,7 @@ row_undo_mod_clust(
|
||||
if (index->table->is_temporary()) {
|
||||
mtr.set_log_mode(MTR_LOG_NO_REDO);
|
||||
} else {
|
||||
have_latch = true;
|
||||
purge_sys.latch.rd_lock(SRW_LOCK_CALL);
|
||||
if (!row_undo_mod_must_purge(node, &mtr)) {
|
||||
if (!row_undo_mod_must_purge(*node)) {
|
||||
goto mtr_commit_exit;
|
||||
}
|
||||
index->set_modified(mtr);
|
||||
@@ -400,17 +390,12 @@ row_undo_mod_clust(
|
||||
|
||||
mtr.start();
|
||||
if (pcur->restore_position(BTR_MODIFY_LEAF, &mtr)
|
||||
!= btr_pcur_t::SAME_ALL) {
|
||||
goto mtr_commit_exit;
|
||||
}
|
||||
rec_t* rec = btr_pcur_get_rec(pcur);
|
||||
have_latch = true;
|
||||
purge_sys.latch.rd_lock(SRW_LOCK_CALL);
|
||||
if (!purge_sys.changes_visible(node->new_trx_id,
|
||||
node->table->name)) {
|
||||
!= btr_pcur_t::SAME_ALL
|
||||
|| !purge_sys.is_purgeable(node->new_trx_id)) {
|
||||
goto mtr_commit_exit;
|
||||
}
|
||||
|
||||
rec_t* rec = btr_pcur_get_rec(pcur);
|
||||
ulint trx_id_offset = index->trx_id_offset;
|
||||
ulint trx_id_pos = index->n_uniq ? index->n_uniq : 1;
|
||||
/* Reserve enough offsets for the PRIMARY KEY and
|
||||
@@ -477,10 +462,6 @@ row_undo_mod_clust(
|
||||
}
|
||||
|
||||
mtr_commit_exit:
|
||||
if (have_latch) {
|
||||
purge_sys.latch.rd_unlock();
|
||||
}
|
||||
|
||||
btr_pcur_commit_specify_mtr(pcur, &mtr);
|
||||
|
||||
func_exit:
|
||||
|
||||
@@ -469,46 +469,6 @@ row_upd_changes_field_size_or_external(
|
||||
return(FALSE);
|
||||
}
|
||||
|
||||
/***********************************************************//**
|
||||
Returns true if row update contains disowned external fields.
|
||||
@return true if the update contains disowned external fields. */
|
||||
bool
|
||||
row_upd_changes_disowned_external(
|
||||
/*==============================*/
|
||||
const upd_t* update) /*!< in: update vector */
|
||||
{
|
||||
const upd_field_t* upd_field;
|
||||
const dfield_t* new_val;
|
||||
ulint new_len;
|
||||
ulint n_fields;
|
||||
ulint i;
|
||||
|
||||
n_fields = upd_get_n_fields(update);
|
||||
|
||||
for (i = 0; i < n_fields; i++) {
|
||||
const byte* field_ref;
|
||||
|
||||
upd_field = upd_get_nth_field(update, i);
|
||||
new_val = &(upd_field->new_val);
|
||||
new_len = dfield_get_len(new_val);
|
||||
|
||||
if (!dfield_is_ext(new_val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ut_ad(new_len >= BTR_EXTERN_FIELD_REF_SIZE);
|
||||
|
||||
field_ref = static_cast<const byte*>(dfield_get_data(new_val))
|
||||
+ new_len - BTR_EXTERN_FIELD_REF_SIZE;
|
||||
|
||||
if (field_ref[BTR_EXTERN_LEN] & BTR_EXTERN_OWNER_FLAG) {
|
||||
return(true);
|
||||
}
|
||||
}
|
||||
|
||||
return(false);
|
||||
}
|
||||
|
||||
/***************************************************************//**
|
||||
Builds an update vector from those fields which in a secondary index entry
|
||||
differ from a record that has the equal ordering fields. NOTE: we compare
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*****************************************************************************
|
||||
|
||||
Copyright (c) 1997, 2017, Oracle and/or its affiliates. All Rights Reserved.
|
||||
Copyright (c) 2017, 2021, MariaDB Corporation.
|
||||
Copyright (c) 2017, 2022, MariaDB Corporation.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
@@ -104,6 +104,9 @@ row_vers_impl_x_locked_low(
|
||||
DBUG_ENTER("row_vers_impl_x_locked_low");
|
||||
|
||||
ut_ad(rec_offs_validate(rec, index, offsets));
|
||||
ut_ad(mtr->memo_contains_page_flagged(clust_rec,
|
||||
MTR_MEMO_PAGE_S_FIX
|
||||
| MTR_MEMO_PAGE_X_FIX));
|
||||
|
||||
if (ulint trx_id_offset = clust_index->trx_id_offset) {
|
||||
trx_id = mach_read_from_6(clust_rec + trx_id_offset);
|
||||
@@ -190,7 +193,7 @@ row_vers_impl_x_locked_low(
|
||||
heap = mem_heap_create(1024);
|
||||
|
||||
trx_undo_prev_version_build(
|
||||
clust_rec, mtr, version, clust_index, clust_offsets,
|
||||
version, clust_index, clust_offsets,
|
||||
heap, &prev_version, NULL,
|
||||
dict_index_has_virtual(index) ? &vrow : NULL, 0);
|
||||
|
||||
@@ -527,6 +530,10 @@ row_vers_build_cur_vrow_low(
|
||||
= DATA_MISSING;
|
||||
}
|
||||
|
||||
ut_ad(mtr->memo_contains_page_flagged(rec,
|
||||
MTR_MEMO_PAGE_S_FIX
|
||||
| MTR_MEMO_PAGE_X_FIX));
|
||||
|
||||
version = rec;
|
||||
|
||||
/* If this is called by purge thread, set TRX_UNDO_PREV_IN_PURGE
|
||||
@@ -543,7 +550,7 @@ row_vers_build_cur_vrow_low(
|
||||
version, clust_index, clust_offsets);
|
||||
|
||||
trx_undo_prev_version_build(
|
||||
rec, mtr, version, clust_index, clust_offsets,
|
||||
version, clust_index, clust_offsets,
|
||||
heap, &prev_version, NULL, vrow, status);
|
||||
|
||||
if (heap2) {
|
||||
@@ -643,6 +650,10 @@ row_vers_vc_matches_cluster(
|
||||
/* First compare non-virtual columns (primary keys) */
|
||||
ut_ad(index->n_fields == n_fields);
|
||||
ut_ad(n_fields == dtuple_get_n_fields(icentry));
|
||||
ut_ad(mtr->memo_contains_page_flagged(rec,
|
||||
MTR_MEMO_PAGE_S_FIX
|
||||
| MTR_MEMO_PAGE_X_FIX));
|
||||
|
||||
{
|
||||
const dfield_t* a = ientry->fields;
|
||||
const dfield_t* b = icentry->fields;
|
||||
@@ -684,7 +695,7 @@ row_vers_vc_matches_cluster(
|
||||
ut_ad(roll_ptr != 0);
|
||||
|
||||
trx_undo_prev_version_build(
|
||||
rec, mtr, version, clust_index, clust_offsets,
|
||||
version, clust_index, clust_offsets,
|
||||
heap, &prev_version, NULL, vrow,
|
||||
TRX_UNDO_PREV_IN_PURGE | TRX_UNDO_GET_OLD_V_VALUE);
|
||||
|
||||
@@ -834,7 +845,7 @@ row_vers_build_cur_vrow(
|
||||
}
|
||||
|
||||
/** Finds out if a version of the record, where the version >= the current
|
||||
purge view, should have ientry as its secondary index entry. We check
|
||||
purge_sys.view, should have ientry as its secondary index entry. We check
|
||||
if there is any not delete marked version of the record where the trx
|
||||
id >= purge view, and the secondary index entry == ientry; exactly in
|
||||
this case we return TRUE.
|
||||
@@ -1016,11 +1027,12 @@ unsafe_to_purge:
|
||||
heap = mem_heap_create(1024);
|
||||
vrow = NULL;
|
||||
|
||||
trx_undo_prev_version_build(rec, mtr, version,
|
||||
trx_undo_prev_version_build(version,
|
||||
clust_index, clust_offsets,
|
||||
heap, &prev_version, NULL,
|
||||
heap, &prev_version, nullptr,
|
||||
dict_index_has_virtual(index)
|
||||
? &vrow : NULL, 0);
|
||||
? &vrow : nullptr,
|
||||
TRX_UNDO_CHECK_PURGEABILITY);
|
||||
mem_heap_free(heap2); /* free version and clust_offsets */
|
||||
|
||||
if (!prev_version) {
|
||||
@@ -1099,7 +1111,9 @@ unsafe_to_purge:
|
||||
Constructs the version of a clustered index record which a consistent
|
||||
read should see. We assume that the trx id stored in rec is such that
|
||||
the consistent read should not see rec in its present version.
|
||||
@return DB_SUCCESS or DB_MISSING_HISTORY */
|
||||
@return error code
|
||||
@retval DB_SUCCESS if a previous version was fetched
|
||||
@retval DB_MISSING_HISTORY if the history is missing (a sign of corruption) */
|
||||
dberr_t
|
||||
row_vers_build_for_consistent_read(
|
||||
/*===============================*/
|
||||
@@ -1139,7 +1153,7 @@ row_vers_build_for_consistent_read(
|
||||
|
||||
trx_id = row_get_rec_trx_id(rec, index, *offsets);
|
||||
|
||||
ut_ad(!view->changes_visible(trx_id, index->table->name));
|
||||
ut_ad(!view->changes_visible(trx_id));
|
||||
|
||||
ut_ad(!vrow || !(*vrow));
|
||||
|
||||
@@ -1157,12 +1171,10 @@ row_vers_build_for_consistent_read(
|
||||
/* If purge can't see the record then we can't rely on
|
||||
the UNDO log record. */
|
||||
|
||||
bool purge_sees = trx_undo_prev_version_build(
|
||||
rec, mtr, version, index, *offsets, heap,
|
||||
err = trx_undo_prev_version_build(
|
||||
version, index, *offsets, heap,
|
||||
&prev_version, NULL, vrow, 0);
|
||||
|
||||
err = (purge_sees) ? DB_SUCCESS : DB_MISSING_HISTORY;
|
||||
|
||||
if (prev_heap != NULL) {
|
||||
mem_heap_free(prev_heap);
|
||||
}
|
||||
@@ -1184,7 +1196,7 @@ row_vers_build_for_consistent_read(
|
||||
|
||||
trx_id = row_get_rec_trx_id(prev_version, index, *offsets);
|
||||
|
||||
if (view->changes_visible(trx_id, index->table->name)) {
|
||||
if (view->changes_visible(trx_id)) {
|
||||
|
||||
/* The view already sees this version: we can copy
|
||||
it to in_heap and return */
|
||||
@@ -1201,8 +1213,11 @@ row_vers_build_for_consistent_read(
|
||||
dtuple_dup_v_fld(*vrow, in_heap);
|
||||
}
|
||||
break;
|
||||
} else if (trx_id >= view->low_limit_id()
|
||||
&& trx_id >= trx_sys.get_max_trx_id()) {
|
||||
err = DB_CORRUPTION;
|
||||
break;
|
||||
}
|
||||
|
||||
version = prev_version;
|
||||
}
|
||||
|
||||
@@ -1319,10 +1334,9 @@ committed_version_trx:
|
||||
heap2 = heap;
|
||||
heap = mem_heap_create(1024);
|
||||
|
||||
if (!trx_undo_prev_version_build(rec, mtr, version, index,
|
||||
*offsets, heap,
|
||||
&prev_version,
|
||||
in_heap, vrow, 0)) {
|
||||
if (trx_undo_prev_version_build(version, index, *offsets, heap,
|
||||
&prev_version, in_heap, vrow,
|
||||
0) != DB_SUCCESS) {
|
||||
mem_heap_free(heap);
|
||||
heap = heap2;
|
||||
heap2 = NULL;
|
||||
|
||||
@@ -42,10 +42,6 @@ Created 3/26/1996 Heikki Tuuri
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#ifdef UNIV_PFS_RWLOCK
|
||||
extern mysql_pfs_key_t trx_purge_latch_key;
|
||||
#endif /* UNIV_PFS_RWLOCK */
|
||||
|
||||
/** Maximum allowable purge history length. <=0 means 'infinite'. */
|
||||
ulong srv_max_purge_lag = 0;
|
||||
|
||||
@@ -184,6 +180,7 @@ void purge_sys_t::create()
|
||||
hdr_page_no= 0;
|
||||
hdr_offset= 0;
|
||||
latch.SRW_LOCK_INIT(trx_purge_latch_key);
|
||||
end_latch.init();
|
||||
mysql_mutex_init(purge_sys_pq_mutex_key, &pq_mutex, nullptr);
|
||||
truncate.current= NULL;
|
||||
truncate.last= NULL;
|
||||
@@ -205,11 +202,40 @@ void purge_sys_t::close()
|
||||
trx->state= TRX_STATE_NOT_STARTED;
|
||||
trx->free();
|
||||
latch.destroy();
|
||||
end_latch.destroy();
|
||||
mysql_mutex_destroy(&pq_mutex);
|
||||
mem_heap_free(heap);
|
||||
heap= nullptr;
|
||||
}
|
||||
|
||||
/** Determine if the history of a transaction is purgeable.
|
||||
@param trx_id transaction identifier
|
||||
@return whether the history is purgeable */
|
||||
TRANSACTIONAL_TARGET bool purge_sys_t::is_purgeable(trx_id_t trx_id) const
|
||||
{
|
||||
bool purgeable;
|
||||
#if !defined SUX_LOCK_GENERIC && !defined NO_ELISION
|
||||
purgeable= false;
|
||||
if (xbegin())
|
||||
{
|
||||
if (!latch.is_write_locked())
|
||||
{
|
||||
purgeable= view.changes_visible(trx_id);
|
||||
xend();
|
||||
}
|
||||
else
|
||||
xabort();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
latch.rd_lock(SRW_LOCK_CALL);
|
||||
purgeable= view.changes_visible(trx_id);
|
||||
latch.rd_unlock();
|
||||
}
|
||||
return purgeable;
|
||||
}
|
||||
|
||||
/*================ UNDO LOG HISTORY LIST =============================*/
|
||||
|
||||
/** Prepend the history list with an undo log.
|
||||
@@ -1199,7 +1225,6 @@ trx_purge_attach_undo_recs(ulint n_purge_threads)
|
||||
|
||||
i = 0;
|
||||
|
||||
const ulint batch_size = srv_purge_batch_size;
|
||||
std::unordered_map<table_id_t, purge_node_t*> table_id_map;
|
||||
mem_heap_empty(purge_sys.heap);
|
||||
|
||||
@@ -1251,7 +1276,7 @@ trx_purge_attach_undo_recs(ulint n_purge_threads)
|
||||
|
||||
node->undo_recs.push(purge_rec);
|
||||
|
||||
if (n_pages_handled >= batch_size) {
|
||||
if (n_pages_handled >= srv_purge_batch_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1303,14 +1328,14 @@ extern tpool::waitable_task purge_worker_task;
|
||||
/** Wait for pending purge jobs to complete. */
|
||||
static void trx_purge_wait_for_workers_to_complete()
|
||||
{
|
||||
bool notify_wait = purge_worker_task.is_running();
|
||||
const bool notify_wait{purge_worker_task.is_running()};
|
||||
|
||||
if (notify_wait)
|
||||
tpool::tpool_wait_begin();
|
||||
tpool::tpool_wait_begin();
|
||||
|
||||
purge_worker_task.wait();
|
||||
|
||||
if(notify_wait)
|
||||
if (notify_wait)
|
||||
tpool::tpool_wait_end();
|
||||
|
||||
/* There should be no outstanding tasks as long
|
||||
@@ -1318,12 +1343,33 @@ static void trx_purge_wait_for_workers_to_complete()
|
||||
ut_ad(srv_get_task_queue_length() == 0);
|
||||
}
|
||||
|
||||
/** Update end_view at the end of a purge batch. */
|
||||
TRANSACTIONAL_INLINE void purge_sys_t::clone_end_view()
|
||||
{
|
||||
/* This is only invoked only by the purge coordinator,
|
||||
which is the only thread that can modify our inputs head, tail, view.
|
||||
Therefore, we only need to protect end_view from concurrent reads. */
|
||||
|
||||
/* Limit the end_view similar to what trx_purge_truncate_history() does. */
|
||||
const trx_id_t trx_no= head.trx_no ? head.trx_no : tail.trx_no;
|
||||
#ifdef SUX_LOCK_GENERIC
|
||||
end_latch.wr_lock();
|
||||
#else
|
||||
transactional_lock_guard<srw_spin_lock_low> g(end_latch);
|
||||
#endif
|
||||
end_view= view;
|
||||
end_view.clamp_low_limit_id(trx_no);
|
||||
#ifdef SUX_LOCK_GENERIC
|
||||
end_latch.wr_unlock();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
Run a purge batch.
|
||||
@param n_tasks number of purge tasks to submit to the queue
|
||||
@param truncate whether to truncate the history at the end of the batch
|
||||
@return number of undo log pages handled in the batch */
|
||||
ulint trx_purge(ulint n_tasks, bool truncate)
|
||||
TRANSACTIONAL_TARGET ulint trx_purge(ulint n_tasks, bool truncate)
|
||||
{
|
||||
que_thr_t* thr = NULL;
|
||||
ulint n_pages_handled;
|
||||
@@ -1357,6 +1403,8 @@ ulint trx_purge(ulint n_tasks, bool truncate)
|
||||
|
||||
trx_purge_wait_for_workers_to_complete();
|
||||
|
||||
purge_sys.clone_end_view();
|
||||
|
||||
if (truncate) {
|
||||
trx_purge_truncate_history();
|
||||
}
|
||||
|
||||
@@ -2069,51 +2069,49 @@ trx_undo_get_undo_rec_low(
|
||||
return undo_rec;
|
||||
}
|
||||
|
||||
/** Copy an undo record to heap.
|
||||
@param[in] roll_ptr roll pointer to record
|
||||
@param[in,out] heap memory heap where copied
|
||||
@param[in] trx_id id of the trx that generated
|
||||
the roll pointer: it points to an
|
||||
undo log of this transaction
|
||||
@param[in] name table name
|
||||
@param[out] undo_rec own: copy of the record
|
||||
@retval true if the undo log has been
|
||||
truncated and we cannot fetch the old version
|
||||
@retval false if the undo log record is available
|
||||
NOTE: the caller must have latches on the clustered index page. */
|
||||
static MY_ATTRIBUTE((warn_unused_result))
|
||||
bool
|
||||
trx_undo_get_undo_rec(
|
||||
roll_ptr_t roll_ptr,
|
||||
mem_heap_t* heap,
|
||||
trx_id_t trx_id,
|
||||
const table_name_t& name,
|
||||
trx_undo_rec_t** undo_rec)
|
||||
/** Copy an undo record to heap, to check if a secondary index record
|
||||
can be safely purged.
|
||||
@param trx_id DB_TRX_ID corresponding to roll_ptr
|
||||
@param name table name
|
||||
@param roll_ptr DB_ROLL_PTR pointing to the undo log record
|
||||
@param heap memory heap for allocation
|
||||
@return copy of the record
|
||||
@retval nullptr if the version is visible to purge_sys.view */
|
||||
static trx_undo_rec_t *trx_undo_get_rec_if_purgeable(trx_id_t trx_id,
|
||||
const table_name_t &name,
|
||||
roll_ptr_t roll_ptr,
|
||||
mem_heap_t* heap)
|
||||
{
|
||||
purge_sys.latch.rd_lock(SRW_LOCK_CALL);
|
||||
|
||||
bool missing_history = purge_sys.changes_visible(trx_id, name);
|
||||
if (!missing_history) {
|
||||
*undo_rec = trx_undo_get_undo_rec_low(roll_ptr, heap);
|
||||
missing_history = !*undo_rec;
|
||||
}
|
||||
|
||||
purge_sys.latch.rd_unlock();
|
||||
|
||||
return missing_history;
|
||||
{
|
||||
purge_sys_t::view_guard check;
|
||||
if (!check.view().changes_visible(trx_id))
|
||||
return trx_undo_get_undo_rec_low(roll_ptr, heap);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef UNIV_DEBUG
|
||||
#define ATTRIB_USED_ONLY_IN_DEBUG
|
||||
#else /* UNIV_DEBUG */
|
||||
#define ATTRIB_USED_ONLY_IN_DEBUG MY_ATTRIBUTE((unused))
|
||||
#endif /* UNIV_DEBUG */
|
||||
/** Copy an undo record to heap.
|
||||
@param trx_id DB_TRX_ID corresponding to roll_ptr
|
||||
@param name table name
|
||||
@param roll_ptr DB_ROLL_PTR pointing to the undo log record
|
||||
@param heap memory heap for allocation
|
||||
@return copy of the record
|
||||
@retval nullptr if the undo log is not available */
|
||||
static trx_undo_rec_t *trx_undo_get_undo_rec(trx_id_t trx_id,
|
||||
const table_name_t &name,
|
||||
roll_ptr_t roll_ptr,
|
||||
mem_heap_t *heap)
|
||||
{
|
||||
{
|
||||
purge_sys_t::end_view_guard check;
|
||||
if (!check.view().changes_visible(trx_id))
|
||||
return trx_undo_get_undo_rec_low(roll_ptr, heap);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/** Build a previous version of a clustered index record. The caller
|
||||
must hold a latch on the index page of the clustered index record.
|
||||
@param index_rec clustered index record in the index tree
|
||||
@param index_mtr mtr which contains the latch to index_rec page
|
||||
and purge_view
|
||||
@param rec version of a clustered index record
|
||||
@param index clustered index
|
||||
@param offsets rec_get_offsets(rec, index)
|
||||
@@ -2134,14 +2132,13 @@ must hold a latch on the index page of the clustered index record.
|
||||
And if we read "after image" of undo log
|
||||
@param undo_block undo log block which was cached during
|
||||
online dml apply or nullptr
|
||||
@retval true if previous version was built, or if it was an insert
|
||||
or the table has been rebuilt
|
||||
@retval false if the previous version is earlier than purge_view,
|
||||
or being purged, which means that it may have been removed */
|
||||
bool
|
||||
@return error code
|
||||
@retval DB_SUCCESS if previous version was successfully built,
|
||||
or if it was an insert or the undo record refers to the table before rebuild
|
||||
@retval DB_MISSING_HISTORY if the history is missing */
|
||||
TRANSACTIONAL_TARGET
|
||||
dberr_t
|
||||
trx_undo_prev_version_build(
|
||||
const rec_t *index_rec ATTRIB_USED_ONLY_IN_DEBUG,
|
||||
mtr_t *index_mtr ATTRIB_USED_ONLY_IN_DEBUG,
|
||||
const rec_t *rec,
|
||||
dict_index_t *index,
|
||||
rec_offs *offsets,
|
||||
@@ -2151,7 +2148,6 @@ trx_undo_prev_version_build(
|
||||
dtuple_t **vrow,
|
||||
ulint v_status)
|
||||
{
|
||||
trx_undo_rec_t* undo_rec = NULL;
|
||||
dtuple_t* entry;
|
||||
trx_id_t rec_trx_id;
|
||||
ulint type;
|
||||
@@ -2166,11 +2162,7 @@ trx_undo_prev_version_build(
|
||||
byte* buf;
|
||||
|
||||
ut_ad(!index->table->is_temporary());
|
||||
ut_ad(index_mtr->memo_contains_page_flagged(index_rec,
|
||||
MTR_MEMO_PAGE_S_FIX
|
||||
| MTR_MEMO_PAGE_X_FIX));
|
||||
ut_ad(rec_offs_validate(rec, index, offsets));
|
||||
ut_a(index->is_primary());
|
||||
|
||||
roll_ptr = row_get_rec_roll_ptr(rec, index, offsets);
|
||||
|
||||
@@ -2178,27 +2170,20 @@ trx_undo_prev_version_build(
|
||||
|
||||
if (trx_undo_roll_ptr_is_insert(roll_ptr)) {
|
||||
/* The record rec is the first inserted version */
|
||||
return(true);
|
||||
return DB_SUCCESS;
|
||||
}
|
||||
|
||||
rec_trx_id = row_get_rec_trx_id(rec, index, offsets);
|
||||
|
||||
ut_ad(!index->table->skip_alter_undo);
|
||||
|
||||
if (trx_undo_get_undo_rec(
|
||||
roll_ptr, heap, rec_trx_id, index->table->name,
|
||||
&undo_rec)) {
|
||||
if (v_status & TRX_UNDO_PREV_IN_PURGE) {
|
||||
/* We are fetching the record being purged */
|
||||
undo_rec = trx_undo_get_undo_rec_low(roll_ptr, heap);
|
||||
if (!undo_rec) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
/* The undo record may already have been purged,
|
||||
during purge or semi-consistent read. */
|
||||
return(false);
|
||||
}
|
||||
trx_undo_rec_t* undo_rec = v_status == TRX_UNDO_CHECK_PURGEABILITY
|
||||
? trx_undo_get_rec_if_purgeable(rec_trx_id, index->table->name,
|
||||
roll_ptr, heap)
|
||||
: trx_undo_get_undo_rec(rec_trx_id, index->table->name,
|
||||
roll_ptr, heap);
|
||||
if (!undo_rec) {
|
||||
return DB_MISSING_HISTORY;
|
||||
}
|
||||
|
||||
const byte *ptr =
|
||||
@@ -2209,7 +2194,7 @@ trx_undo_prev_version_build(
|
||||
/* The table should have been rebuilt, but purge has
|
||||
not yet removed the undo log records for the
|
||||
now-dropped old table (table_id). */
|
||||
return(true);
|
||||
return DB_SUCCESS;
|
||||
}
|
||||
|
||||
ptr = trx_undo_update_rec_get_sys_cols(ptr, &trx_id, &roll_ptr,
|
||||
@@ -2257,24 +2242,9 @@ trx_undo_prev_version_build(
|
||||
delete-marked record by trx_id, no transactions need to access
|
||||
the BLOB. */
|
||||
|
||||
/* the row_upd_changes_disowned_external(update) call could be
|
||||
omitted, but the synchronization on purge_sys.latch is likely
|
||||
more expensive. */
|
||||
|
||||
if ((update->info_bits & REC_INFO_DELETED_FLAG)
|
||||
&& row_upd_changes_disowned_external(update)) {
|
||||
purge_sys.latch.rd_lock(SRW_LOCK_CALL);
|
||||
|
||||
bool missing_extern = purge_sys.changes_visible(
|
||||
trx_id, index->table->name);
|
||||
|
||||
purge_sys.latch.rd_unlock();
|
||||
|
||||
if (missing_extern) {
|
||||
/* treat as a fresh insert, not to
|
||||
cause assertion error at the caller. */
|
||||
return(true);
|
||||
}
|
||||
if (update->info_bits & REC_INFO_DELETED_FLAG
|
||||
&& purge_sys.is_purgeable(trx_id)) {
|
||||
return DB_SUCCESS;
|
||||
}
|
||||
|
||||
/* We have to set the appropriate extern storage bits in the
|
||||
@@ -2289,8 +2259,8 @@ trx_undo_prev_version_build(
|
||||
following call is safe. */
|
||||
if (!row_upd_index_replace_new_col_vals(entry, *index, update,
|
||||
heap)) {
|
||||
ut_a(v_status & TRX_UNDO_PREV_IN_PURGE);
|
||||
return false;
|
||||
return (v_status & TRX_UNDO_PREV_IN_PURGE)
|
||||
? DB_MISSING_HISTORY : DB_CORRUPTION;
|
||||
}
|
||||
|
||||
/* Get number of externally stored columns in updated record */
|
||||
@@ -2387,7 +2357,7 @@ trx_undo_prev_version_build(
|
||||
v_status & TRX_UNDO_PREV_IN_PURGE);
|
||||
}
|
||||
|
||||
return(true);
|
||||
return DB_SUCCESS;
|
||||
}
|
||||
|
||||
/** Read virtual column value from undo log
|
||||
|
||||
@@ -44,40 +44,6 @@ Created 3/26/1996 Heikki Tuuri
|
||||
/** The transaction system */
|
||||
trx_sys_t trx_sys;
|
||||
|
||||
/** Check whether transaction id is valid.
|
||||
@param[in] id transaction id to check
|
||||
@param[in] name table name */
|
||||
void
|
||||
ReadViewBase::check_trx_id_sanity(
|
||||
trx_id_t id,
|
||||
const table_name_t& name)
|
||||
{
|
||||
if (id >= trx_sys.get_max_trx_id()) {
|
||||
|
||||
ib::warn() << "A transaction id"
|
||||
<< " in a record of table "
|
||||
<< name
|
||||
<< " is newer than the"
|
||||
<< " system-wide maximum.";
|
||||
ut_ad(0);
|
||||
THD *thd = current_thd;
|
||||
if (thd != NULL) {
|
||||
char table_name[MAX_FULL_NAME_LEN + 1];
|
||||
|
||||
innobase_format_name(
|
||||
table_name, sizeof(table_name),
|
||||
name.m_name);
|
||||
|
||||
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
||||
ER_SIGNAL_WARN,
|
||||
"InnoDB: Transaction id"
|
||||
" in a record of table"
|
||||
" %s is newer than system-wide"
|
||||
" maximum.", table_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef UNIV_DEBUG
|
||||
/* Flag to control TRX_RSEG_N_SLOTS behavior debugging. */
|
||||
uint trx_rseg_n_slots_debug = 0;
|
||||
|
||||
@@ -785,7 +785,7 @@ corrupted:
|
||||
ib::info() << "Trx id counter is " << trx_sys.get_max_trx_id();
|
||||
}
|
||||
|
||||
purge_sys.clone_oldest_view();
|
||||
purge_sys.clone_oldest_view<true>();
|
||||
return DB_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -2168,6 +2168,7 @@ trx_set_rw_mode(
|
||||
ut_ad(trx->rsegs.m_redo.rseg != 0);
|
||||
|
||||
trx_sys.register_rw(trx);
|
||||
ut_ad(trx->id);
|
||||
|
||||
/* So that we can see our own changes. */
|
||||
if (trx->read_view.is_open()) {
|
||||
|
||||
Reference in New Issue
Block a user