MDEV-26217 Failing assertion: list.count > 0 in ut_list_remove or Assertion `lock->trx == this' failed in dberr_t trx_t::drop_table

This follows up the previous fix in
commit c3c53926c467c95386ae98d61ada87294bd61478 (MDEV-26554).

ha_innobase::delete_table(): Work around the insufficient
metadata locking (MDL) during DML operations by acquiring exclusive
InnoDB table locks on all child tables. Previously, this was only
done on TRUNCATE and ALTER.

ibuf_delete_rec(), btr_cur_optimistic_delete(): Do not invoke
lock_update_delete() during change buffer operations.
The revised trx_t::commit(std::vector<pfs_os_file_t>&) will
hold exclusive lock_sys.latch while invoking fil_delete_tablespace(),
which in turn may invoke ibuf_delete_rec().

dict_index_t::has_locking(): A new predicate, replacing the dummy
!dict_table_is_locking_disabled(index->table). Used for skipping lock
operations during ibuf_delete_rec().

trx_t::commit(std::vector<pfs_os_file_t>&): Release the locks
and remove the table from the cache while holding exclusive
lock_sys.latch.

trx_t::commit_in_memory(): Skip release_locks() if dict_operation holds.

trx_t::commit(): Reset dict_operation before invoking commit_in_memory()
via commit_persist().

lock_release_on_drop(): Release locks while lock_sys.latch is
exclusively locked.

lock_table(): Add a parameter for a pointer to the table.
We must not dereference the table before a lock_sys.latch has
been acquired. If the pointer to the table does not match the table
at that point, the table is invalid and DB_DEADLOCK will be returned.

row_ins_foreign_check_on_constraint(): Improve the checks.
Remove a bogus DB_LOCK_WAIT_TIMEOUT return that was needed
before commit c5fd9aa562fb15e8d6ededceccbec0c9792a3243 (MDEV-25919).

row_upd_check_references_constraints(),
wsrep_row_upd_check_foreign_constraints(): Simplify checks.
This commit is contained in:
Marko Mäkelä 2022-04-26 18:09:03 +03:00
parent ec85e7b196
commit 2ca1123464
23 changed files with 326 additions and 221 deletions

@ -1 +1 @@
Subproject commit e6c07a296d2996e8d5c3cc615dfc50013bbcc794
Subproject commit c3513bf2573c30f6d2df815de216120e92142020

View File

@ -1,11 +1,9 @@
CREATE TABLE t1 (a INT NOT NULL) ENGINE=InnoDB STATS_PERSISTENT=0;
connect ddl,localhost,root;
SET DEBUG_SYNC='after_trx_committed_in_memory SIGNAL stuck WAIT_FOR ever EXECUTE 2';
SET DEBUG_SYNC='after_trx_committed_in_memory SIGNAL stuck WAIT_FOR ever';
ALTER TABLE t1 ADD PRIMARY KEY(a);
connection default;
SET DEBUG_SYNC='now WAIT_FOR stuck';
SET DEBUG_SYNC='now SIGNAL ever';
SET DEBUG_SYNC='now WAIT_FOR stuck';
SET GLOBAL innodb_log_checkpoint_now=ON;
# restart
disconnect ddl;

View File

@ -960,11 +960,30 @@ ALTER TABLE parent FORCE, ALGORITHM=INPLACE;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
ALTER TABLE parent ADD COLUMN b INT, ALGORITHM=INSTANT;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
disconnect con1;
connection con1;
COMMIT;
connection default;
SET innodb_lock_wait_timeout=DEFAULT;
TRUNCATE TABLE parent;
ALTER TABLE parent FORCE, ALGORITHM=COPY;
ALTER TABLE parent FORCE, ALGORITHM=INPLACE;
ALTER TABLE parent ADD COLUMN b INT, ALGORITHM=INSTANT;
DROP TABLE child, parent;
#
# MDEV-26217 Failing assertion: list.count > 0 in ut_list_remove
# or Assertion `lock->trx == this' failed in dberr_t trx_t::drop_table
#
CREATE TABLE t1 (pk INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1);
CREATE TABLE t2 (pk INT PRIMARY KEY, FOREIGN KEY(pk) REFERENCES t1(pk))
ENGINE=InnoDB;
connection con1;
SET FOREIGN_KEY_CHECKS=OFF;
CREATE OR REPLACE TABLE t1 (b INT) ENGINE=InnoDB;
connection default;
INSERT INTO t2 VALUES (1);
connection con1;
disconnect con1;
connection default;
DROP TABLE IF EXISTS t2, t1;
# End of 10.6 tests

View File

@ -5,13 +5,11 @@
CREATE TABLE t1 (a INT NOT NULL) ENGINE=InnoDB STATS_PERSISTENT=0;
connect ddl,localhost,root;
SET DEBUG_SYNC='after_trx_committed_in_memory SIGNAL stuck WAIT_FOR ever EXECUTE 2';
SET DEBUG_SYNC='after_trx_committed_in_memory SIGNAL stuck WAIT_FOR ever';
send ALTER TABLE t1 ADD PRIMARY KEY(a);
connection default;
SET DEBUG_SYNC='now WAIT_FOR stuck';
SET DEBUG_SYNC='now SIGNAL ever';
SET DEBUG_SYNC='now WAIT_FOR stuck';
SET GLOBAL innodb_log_checkpoint_now=ON;

View File

@ -965,7 +965,9 @@ ALTER TABLE parent FORCE, ALGORITHM=COPY;
ALTER TABLE parent FORCE, ALGORITHM=INPLACE;
--error ER_LOCK_WAIT_TIMEOUT
ALTER TABLE parent ADD COLUMN b INT, ALGORITHM=INSTANT;
disconnect con1;
connection con1;
COMMIT;
connection default;
# Restore the timeout to avoid occasional races with purge.
SET innodb_lock_wait_timeout=DEFAULT;
TRUNCATE TABLE parent;
@ -974,6 +976,37 @@ ALTER TABLE parent FORCE, ALGORITHM=INPLACE;
ALTER TABLE parent ADD COLUMN b INT, ALGORITHM=INSTANT;
DROP TABLE child, parent;
--echo #
--echo # MDEV-26217 Failing assertion: list.count > 0 in ut_list_remove
--echo # or Assertion `lock->trx == this' failed in dberr_t trx_t::drop_table
--echo #
CREATE TABLE t1 (pk INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1);
CREATE TABLE t2 (pk INT PRIMARY KEY, FOREIGN KEY(pk) REFERENCES t1(pk))
ENGINE=InnoDB;
--connection con1
SET FOREIGN_KEY_CHECKS=OFF;
--send
CREATE OR REPLACE TABLE t1 (b INT) ENGINE=InnoDB;
--connection default
--error 0,ER_NO_REFERENCED_ROW_2,ER_LOCK_DEADLOCK
INSERT INTO t2 VALUES (1);
--connection con1
--error 0,ER_CANT_CREATE_TABLE
--reap
# Cleanup
--disconnect con1
--connection default
--disable_warnings
DROP TABLE IF EXISTS t2, t1;
--enable_warnings
--echo # End of 10.6 tests
--source include/wait_until_count_sessions.inc

View File

@ -1378,7 +1378,7 @@ static void btr_page_reorganize_low(page_cur_t *cursor, dict_index_t *index,
block->page.frame + PAGE_MAX_TRX_ID + PAGE_HEADER,
PAGE_DATA - (PAGE_MAX_TRX_ID + PAGE_HEADER)));
if (!dict_table_is_locking_disabled(index->table))
if (index->has_locking())
lock_move_reorganize_page(block, old);
/* Write log for the changes, if needed. */
@ -1849,8 +1849,11 @@ btr_root_raise_and_insert(
root->page.frame, index, mtr);
/* Update the lock table and possible hash index. */
lock_move_rec_list_end(new_block, root,
page_get_infimum_rec(root->page.frame));
if (index->has_locking()) {
lock_move_rec_list_end(
new_block, root,
page_get_infimum_rec(root->page.frame));
}
/* Move any existing predicate locks */
if (dict_index_is_spatial(index)) {
@ -1898,7 +1901,7 @@ btr_root_raise_and_insert(
information of the record to be inserted on the infimum of the
root page: we cannot discard the lock structs on the root page */
if (!dict_table_is_locking_disabled(index->table)) {
if (index->has_locking()) {
lock_update_root_raise(*new_block, root_id);
}
@ -2594,7 +2597,7 @@ btr_insert_into_right_sibling(
max_size = page_get_max_insert_size_after_reorganize(next_page, 1);
/* Extends gap lock for the next page */
if (!dict_table_is_locking_disabled(cursor->index->table)) {
if (cursor->index->has_locking()) {
lock_update_split_left(next_block, block);
}
@ -2907,9 +2910,11 @@ insert_empty:
ULINT_UNDEFINED, mtr);
/* Update the lock table and possible hash index. */
lock_move_rec_list_start(
new_block, block, move_limit,
new_page + PAGE_NEW_INFIMUM);
if (cursor->index->has_locking()) {
lock_move_rec_list_start(
new_block, block, move_limit,
new_page + PAGE_NEW_INFIMUM);
}
btr_search_move_or_delete_hash_entries(
new_block, block);
@ -2923,7 +2928,7 @@ insert_empty:
left_block = new_block;
right_block = block;
if (!dict_table_is_locking_disabled(cursor->index->table)) {
if (cursor->index->has_locking()) {
lock_update_split_left(right_block, left_block);
}
} else {
@ -2949,7 +2954,10 @@ insert_empty:
cursor->index, mtr);
/* Update the lock table and possible hash index. */
lock_move_rec_list_end(new_block, block, move_limit);
if (cursor->index->has_locking()) {
lock_move_rec_list_end(new_block, block,
move_limit);
}
btr_search_move_or_delete_hash_entries(
new_block, block);
@ -2965,7 +2973,7 @@ insert_empty:
left_block = block;
right_block = new_block;
if (!dict_table_is_locking_disabled(cursor->index->table)) {
if (cursor->index->has_locking()) {
lock_update_split_right(right_block, left_block);
}
}
@ -3265,8 +3273,10 @@ btr_lift_page_up(
/* Update the lock table and possible hash index. */
lock_move_rec_list_end(father_block, block,
page_get_infimum_rec(page));
if (index->has_locking()) {
lock_move_rec_list_end(father_block, block,
page_get_infimum_rec(page));
}
/* Also update the predicate locks */
if (dict_index_is_spatial(index)) {
@ -3277,7 +3287,7 @@ btr_lift_page_up(
}
}
if (!dict_table_is_locking_disabled(index->table)) {
if (index->has_locking()) {
const page_id_t id{block->page.id()};
/* Free predicate page locks on the block */
if (index->is_spatial()) {
@ -3541,7 +3551,7 @@ retry:
lock_sys.prdt_page_free_from_discard(id);
} else {
btr_cur_node_ptr_delete(&father_cursor, mtr);
if (!dict_table_is_locking_disabled(index->table)) {
if (index->has_locking()) {
lock_update_merge_left(
*merge_block, orig_pred, id);
}
@ -3706,7 +3716,7 @@ retry:
mtr);
}
if (!dict_table_is_locking_disabled(index->table)) {
if (index->has_locking()) {
lock_update_merge_right(
merge_block, orig_succ, block);
}
@ -3848,7 +3858,7 @@ btr_discard_only_page_on_level(
}
father = btr_cur_get_block(&cursor);
if (!dict_table_is_locking_disabled(index->table)) {
if (index->has_locking()) {
lock_update_discard(
father, PAGE_HEAP_NO_SUPREMUM, block);
}
@ -4026,7 +4036,7 @@ btr_discard_page(
}
#endif /* UNIV_ZIP_DEBUG */
if (!dict_table_is_locking_disabled(index->table)) {
if (index->has_locking()) {
if (left_page_no != FIL_NULL) {
lock_update_discard(merge_block, PAGE_HEAP_NO_SUPREMUM,
block);
@ -4035,10 +4045,10 @@ btr_discard_page(
lock_get_min_heap_no(merge_block),
block);
}
}
if (dict_index_is_spatial(index)) {
rtr_check_discard_page(index, cursor, block);
if (index->is_spatial()) {
rtr_check_discard_page(index, cursor, block);
}
}
/* Free the file page */

View File

@ -4254,6 +4254,7 @@ btr_cur_update_in_place(
ut_ad(page_is_leaf(cursor->page_cur.block->page.frame));
rec = btr_cur_get_rec(cursor);
index = cursor->index;
ut_ad(!index->is_ibuf());
ut_ad(rec_offs_validate(rec, index, offsets));
ut_ad(!!page_rec_is_comp(rec) == dict_table_is_comp(index->table));
ut_ad(trx_id > 0 || (flags & BTR_KEEP_SYS_FLAG)
@ -4554,6 +4555,7 @@ btr_cur_optimistic_update(
page = buf_block_get_frame(block);
rec = btr_cur_get_rec(cursor);
index = cursor->index;
ut_ad(index->has_locking());
ut_ad(trx_id > 0 || (flags & BTR_KEEP_SYS_FLAG)
|| index->table->is_temporary());
ut_ad(!!page_rec_is_comp(rec) == dict_table_is_comp(index->table));
@ -4737,7 +4739,7 @@ any_extern:
/* Ok, we may do the replacement. Store on the page infimum the
explicit locks on rec, before deleting rec (see the comment in
btr_cur_pessimistic_update). */
if (!dict_table_is_locking_disabled(index->table)) {
if (index->has_locking()) {
lock_rec_store_on_page_infimum(block, rec);
}
@ -4771,7 +4773,7 @@ any_extern:
would have too many fields, and we would be unable to
know the size of the freed record. */
btr_page_reorganize(page_cursor, index, mtr);
} else if (!dict_table_is_locking_disabled(index->table)) {
} else {
/* Restore the old explicit lock state on the record */
lock_rec_restore_from_page_infimum(*block, rec,
block->page.id());
@ -4901,6 +4903,7 @@ btr_cur_pessimistic_update(
block = btr_cur_get_block(cursor);
page_zip = buf_block_get_page_zip(block);
index = cursor->index;
ut_ad(index->has_locking());
ut_ad(mtr->memo_contains_flagged(&index->lock, MTR_MEMO_X_LOCK |
MTR_MEMO_SX_LOCK));
@ -5097,9 +5100,7 @@ btr_cur_pessimistic_update(
in the lock system delete the lock structs set on the
root page even if the root page carries just node
pointers. */
if (!dict_table_is_locking_disabled(index->table)) {
lock_rec_store_on_page_infimum(block, rec);
}
lock_rec_store_on_page_infimum(block, rec);
}
#ifdef UNIV_ZIP_DEBUG
@ -5131,7 +5132,7 @@ btr_cur_pessimistic_update(
btr_set_instant(page_cursor->block, *index,
mtr);
}
} else if (!dict_table_is_locking_disabled(index->table)) {
} else {
lock_rec_restore_from_page_infimum(
*btr_cur_get_block(cursor), rec,
block->page.id());
@ -5277,7 +5278,7 @@ btr_cur_pessimistic_update(
know the size of the freed record. */
btr_page_reorganize(page_cursor, index, mtr);
rec = page_cursor->rec;
} else if (!dict_table_is_locking_disabled(index->table)) {
} else {
lock_rec_restore_from_page_infimum(
*btr_cur_get_block(cursor), rec, block->page.id());
}
@ -5287,7 +5288,7 @@ btr_cur_pessimistic_update(
record was nonexistent, the supremum might have inherited its locks
from a wrong record. */
if (!was_first && !dict_table_is_locking_disabled(index->table)) {
if (!was_first) {
btr_cur_pess_upd_restore_supremum(btr_cur_get_block(cursor),
rec, mtr);
}
@ -5451,15 +5452,13 @@ It is assumed that the mtr has an x-latch on the page where the cursor is
positioned, but no latch on the whole tree.
@return TRUE if success, i.e., the page did not become too empty */
ibool
btr_cur_optimistic_delete_func(
/*===========================*/
btr_cur_optimistic_delete(
/*======================*/
btr_cur_t* cursor, /*!< in: cursor on leaf page, on the record to
delete; cursor stays valid: if deletion
succeeds, on function exit it points to the
successor of the deleted record */
#ifdef UNIV_DEBUG
ulint flags, /*!< in: BTR_CREATE_FLAG or 0 */
#endif /* UNIV_DEBUG */
mtr_t* mtr) /*!< in: mtr; if this function returns
TRUE on a leaf page of a secondary
index, the mtr must be committed
@ -5531,7 +5530,7 @@ btr_cur_optimistic_delete_func(
|| (first_rec != rec
&& rec_is_add_metadata(first_rec, *index));
if (UNIV_LIKELY(empty_table)) {
if (UNIV_LIKELY(!is_metadata)) {
if (UNIV_LIKELY(!is_metadata && !flags)) {
lock_update_delete(block, rec);
}
btr_page_empty(block, buf_block_get_page_zip(block),
@ -5570,7 +5569,9 @@ btr_cur_optimistic_delete_func(
cursor->index, mtr);
goto func_exit;
} else {
lock_update_delete(block, rec);
if (!flags) {
lock_update_delete(block, rec);
}
btr_search_update_hash_on_delete(cursor);
}

View File

@ -68,6 +68,7 @@ before transaction commit and must be rolled back explicitly are as follows:
#include "dict0defrag_bg.h"
#include "btr0defragment.h"
#include "lock0lock.h"
#include "que0que.h"
#include "pars0pars.h"
@ -234,6 +235,24 @@ void trx_t::commit(std::vector<pfs_os_file_t> &deleted)
if (dict_operation)
{
ut_ad(dict_sys.locked());
lock_sys.wr_lock(SRW_LOCK_CALL);
mutex_lock();
lock_release_on_drop(this);
ut_ad(UT_LIST_GET_LEN(lock.trx_locks) == 0);
ut_ad(ib_vector_is_empty(autoinc_locks));
mem_heap_empty(lock.lock_heap);
lock.table_locks.clear();
lock.was_chosen_as_deadlock_victim= false;
lock.n_rec_locks= 0;
while (dict_table_t *table= UT_LIST_GET_FIRST(lock.evicted_tables))
{
UT_LIST_REMOVE(lock.evicted_tables, table);
dict_mem_table_free(table);
}
dict_operation= false;
id= 0;
mutex_unlock();
for (const auto &p : mod_tables)
{
if (p.second.is_dropped())
@ -255,6 +274,12 @@ void trx_t::commit(std::vector<pfs_os_file_t> &deleted)
}
}
}
lock_sys.wr_unlock();
mysql_mutex_lock(&lock_sys.wait_mutex);
lock_sys.deadlock_check();
mysql_mutex_unlock(&lock_sys.wait_mutex);
}
commit_cleanup();
}

View File

@ -13490,6 +13490,7 @@ int ha_innobase::delete_table(const char *name)
dict_sys.unlock();
trx_t *trx= parent_trx;
dberr_t err= DB_SUCCESS;
if (!trx->lock.table_locks.empty() &&
thd_ddl_options(trx->mysql_thd)->is_create_select())
{
@ -13509,11 +13510,28 @@ int ha_innobase::delete_table(const char *name)
{
trx= innobase_trx_allocate(thd);
trx_start_for_ddl(trx);
if (table->name.is_temporary())
/* There is no need to lock any FOREIGN KEY child tables. */;
#ifdef WITH_PARTITION_STORAGE_ENGINE
else if (table->name.part())
/* FOREIGN KEY constraints cannot exist on partitioned tables. */;
#endif
else
{
dict_sys.freeze(SRW_LOCK_CALL);
for (const dict_foreign_t* f : table->referenced_set)
if (dict_table_t* child= f->foreign_table)
if ((err= lock_table_for_trx(child, trx, LOCK_X)) != DB_SUCCESS)
break;
dict_sys.unfreeze();
}
}
dict_table_t *table_stats= nullptr, *index_stats= nullptr;
MDL_ticket *mdl_table= nullptr, *mdl_index= nullptr;
dberr_t err= lock_table_for_trx(table, trx, LOCK_X);
if (err == DB_SUCCESS)
err= lock_table_for_trx(table, trx, LOCK_X);
const bool fts= err == DB_SUCCESS &&
(table->flags2 & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS));

View File

@ -4030,7 +4030,6 @@ static MY_ATTRIBUTE((warn_unused_result, nonnull))
bool ibuf_delete_rec(const page_id_t page_id, btr_pcur_t* pcur,
const dtuple_t* search_tuple, mtr_t* mtr)
{
ibool success;
page_t* root;
dberr_t err;
@ -4041,10 +4040,8 @@ bool ibuf_delete_rec(const page_id_t page_id, btr_pcur_t* pcur,
ut_ad(ibuf_rec_get_space(mtr, btr_pcur_get_rec(pcur))
== page_id.space());
success = btr_cur_optimistic_delete(btr_pcur_get_btr_cur(pcur),
0, mtr);
if (success) {
if (btr_cur_optimistic_delete(btr_pcur_get_btr_cur(pcur),
BTR_CREATE_FLAG, mtr)) {
if (page_is_empty(btr_pcur_get_page(pcur))) {
/* If a B-tree page is empty, it must be the root page
and the whole B-tree must be empty. InnoDB does not
@ -4088,8 +4085,8 @@ bool ibuf_delete_rec(const page_id_t page_id, btr_pcur_t* pcur,
root = ibuf_tree_root_get(mtr)->page.frame;
btr_cur_pessimistic_delete(&err, TRUE, btr_pcur_get_btr_cur(pcur), 0,
false, mtr);
btr_cur_pessimistic_delete(&err, TRUE, btr_pcur_get_btr_cur(pcur),
BTR_CREATE_FLAG, false, mtr);
ut_a(err == DB_SUCCESS);
ibuf_size_update(root);

View File

@ -1,7 +1,7 @@
/*****************************************************************************
Copyright (c) 1994, 2019, 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
@ -459,27 +459,18 @@ that the mtr has an x-latch on the page where the cursor is positioned,
but no latch on the whole tree.
@return TRUE if success, i.e., the page did not become too empty */
ibool
btr_cur_optimistic_delete_func(
btr_cur_optimistic_delete(
/*===========================*/
btr_cur_t* cursor, /*!< in: cursor on the record to delete;
cursor stays valid: if deletion succeeds,
on function exit it points to the successor
of the deleted record */
# ifdef UNIV_DEBUG
ulint flags, /*!< in: BTR_CREATE_FLAG or 0 */
# endif /* UNIV_DEBUG */
mtr_t* mtr) /*!< in: mtr; if this function returns
TRUE on a leaf page of a secondary
index, the mtr must be committed
before latching any further pages */
MY_ATTRIBUTE((nonnull, warn_unused_result));
# ifdef UNIV_DEBUG
# define btr_cur_optimistic_delete(cursor, flags, mtr) \
btr_cur_optimistic_delete_func(cursor, flags, mtr)
# else /* UNIV_DEBUG */
# define btr_cur_optimistic_delete(cursor, flags, mtr) \
btr_cur_optimistic_delete_func(cursor, mtr)
# endif /* UNIV_DEBUG */
/*************************************************************//**
Removes the record on which the tree cursor is positioned. Tries
to compress the page if its fillfactor drops below a threshold

View File

@ -1,7 +1,7 @@
/*****************************************************************************
Copyright (c) 1996, 2016, 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
@ -24,9 +24,7 @@ Data types
Created 1/16/1996 Heikki Tuuri
*******************************************************/
#ifndef data0type_h
#define data0type_h
#pragma once
#include "univ.i"
/** Special length indicating a missing instantly added column */
@ -196,9 +194,6 @@ constexpr uint8_t DATA_MBR_LEN= uint8_t(SPDIMS * 2 * sizeof(double));
/** system-versioned user data column */
#define DATA_VERSIONED (DATA_VERS_START|DATA_VERS_END)
/** Check whether locking is disabled (never). */
#define dict_table_is_locking_disabled(table) false
/*-------------------------------------------*/
/* This many bytes we need to store the type information affecting the
@ -588,5 +583,3 @@ static const byte REC_INFO_METADATA_ALTER
= REC_INFO_METADATA_ADD | REC_INFO_DELETED_FLAG;
#include "data0type.inl"
#endif

View File

@ -1184,6 +1184,9 @@ public:
/** @return whether this is the change buffer */
bool is_ibuf() const { return UNIV_UNLIKELY(type & DICT_IBUF); }
/** @return whether this index requires locking */
bool has_locking() const { return !is_ibuf(); }
/** @return whether the index includes virtual columns */
bool has_virtual() const { return type & DICT_VIRTUAL; }

View File

@ -374,18 +374,18 @@ lock_clust_rec_read_check_and_lock_alt(
LOCK_REC_NOT_GAP */
que_thr_t* thr) /*!< in: query thread */
MY_ATTRIBUTE((warn_unused_result));
/*********************************************************************//**
Locks the specified database table in the mode given. If the lock cannot
be granted immediately, the query thread is put to wait.
@return DB_SUCCESS, DB_LOCK_WAIT, or DB_DEADLOCK */
dberr_t
lock_table(
/*=======*/
dict_table_t* table, /*!< in/out: database table
in dictionary cache */
lock_mode mode, /*!< in: lock mode */
que_thr_t* thr) /*!< in: query thread */
MY_ATTRIBUTE((warn_unused_result));
/** Acquire a table lock.
@param table table to be locked
@param fktable pointer to table, in case of a FOREIGN key check
@param mode lock mode
@param thr SQL execution thread
@retval DB_SUCCESS if the lock was acquired
@retval DB_DEADLOCK if a deadlock occurred, or fktable && *fktable != table
@retval DB_LOCK_WAIT if lock_wait() must be invoked */
dberr_t lock_table(dict_table_t *table, dict_table_t *const*fktable,
lock_mode mode, que_thr_t *thr)
MY_ATTRIBUTE((warn_unused_result));
/** Create a table lock object for a resurrected transaction.
@param table table to be X-locked
@ -426,6 +426,11 @@ lock_rec_unlock(
and release possible other transactions waiting because of these locks. */
void lock_release(trx_t* trx);
/** Release the explicit locks of a committing transaction while
dict_sys.latch is exclusively locked,
and release possible other transactions waiting because of these locks. */
void lock_release_on_drop(trx_t *trx);
/** Release non-exclusive locks on XA PREPARE,
and release possible other transactions waiting because of these locks. */
void lock_release_on_prepare(trx_t *trx);

View File

@ -766,8 +766,12 @@ public:
flush the log in
trx_commit_complete_for_mysql() */
ulint duplicates; /*!< TRX_DUP_IGNORE | TRX_DUP_REPLACE */
bool dict_operation; /**< whether this modifies InnoDB
data dictionary */
/** whether this modifies InnoDB dictionary tables */
bool dict_operation;
#ifdef UNIV_DEBUG
/** copy of dict_operation during commit() */
bool was_dict_operation;
#endif
/** whether dict_sys.latch is held exclusively; protected by
dict_sys.latch */
bool dict_operation_lock_mode;
@ -942,7 +946,8 @@ private:
ATTRIBUTE_COLD void apply_log();
/** Process tables that were modified by the committing transaction. */
inline void commit_tables();
/** Mark a transaction committed in the main memory data structures. */
/** Mark a transaction committed in the main memory data structures.
@param mtr mini-transaction (if there are any persistent modifications) */
inline void commit_in_memory(const mtr_t *mtr);
/** Write log for committing the transaction. */
void commit_persist();

View File

@ -3460,59 +3460,53 @@ static dberr_t lock_table_wsrep(dict_table_t *table, lock_mode mode,
}
#endif
/*********************************************************************//**
Locks the specified database table in the mode given. If the lock cannot
be granted immediately, the query thread is put to wait.
@return DB_SUCCESS, DB_LOCK_WAIT, or DB_DEADLOCK */
dberr_t
lock_table(
/*=======*/
dict_table_t* table, /*!< in/out: database table
in dictionary cache */
lock_mode mode, /*!< in: lock mode */
que_thr_t* thr) /*!< in: query thread */
/** Acquire a table lock.
@param table table to be locked
@param fktable pointer to table, in case of a FOREIGN key check
@param mode lock mode
@param thr SQL execution thread
@retval DB_SUCCESS if the lock was acquired
@retval DB_DEADLOCK if a deadlock occurred, or fktable && *fktable != table
@retval DB_LOCK_WAIT if lock_wait() must be invoked */
dberr_t lock_table(dict_table_t *table, dict_table_t *const*fktable,
lock_mode mode, que_thr_t *thr)
{
trx_t* trx;
ut_ad(table);
if (table->is_temporary()) {
return DB_SUCCESS;
}
if (!fktable && table->is_temporary())
return DB_SUCCESS;
trx = thr_get_trx(thr);
ut_ad(fktable || table->get_ref_count() || !table->can_be_evicted);
/* Look for equal or stronger locks the same trx already
has on the table. No need to acquire LockMutexGuard here
because only this transaction can add/access table locks
to/from trx_t::table_locks. */
trx_t *trx= thr_get_trx(thr);
if (lock_table_has(trx, table, mode) || srv_read_only_mode) {
return(DB_SUCCESS);
}
/* Look for equal or stronger locks the same trx already has on the
table. No need to acquire LockMutexGuard here because only the
thread that is executing a transaction can access trx_t::table_locks. */
if (lock_table_has(trx, table, mode) || srv_read_only_mode)
return DB_SUCCESS;
/* Read only transactions can write to temp tables, we don't want
to promote them to RW transactions. Their updates cannot be visible
to other transactions. Therefore we can keep them out
of the read views. */
if ((mode == LOCK_IX || mode == LOCK_X)
&& !trx->read_only
&& trx->rsegs.m_redo.rseg == 0) {
trx_set_rw_mode(trx);
}
if ((mode == LOCK_IX || mode == LOCK_X) &&
!trx->read_only && !trx->rsegs.m_redo.rseg)
trx_set_rw_mode(trx);
#ifdef WITH_WSREP
if (trx->is_wsrep()) {
return lock_table_wsrep(table, mode, thr, trx);
}
if (trx->is_wsrep())
return lock_table_wsrep(table, mode, thr, trx);
#endif
lock_sys.rd_lock(SRW_LOCK_CALL);
table->lock_mutex_lock();
dberr_t err = lock_table_low(table, mode, thr, trx);
table->lock_mutex_unlock();
lock_sys.rd_unlock();
lock_sys.rd_lock(SRW_LOCK_CALL);
dberr_t err;
if (fktable != nullptr && *fktable != table)
err= DB_DEADLOCK;
else
{
table->lock_mutex_lock();
err= lock_table_low(table, mode, thr, trx);
table->lock_mutex_unlock();
}
lock_sys.rd_unlock();
return err;
return err;
}
/** Create a table lock object for a resurrected transaction.
@ -3649,7 +3643,7 @@ dberr_t lock_table_for_trx(dict_table_t *table, trx_t *trx, lock_mode mode,
run_again:
thr->run_node= thr;
thr->prev_node= thr->common.parent;
dberr_t err= lock_table(table, mode, thr);
dberr_t err= lock_table(table, nullptr, mode, thr);
switch (err) {
case DB_SUCCESS:
@ -3820,7 +3814,7 @@ restart:
ut_ad(!lock->index->table->is_temporary());
ut_ad(lock->mode() != LOCK_X ||
lock->index->table->id >= DICT_HDR_FIRST_ID ||
trx->dict_operation);
trx->dict_operation || trx->was_dict_operation);
auto &lock_hash= lock_sys.hash_get(lock->type_mode);
auto cell= lock_hash.cell_get(lock->un_member.rec_lock.page_id.fold());
auto latch= lock_sys_t::hash_table::latch(cell);
@ -3838,7 +3832,7 @@ restart:
ut_ad(!table->is_temporary());
ut_ad(table->id >= DICT_HDR_FIRST_ID ||
(lock->mode() != LOCK_IX && lock->mode() != LOCK_X) ||
trx->dict_operation);
trx->dict_operation || trx->was_dict_operation);
if (!table->lock_mutex_trylock())
all_released= false;
else
@ -3895,7 +3889,7 @@ restart:
ut_ad(!lock->index->table->is_temporary());
ut_ad(lock->mode() != LOCK_X ||
lock->index->table->id >= DICT_HDR_FIRST_ID ||
trx->dict_operation);
trx->dict_operation || trx->was_dict_operation);
lock_rec_dequeue_from_page(lock, false);
}
else
@ -3904,7 +3898,7 @@ restart:
ut_ad(!table->is_temporary());
ut_ad(table->id >= DICT_HDR_FIRST_ID ||
(lock->mode() != LOCK_IX && lock->mode() != LOCK_X) ||
trx->dict_operation);
trx->dict_operation || trx->was_dict_operation);
lock_table_dequeue(lock, false);
}
@ -3941,6 +3935,38 @@ released:
#endif
}
/** Release the explicit locks of a committing transaction while
dict_sys.latch is exclusively locked,
and release possible other transactions waiting because of these locks. */
void lock_release_on_drop(trx_t *trx)
{
ut_ad(lock_sys.is_writer());
ut_ad(trx->mutex_is_owner());
ut_ad(trx->dict_operation);
while (lock_t *lock= UT_LIST_GET_LAST(trx->lock.trx_locks))
{
ut_ad(lock->trx == trx);
if (!lock->is_table())
{
ut_ad(!lock->index->table->is_temporary());
ut_ad(lock->mode() != LOCK_X ||
lock->index->table->id >= DICT_HDR_FIRST_ID ||
trx->dict_operation);
lock_rec_dequeue_from_page(lock, false);
}
else
{
ut_d(dict_table_t *table= lock->un_member.tab_lock.table);
ut_ad(!table->is_temporary());
ut_ad(table->id >= DICT_HDR_FIRST_ID ||
(lock->mode() != LOCK_IX && lock->mode() != LOCK_X) ||
trx->dict_operation);
lock_table_dequeue(lock, false);
}
}
}
/** Release non-exclusive locks on XA PREPARE,
and wake up possible other transactions waiting because of these locks.
@param trx transaction in XA PREPARE state

View File

@ -2,7 +2,7 @@
Copyright (c) 1994, 2016, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2012, Facebook Inc.
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
@ -654,7 +654,7 @@ page_copy_rec_list_end(
/* Update the lock table and possible hash index */
if (dict_table_is_locking_disabled(index->table)) {
if (!index->has_locking()) {
} else if (rec_move && dict_index_is_spatial(index)) {
lock_rtr_move_rec_list(new_block, block, rec_move, num_moved);
} else {
@ -821,7 +821,7 @@ zip_reorganize:
/* Update the lock table and possible hash index */
if (dict_table_is_locking_disabled(index->table)) {
if (!index->has_locking()) {
} else if (dict_index_is_spatial(index)) {
lock_rtr_move_rec_list(new_block, block, rec_move, num_moved);
} else {

View File

@ -1000,7 +1000,8 @@ row_ins_foreign_check_on_constraint(
{
upd_node_t* node;
upd_node_t* cascade;
dict_table_t* table = foreign->foreign_table;
dict_table_t*const*const fktable = &foreign->foreign_table;
dict_table_t* table = *fktable;
dict_index_t* index;
dict_index_t* clust_index;
dtuple_t* ref;
@ -1168,7 +1169,7 @@ row_ins_foreign_check_on_constraint(
/* Set an X-lock on the row to delete or update in the child table */
err = lock_table(table, LOCK_IX, thr);
err = lock_table(table, fktable, LOCK_IX, thr);
if (err == DB_SUCCESS) {
/* Here it suffices to use a LOCK_REC_NOT_GAP type lock;
@ -1454,10 +1455,7 @@ row_ins_check_foreign_constraint(
dtuple_t* entry, /*!< in: index entry for index */
que_thr_t* thr) /*!< in: query thread */
{
dberr_t err;
upd_node_t* upd_node;
dict_table_t* check_table;
dict_index_t* check_index;
ulint n_fields_cmp;
btr_pcur_t pcur;
int cmp;
@ -1479,12 +1477,10 @@ row_ins_check_foreign_constraint(
upd_node= NULL;
#endif /* WITH_WSREP */
err = DB_SUCCESS;
if (trx->check_foreigns == FALSE) {
if (!trx->check_foreigns) {
/* The user has suppressed foreign key checks currently for
this session */
goto exit_func;
DBUG_RETURN(DB_SUCCESS);
}
/* If any of the foreign key fields in entry is SQL NULL, we
@ -1493,12 +1489,12 @@ row_ins_check_foreign_constraint(
for (ulint i = 0; i < entry->n_fields; i++) {
dfield_t* field = dtuple_get_nth_field(entry, i);
if (i < foreign->n_fields && dfield_is_null(field)) {
goto exit_func;
DBUG_RETURN(DB_SUCCESS);
}
/* System Versioning: if row_end != Inf, we
suppress the foreign key check */
if (field->type.vers_sys_end() && field->vers_history_row()) {
goto exit_func;
DBUG_RETURN(DB_SUCCESS);
}
}
@ -1523,7 +1519,7 @@ row_ins_check_foreign_constraint(
another, and the user has problems predicting in
which order they are performed. */
goto exit_func;
DBUG_RETURN(DB_SUCCESS);
}
}
@ -1535,23 +1531,32 @@ row_ins_check_foreign_constraint(
dfield_t* row_end = dtuple_get_nth_field(
insert_node->row, table->vers_end);
if (row_end->vers_history_row()) {
goto exit_func;
DBUG_RETURN(DB_SUCCESS);
}
}
}
if (check_ref) {
check_table = foreign->referenced_table;
check_index = foreign->referenced_index;
} else {
check_table = foreign->foreign_table;
check_index = foreign->foreign_index;
dict_table_t *check_table;
dict_index_t *check_index;
dberr_t err = DB_SUCCESS;
{
dict_table_t*& fktable = check_ref
? foreign->referenced_table : foreign->foreign_table;
check_table = fktable;
if (check_table) {
err = lock_table(check_table, &fktable, LOCK_IS, thr);
if (err != DB_SUCCESS) {
goto do_possible_lock_wait;
}
}
check_table = fktable;
}
if (check_table == NULL
|| !check_table->is_readable()
|| check_index == NULL) {
check_index = check_ref
? foreign->referenced_index : foreign->foreign_index;
if (!check_table || !check_table->is_readable() || !check_index) {
FILE* ef = dict_foreign_err_file;
std::string fk_str;
@ -1600,18 +1605,6 @@ row_ins_check_foreign_constraint(
goto exit_func;
}
if (check_table != table) {
/* We already have a LOCK_IX on table, but not necessarily
on check_table */
err = lock_table(check_table, LOCK_IS, thr);
if (err != DB_SUCCESS) {
goto do_possible_lock_wait;
}
}
mtr_start(&mtr);
/* Store old value on n_fields_cmp */
@ -1824,10 +1817,7 @@ do_possible_lock_wait:
thr->lock_state = QUE_THR_LOCK_NOLOCK;
if (err != DB_SUCCESS) {
} else if (check_table->name.is_temporary()) {
err = DB_LOCK_WAIT_TIMEOUT;
} else {
if (err == DB_SUCCESS) {
err = DB_LOCK_WAIT;
}
}
@ -2632,7 +2622,7 @@ commit_exit:
DEBUG_SYNC_C("empty_root_page_insert");
if (!index->table->is_temporary()) {
err = lock_table(index->table, LOCK_X, thr);
err = lock_table(index->table, NULL, LOCK_X, thr);
if (err != DB_SUCCESS) {
trx->error_state = err;
@ -3694,7 +3684,7 @@ row_ins_step(
goto same_trx;
}
err = lock_table(node->table, LOCK_IX, thr);
err = lock_table(node->table, NULL, LOCK_IX, thr);
DBUG_EXECUTE_IF("ib_row_ins_ix_lock_wait",
err = DB_LOCK_WAIT;);

View File

@ -1124,7 +1124,7 @@ row_lock_table_autoinc_for_mysql(
trx_start_if_not_started_xa(trx, true);
err = lock_table(prebuilt->table, LOCK_AUTO_INC, thr);
err = lock_table(prebuilt->table, NULL, LOCK_AUTO_INC, thr);
trx->error_state = err;
} while (err != DB_SUCCESS
@ -1166,7 +1166,7 @@ row_lock_table(row_prebuilt_t* prebuilt)
trx_start_if_not_started_xa(trx, false);
err = lock_table(prebuilt->table, static_cast<lock_mode>(
err = lock_table(prebuilt->table, NULL, static_cast<lock_mode>(
prebuilt->select_lock_type), thr);
trx->error_state = err;
} while (err != DB_SUCCESS

View File

@ -2,7 +2,7 @@
Copyright (c) 1997, 2017, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2008, Google Inc.
Copyright (c) 2015, 2021, MariaDB Corporation.
Copyright (c) 2015, 2022, MariaDB Corporation.
Portions of this file contain modifications contributed and copyrighted by
Google, Inc. Those modifications are gratefully acknowledged and are described
@ -2357,7 +2357,8 @@ row_sel_step(
que_node_get_next(table_node))) {
dberr_t err = lock_table(
table_node->table, i_lock_mode, thr);
table_node->table, nullptr,
i_lock_mode, thr);
if (err != DB_SUCCESS) {
trx_t* trx;
@ -4672,7 +4673,7 @@ aborted:
trx->read_view.open(trx);
} else {
wait_table_again:
err = lock_table(prebuilt->table,
err = lock_table(prebuilt->table, nullptr,
prebuilt->select_lock_type == LOCK_S
? LOCK_IS : LOCK_IX, thr);

View File

@ -241,12 +241,9 @@ row_upd_check_references_constraints(
|| row_upd_changes_first_fields_binary(
entry, index, node->update,
foreign->n_fields))) {
dict_table_t* foreign_table = foreign->foreign_table;
dict_table_t* ref_table = NULL;
if (foreign_table == NULL) {
dict_table_t* ref_table = nullptr;
if (!foreign->foreign_table) {
ref_table = dict_table_open_on_name(
foreign->foreign_table_name_lookup,
false, DICT_ERR_IGNORE_NONE);
@ -293,7 +290,6 @@ wsrep_row_upd_check_foreign_constraints(
dtuple_t* entry;
const rec_t* rec;
dberr_t err;
ibool opened = FALSE;
if (table->foreign_set.empty()) {
return(DB_SUCCESS);
@ -328,27 +324,21 @@ wsrep_row_upd_check_foreign_constraints(
entry, index, node->update,
foreign->n_fields))) {
if (foreign->referenced_table == NULL) {
dict_table_t *opened = nullptr;
if (!foreign->referenced_table) {
foreign->referenced_table =
dict_table_open_on_name(
foreign->referenced_table_name_lookup,
false, DICT_ERR_IGNORE_NONE);
opened = (foreign->referenced_table) ? TRUE : FALSE;
opened = foreign->referenced_table;
}
/* NOTE that if the thread ends up waiting for a lock
we will release dict_sys.latch temporarily!
But the counter on the table protects 'foreign' from
being dropped while the check is running. */
err = row_ins_check_foreign_constraint(
TRUE, foreign, table, entry, thr);
if (foreign->referenced_table) {
if (opened) {
dict_table_close(foreign->referenced_table);
opened = FALSE;
}
if (opened) {
dict_table_close(opened);
}
if (err != DB_SUCCESS) {
@ -2893,7 +2883,7 @@ row_upd_step(
/* It may be that the current session has not yet
started its transaction, or it has been committed: */
err = lock_table(node->table, LOCK_IX, thr);
err = lock_table(node->table, nullptr, LOCK_IX, thr);
if (err != DB_SUCCESS) {

View File

@ -497,6 +497,13 @@ inline void trx_t::release_locks()
}
lock.table_locks.clear();
id= 0;
while (dict_table_t *table= UT_LIST_GET_FIRST(lock.evicted_tables))
{
UT_LIST_REMOVE(lock.evicted_tables, table);
dict_mem_table_free(table);
}
DEBUG_SYNC_C("after_trx_committed_in_memory");
}
/** At shutdown, frees a transaction object. */
@ -526,7 +533,6 @@ TRANSACTIONAL_TARGET void trx_free_at_shutdown(trx_t *trx)
DBUG_LOG("trx", "Free prepared: " << trx);
trx->state = TRX_STATE_NOT_STARTED;
ut_ad(!UT_LIST_GET_LEN(trx->lock.trx_locks));
trx->id = 0;
trx->free();
}
@ -1229,9 +1235,10 @@ void trx_t::evict_table(table_id_t table_id, bool reset_only)
}
}
/** Mark a transaction committed in the main memory data structures. */
TRANSACTIONAL_INLINE inline void trx_t::commit_in_memory(const mtr_t *mtr)
{
/* We already detached from rseg in trx_write_serialisation_history() */
ut_ad(!rsegs.m_redo.undo);
must_flush_log_later= false;
read_view.close();
@ -1242,12 +1249,14 @@ TRANSACTIONAL_INLINE inline void trx_t::commit_in_memory(const mtr_t *mtr)
ut_ad(!will_lock);
ut_a(!is_recovered);
ut_ad(!rsegs.m_redo.rseg);
ut_ad(!rsegs.m_redo.undo);
ut_ad(mysql_thd);
ut_ad(state == TRX_STATE_ACTIVE);
/* Note: We do not have to hold any lock_sys latch here, because
this is a non-locking transaction. */
ut_a(UT_LIST_GET_LEN(lock.trx_locks) == 0);
ut_ad(UT_LIST_GET_LEN(lock.evicted_tables) == 0);
/* This state change is not protected by any mutex, therefore
there is an inherent race here around state transition during
@ -1293,21 +1302,10 @@ TRANSACTIONAL_INLINE inline void trx_t::commit_in_memory(const mtr_t *mtr)
is_recovered= false;
}
release_locks();
id= 0;
DEBUG_SYNC_C("after_trx_committed_in_memory");
while (dict_table_t *table= UT_LIST_GET_FIRST(lock.evicted_tables))
{
UT_LIST_REMOVE(lock.evicted_tables, table);
dict_mem_table_free(table);
}
if (UNIV_LIKELY(!dict_operation))
release_locks();
}
/* We already detached from rseg in trx_write_serialisation_history() */
ut_ad(!rsegs.m_redo.undo);
ut_ad(UT_LIST_GET_LEN(lock.evicted_tables) == 0);
if (trx_rseg_t *rseg= rsegs.m_redo.rseg)
/* This is safe due to us having detached the persistent undo log. */
rseg->release();
@ -1381,10 +1379,10 @@ TRANSACTIONAL_INLINE inline void trx_t::commit_in_memory(const mtr_t *mtr)
void trx_t::commit_cleanup()
{
mutex.wr_lock();
dict_operation= false;
ut_ad(!dict_operation);
ut_ad(!was_dict_operation);
DBUG_LOG("trx", "Commit in memory: " << this);
mutex.wr_lock();
state= TRX_STATE_NOT_STARTED;
mod_tables.clear();
@ -1470,7 +1468,11 @@ void trx_t::commit_persist()
void trx_t::commit()
{
ut_ad(!was_dict_operation);
ut_d(was_dict_operation= dict_operation);
dict_operation= false;
commit_persist();
ut_d(was_dict_operation= false);
ut_d(for (const auto &p : mod_tables) ut_ad(!p.second.is_dropped()));
commit_cleanup();
}

@ -1 +1 @@
Subproject commit 23fb8624624c9144c77f3874647fa0f7394b0aa8
Subproject commit edd141127c11d78ef073f9f3ca61708821f20b32