Merge branch 10.6 into 10.7

This commit is contained in:
Daniel Black 2022-10-25 16:02:57 +11:00
commit e80acbbe91
40 changed files with 2647 additions and 2023 deletions

@ -1 +1 @@
Subproject commit 380ee32375bb36b68796c1c3eb09285f03fea5f5
Subproject commit 72b40bfaa869f3fe84242471dda989d13983d84c

View File

@ -104,32 +104,11 @@ HANDLER t1 CLOSE;
DROP TABLE t1;
# Test case 10: Check that the statements 'HELP'
# is supported by prepared statements
HELP `ALTER SERVER`;
INSERT INTO mysql.help_topic VALUES (0, 'Tamagotchi', 0, 'This digital pet is not a KB article', 'no example', 'https://tamagotchi.com/');
HELP `Tamagotchi`;
name description example
ALTER SERVER Syntax
------
ALTER SERVER server_name
OPTIONS (option [, option] ...)
Description
-----------
Alters the server information for server_name, adjusting the specified options
as per the CREATE SERVER command. The corresponding fields in the
mysql.servers table are updated accordingly. This statement requires the SUPER
privilege or, from MariaDB 10.5.2, the FEDERATED ADMIN privilege.
ALTER SERVER is not written to the binary log, irrespective of the binary log
format being used. From MariaDB 10.1.13, Galera replicates the CREATE SERVER,
ALTER SERVER and DROP SERVER statements.
Examples
--------
ALTER SERVER s OPTIONS (USER 'sally');
URL: mariadb.com/kb/en/alter-server/
Tamagotchi This digital pet is not a KB article no example
DELETE FROM mysql.help_topic WHERE help_topic_id = 0;
# Test case 11: Check that the statements CREATE/ALTER/DROP PROCEDURE
# are supported by prepared statements
CREATE PROCEDURE p1() SET @a=1;

View File

@ -128,7 +128,10 @@ DROP TABLE t1;
--echo # Test case 10: Check that the statements 'HELP'
--echo # is supported by prepared statements
HELP `ALTER SERVER`;
# avoid existing articles that may get updated.
INSERT INTO mysql.help_topic VALUES (0, 'Tamagotchi', 0, 'This digital pet is not a KB article', 'no example', 'https://tamagotchi.com/');
HELP `Tamagotchi`;
DELETE FROM mysql.help_topic WHERE help_topic_id = 0;
--echo # Test case 11: Check that the statements CREATE/ALTER/DROP PROCEDURE
--echo # are supported by prepared statements

View File

@ -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;

View File

@ -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);

View File

@ -0,0 +1,31 @@
connect suspend_purge,localhost,root,,;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
CREATE TABLE t (a int PRIMARY KEY, b int) engine = InnoDB;
CREATE TABLE t2 (a int PRIMARY KEY) engine = InnoDB;
INSERT INTO t VALUES (10, 10), (20, 20), (30, 30);
INSERT INTO t2 VALUES (10), (20), (30);
BEGIN;
UPDATE t2 SET a = a + 100;
SELECT * FROM t WHERE a = 20 FOR UPDATE;
a b
20 20
connect con_2,localhost,root,,;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SET DEBUG_SYNC = 'lock_trx_handle_wait_before_unlocked_wait_lock_check SIGNAL upd_locked WAIT_FOR upd_cont';
UPDATE t SET b = 100;
connection default;
SET DEBUG_SYNC="now WAIT_FOR upd_locked";
SET DEBUG_SYNC="lock_wait_before_suspend SIGNAL upd_cont";
SELECT * FROM t WHERE a = 10 FOR UPDATE;
connection con_2;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
disconnect con_2;
connection default;
a b
10 10
SET DEBUG_SYNC = 'RESET';
DROP TABLE t;
DROP TABLE t2;
disconnect suspend_purge;

View File

@ -0,0 +1,37 @@
connect suspend_purge,localhost,root,,;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
CREATE TABLE t (a int PRIMARY KEY, b int) engine = InnoDB;
CREATE TABLE t2 (a int PRIMARY KEY) engine = InnoDB;
INSERT INTO t VALUES (10, 10), (20, 20), (30, 30);
INSERT INTO t2 VALUES (10), (20), (30);
BEGIN;
UPDATE t2 SET a = a + 100;
SELECT * FROM t WHERE a = 20 FOR UPDATE;
a b
20 20
connect con_2,localhost,root,,;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET DEBUG_SYNC = 'lock_trx_handle_wait_enter SIGNAL upd_locked WAIT_FOR upd_cont';
SET DEBUG_SYNC = 'trx_t_release_locks_enter SIGNAL sel_cont WAIT_FOR upd_cont_2';
BEGIN;
UPDATE t SET b = 100;
connection default;
SET DEBUG_SYNC="now WAIT_FOR upd_locked";
SET DEBUG_SYNC="deadlock_report_before_lock_releasing SIGNAL upd_cont WAIT_FOR sel_cont";
SET DEBUG_SYNC="lock_wait_before_suspend SIGNAL sel_before_suspend";
SELECT * FROM t WHERE a = 10 FOR UPDATE;;
connect con_3,localhost,root,,;
SET DEBUG_SYNC="now WAIT_FOR sel_before_suspend";
SET DEBUG_SYNC="now SIGNAL upd_cont_2";
disconnect con_3;
connection con_2;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
disconnect con_2;
connection default;
a b
10 10
SET DEBUG_SYNC = 'RESET';
DROP TABLE t;
DROP TABLE t2;
disconnect suspend_purge;

View File

@ -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;

View File

@ -0,0 +1,62 @@
--source include/have_innodb.inc
--source include/have_debug_sync.inc
--source include/count_sessions.inc
--connect(suspend_purge,localhost,root,,)
# Purge can cause deadlock in the test, requesting page's RW_X_LATCH for trx
# ids reseting, after trx 2 acqured RW_S_LATCH and suspended in debug sync point
# lock_trx_handle_wait_enter, waiting for upd_cont signal, which must be
# emitted after the last SELECT in this test. The last SELECT will hang waiting
# for purge RW_X_LATCH releasing, and trx 2 will be rolled back by timeout.
START TRANSACTION WITH CONSISTENT SNAPSHOT;
--connection default
CREATE TABLE t (a int PRIMARY KEY, b int) engine = InnoDB;
CREATE TABLE t2 (a int PRIMARY KEY) engine = InnoDB;
INSERT INTO t VALUES (10, 10), (20, 20), (30, 30);
INSERT INTO t2 VALUES (10), (20), (30);
BEGIN; # trx 1
# The following update is necessary to increase the transaction weight, which is
# calculated as the number of locks + the number of undo records during deadlock
# report. Victim's transaction should have minimum weight. We need trx 2 to be
# choosen as victim, that's why we need to increase the current transaction
# weight.
UPDATE t2 SET a = a + 100;
SELECT * FROM t WHERE a = 20 FOR UPDATE;
--connect(con_2,localhost,root,,)
# RC is necessary to do semi-consistent read
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN; # trx 2
# The first time it will be hit on trying to lock (20,20), the second hit
# will be on (30,30).
SET DEBUG_SYNC = 'lock_trx_handle_wait_before_unlocked_wait_lock_check SIGNAL upd_locked WAIT_FOR upd_cont';
# We must not modify primary key fields to cause rr_sequential() read record
# function choosing in mysql_update(), i.e. both query_plan.using_filesort and
# query_plan.using_io_buffer must be false during init_read_record() call.
--send UPDATE t SET b = 100
--connection default
SET DEBUG_SYNC="now WAIT_FOR upd_locked";
SET DEBUG_SYNC="lock_wait_before_suspend SIGNAL upd_cont";
--send SELECT * FROM t WHERE a = 10 FOR UPDATE
--connection con_2
# If the bug is not fixed, lock_trx_handle_wait() wrongly returns DB_SUCCESS
# instead of DB_DEADLOCK, row_search_mvcc() of trx 2 behaves so as if (20,20)
# was locked. Some debug assertion must crash the server. If the bug is fixed,
# trx 2 must just be rolled back by deadlock detector.
--error ER_LOCK_DEADLOCK
--reap
--disconnect con_2
--connection default
--reap
SET DEBUG_SYNC = 'RESET';
DROP TABLE t;
DROP TABLE t2;
--disconnect suspend_purge
--source include/wait_until_count_sessions.inc

View File

@ -0,0 +1,66 @@
--source include/have_innodb.inc
--source include/have_debug_sync.inc
--source include/count_sessions.inc
--connect(suspend_purge,localhost,root,,)
# Purge can cause deadlock in the test, requesting page's RW_X_LATCH for trx
# ids reseting, after trx 2 acqured RW_S_LATCH and suspended in debug sync point
# lock_trx_handle_wait_enter, waiting for upd_cont signal, which must be
# emitted after the last SELECT in this test. The last SELECT will hang waiting
# for purge RW_X_LATCH releasing, and trx 2 will be rolled back by timeout.
START TRANSACTION WITH CONSISTENT SNAPSHOT;
--connection default
CREATE TABLE t (a int PRIMARY KEY, b int) engine = InnoDB;
CREATE TABLE t2 (a int PRIMARY KEY) engine = InnoDB;
INSERT INTO t VALUES (10, 10), (20, 20), (30, 30);
INSERT INTO t2 VALUES (10), (20), (30);
BEGIN; # trx 1
# The following update is necessary to increase the transaction weight, which is
# calculated as the number of locks + the number of undo records during deadlock
# report. Victim's transaction should have minimum weight. We need trx 2 to be
# choosen as victim, that's why we need to increase the current transaction
# weight.
UPDATE t2 SET a = a + 100;
SELECT * FROM t WHERE a = 20 FOR UPDATE;
--connect(con_2,localhost,root,,)
# RC is necessary to do semi-consistent read
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
# It will be hit on trying to lock (20,20).
SET DEBUG_SYNC = 'lock_trx_handle_wait_enter SIGNAL upd_locked WAIT_FOR upd_cont';
SET DEBUG_SYNC = 'trx_t_release_locks_enter SIGNAL sel_cont WAIT_FOR upd_cont_2';
BEGIN; # trx 2
# We must not modify primary key fields to cause rr_sequential() read record
# function choosing in mysql_update(), i.e. both query_plan.using_filesort and
# query_plan.using_io_buffer must be false during init_read_record() call.
# The following UPDATE will be chosen as deadlock victim and rolled back.
--send UPDATE t SET b = 100
--connection default
SET DEBUG_SYNC="now WAIT_FOR upd_locked";
SET DEBUG_SYNC="deadlock_report_before_lock_releasing SIGNAL upd_cont WAIT_FOR sel_cont";
SET DEBUG_SYNC="lock_wait_before_suspend SIGNAL sel_before_suspend";
# If the bug is not fixed, the following SELECT will crash, as the above UPDATE
# will reset trx->lock.wait_thr during rollback
--send SELECT * FROM t WHERE a = 10 FOR UPDATE;
--connect(con_3,localhost,root,,)
SET DEBUG_SYNC="now WAIT_FOR sel_before_suspend";
SET DEBUG_SYNC="now SIGNAL upd_cont_2";
--disconnect con_3
--connection con_2
--error ER_LOCK_DEADLOCK
--reap
--disconnect con_2
--connection default
--reap
SET DEBUG_SYNC = 'RESET';
DROP TABLE t;
DROP TABLE t2;
--disconnect suspend_purge
--source include/wait_until_count_sessions.inc

View File

@ -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;

View File

@ -324,7 +324,7 @@ int my_addr_resolve(void *ptr, my_addr_loc *loc)
for the base program. This is depending on if the compilation is
done with PIE or not.
*/
addr_offset= info.dli_fbase;
addr_offset= (void*) info.dli_fbase;
#ifndef __PIE__
if (strcmp(info.dli_fname, my_progname) == 0 &&
addr_resolve((void*) my_addr_resolve, loc) == 0 &&

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit ac0741e82d71ea8faf6865c4c6604f02449cfa27
Subproject commit 1071ce954807104d25c0951f422e83d5e17406fe

View File

@ -328,7 +328,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

View File

@ -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;

View File

@ -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>

View File

@ -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 {

View File

@ -3657,7 +3657,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;
@ -7395,7 +7395,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. */
@ -8927,7 +8927,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.
@ -9229,7 +9229,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));
}
@ -9829,9 +9829,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;
@ -15167,8 +15167,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
@ -15178,7 +15180,6 @@ ha_innobase::check(
}
if (!m_prebuilt->table->space) {
ib_senderrf(
thd,
IB_LOG_LEVEL_ERROR,
@ -15186,10 +15187,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,
@ -15210,6 +15208,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;
@ -15218,25 +15219,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
@ -15258,7 +15256,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"
@ -15267,7 +15265,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"
@ -15280,18 +15278,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(
@ -15300,11 +15302,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(

View File

@ -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); }

View File

@ -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

View File

@ -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

View File

@ -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));
}

View File

@ -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

View File

@ -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(
/*===============================*/

View File

@ -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; }

View File

@ -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,

View File

@ -44,6 +44,7 @@ Created 5/7/1996 Heikki Tuuri
#include "row0vers.h"
#include "pars0pars.h"
#include "srv0mon.h"
#include "que0que.h"
#include <set>
@ -1788,8 +1789,8 @@ dberr_t lock_wait(que_thr_t *thr)
wait_lock->un_member.tab_lock.table->id <= DICT_FIELDS_ID);
thd_wait_begin(trx->mysql_thd, (type_mode & LOCK_TABLE)
? THD_WAIT_TABLE_LOCK : THD_WAIT_ROW_LOCK);
trx->error_state= DB_SUCCESS;
int err= 0;
mysql_mutex_lock(&lock_sys.wait_mutex);
if (trx->lock.wait_lock)
{
@ -1811,25 +1812,24 @@ dberr_t lock_wait(que_thr_t *thr)
if (row_lock_wait)
lock_sys.wait_start();
trx->error_state= DB_SUCCESS;
#ifdef HAVE_REPLICATION
if (rpl)
lock_wait_rpl_report(trx);
#endif
if (trx->error_state != DB_SUCCESS)
goto check_trx_error;
while (trx->lock.wait_lock)
{
int err;
DEBUG_SYNC_C("lock_wait_before_suspend");
if (no_timeout)
{
my_cond_wait(&trx->lock.cond, &lock_sys.wait_mutex.m_mutex);
err= 0;
}
else
err= my_cond_timedwait(&trx->lock.cond, &lock_sys.wait_mutex.m_mutex,
&abstime);
check_trx_error:
switch (trx->error_state) {
case DB_DEADLOCK:
case DB_INTERRUPTED:
@ -1875,17 +1875,19 @@ end_wait:
/** Resume a lock wait */
static void lock_wait_end(trx_t *trx)
template <bool from_deadlock= false>
void lock_wait_end(trx_t *trx)
{
mysql_mutex_assert_owner(&lock_sys.wait_mutex);
ut_ad(trx->mutex_is_owner());
ut_d(const auto state= trx->state);
ut_ad(state == TRX_STATE_ACTIVE || state == TRX_STATE_PREPARED);
ut_ad(trx->lock.wait_thr);
ut_ad(state == TRX_STATE_COMMITTED_IN_MEMORY || state == TRX_STATE_ACTIVE ||
state == TRX_STATE_PREPARED);
ut_ad(from_deadlock || trx->lock.wait_thr);
if (trx->lock.was_chosen_as_deadlock_victim)
{
ut_ad(state == TRX_STATE_ACTIVE);
ut_ad(from_deadlock || state == TRX_STATE_ACTIVE);
trx->error_state= DB_DEADLOCK;
}
@ -5672,13 +5674,16 @@ static void lock_release_autoinc_locks(trx_t *trx)
}
/** Cancel a waiting lock request and release possibly waiting transactions */
static void lock_cancel_waiting_and_release(lock_t *lock)
template <bool from_deadlock= false>
void lock_cancel_waiting_and_release(lock_t *lock)
{
lock_sys.assert_locked(*lock);
mysql_mutex_assert_owner(&lock_sys.wait_mutex);
trx_t *trx= lock->trx;
trx->mutex_lock();
ut_ad(trx->state == TRX_STATE_ACTIVE);
ut_d(const auto trx_state= trx->state);
ut_ad(trx_state == TRX_STATE_COMMITTED_IN_MEMORY ||
trx_state == TRX_STATE_ACTIVE);
if (!lock->is_table())
lock_rec_dequeue_from_page(lock, true);
@ -5697,7 +5702,8 @@ static void lock_cancel_waiting_and_release(lock_t *lock)
/* Reset the wait flag and the back pointer to lock in trx. */
lock_reset_lock_and_trx_wait(lock);
lock_wait_end(trx);
lock_wait_end<from_deadlock>(trx);
trx->mutex_unlock();
}
@ -5868,6 +5874,7 @@ lock_unlock_table_autoinc(
/** Handle a pending lock wait (DB_LOCK_WAIT) in a semi-consistent read
while holding a clustered index leaf page latch.
@param trx transaction that is or was waiting for a lock
@retval DB_SUCCESS if the lock was granted
@retval DB_DEADLOCK if the transaction must be aborted due to a deadlock
@ -5878,8 +5885,13 @@ dberr_t lock_trx_handle_wait(trx_t *trx)
DEBUG_SYNC_C("lock_trx_handle_wait_enter");
if (trx->lock.was_chosen_as_deadlock_victim)
return DB_DEADLOCK;
DEBUG_SYNC_C("lock_trx_handle_wait_before_unlocked_wait_lock_check");
/* trx->lock.was_chosen_as_deadlock_victim must always be set before
trx->lock.wait_lock if the transaction was chosen as deadlock victim,
the function must not return DB_SUCCESS if
trx->lock.was_chosen_as_deadlock_victim is set. */
if (!trx->lock.wait_lock)
return DB_SUCCESS;
return trx->lock.was_chosen_as_deadlock_victim ? DB_DEADLOCK : DB_SUCCESS;
dberr_t err= DB_SUCCESS;
mysql_mutex_lock(&lock_sys.wait_mutex);
if (trx->lock.was_chosen_as_deadlock_victim)
@ -6282,8 +6294,11 @@ namespace Deadlock
ut_ad(victim->state == TRX_STATE_ACTIVE);
/* victim->lock.was_chosen_as_deadlock_victim must always be set before
releasing waiting locks and reseting trx->lock.wait_lock */
victim->lock.was_chosen_as_deadlock_victim= true;
lock_cancel_waiting_and_release(victim->lock.wait_lock);
DEBUG_SYNC_C("deadlock_report_before_lock_releasing");
lock_cancel_waiting_and_release<true>(victim->lock.wait_lock);
#ifdef WITH_WSREP
if (victim->is_wsrep() && wsrep_thd_is_SR(victim->mysql_thd))
wsrep_handle_SR_rollback(trx->mysql_thd, victim->mysql_thd);

View File

@ -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);

View File

@ -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,

View File

@ -2295,8 +2295,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(
@ -4617,9 +4623,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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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;

View File

@ -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();
}

View File

@ -2076,51 +2076,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)
@ -2141,14 +2139,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,
@ -2158,7 +2155,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;
@ -2173,11 +2169,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);
@ -2185,27 +2177,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 =
@ -2216,7 +2201,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,
@ -2264,24 +2249,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
@ -2296,8 +2266,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 */
@ -2394,7 +2364,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

View File

@ -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;

View File

@ -485,6 +485,7 @@ TRANSACTIONAL_INLINE inline void trx_t::commit_state()
/** Release any explicit locks of a committing transaction. */
inline void trx_t::release_locks()
{
DEBUG_SYNC_C("trx_t_release_locks_enter");
DBUG_ASSERT(state == TRX_STATE_COMMITTED_IN_MEMORY);
DBUG_ASSERT(!is_referenced());
@ -785,7 +786,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;
}
@ -2176,6 +2177,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()) {