Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MDEV-35701 trx_t::autoinc_locks causes unnecessary dynamic memory allocation #3720

Merged
merged 2 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions mysql-test/suite/innodb/r/auto_increment_lock_mode.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
CREATE TABLE t1(a TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE t2(a TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE t3(a TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE t4(a TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE t5(a TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE t6(a SERIAL, b INT) ENGINE=InnoDB;
CREATE FUNCTION p1() RETURNS INT
BEGIN
INSERT INTO t1() VALUES();
INSERT INTO t2() VALUES();
INSERT INTO t3() VALUES();
INSERT INTO t4() VALUES();
INSERT INTO t5() VALUES();
RETURN 1;
END$$
INSERT INTO t6(b) SELECT p1();
UPDATE t1,t2,t3,t4,t5 SET t1.a=2,t2.a=2,t3.a=2,t4.a=2,t5.a=2;
SELECT * FROM t1;
a
2
connect con1,localhost,root,,;
connect con2,localhost,root,,;
connect con3,localhost,root,,;
connection con1;
INSERT INTO t6(b) SELECT SLEEP(p1());
connection con2;
INSERT INTO t6(b) SELECT SLEEP(p1());
connection con3;
UPDATE t1,t2,t3,t4,t5 SET t1.a=0,t2.a=0,t3.a=0,t4.a=0,t5.a=0
WHERE t1.a=2 AND t2.a=2 AND t3.a=2 AND t4.a=2 AND t5.a=2;
connection default;
KILL QUERY $ID1;
KILL QUERY $ID2;
KILL QUERY $ID3;
connection con1;
disconnect con1;
connection con2;
disconnect con2;
connection con3;
disconnect con3;
connection default;
DROP FUNCTION p1;
DROP TABLE t1,t2,t3,t4,t5,t6;
35 changes: 15 additions & 20 deletions mysql-test/suite/innodb/r/innodb-autoinc-56228.result
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
DROP TABLE IF EXISTS t1_56228;
Warnings:
Note 1051 Unknown table 'test.t1_56228'
DROP TABLE IF EXISTS t2_56228;
Warnings:
Note 1051 Unknown table 'test.t2_56228'
DROP FUNCTION IF EXISTS bug56228;
Warnings:
Note 1305 FUNCTION test.bug56228 does not exist
CREATE TEMPORARY TABLE t1_56228(
CREATE TABLE t1_56228(
c1 iNT AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TEMPORARY TABLE t2_56228(
CREATE TABLE t2_56228(
c1 iNT AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE FUNCTION bug56228() RETURNS INT DETERMINISTIC
BEGIN
INSERT INTO t1_56228 VALUES(NULL);
INSERT INTO t2_56228 VALUES(NULL);
INSERT INTO t1_56228 VALUES(NULL);
INSERT INTO t2_56228 VALUES(NULL);
DROP TEMPORARY TABLE t1_56228;
DROP TABLE t1_56228;
RETURN 42;
END //
SELECT bug56228();
bug56228()
42
DROP FUNCTION bug56228;
DROP TEMPORARY TABLE t2_56228;
DROP TEMPORARY TABLE IF EXISTS t1_56228;
Warnings:
Note 1051 Unknown table 'test.t1_56228'
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger
CREATE PROCEDURE bug56228()
BEGIN
INSERT INTO t1_56228 VALUES(NULL);
INSERT INTO t2_56228 VALUES(NULL);
INSERT INTO t1_56228 VALUES(NULL);
INSERT INTO t2_56228 VALUES(NULL);
DROP TABLE t1_56228;
END //
CALL bug56228();
DROP PROCEDURE bug56228;
DROP TABLE t2_56228;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[old]
--innodb-autoinc-lock-mode=0
[new]
--innodb-autoinc-lock-mode=1
[none]
--innodb-autoinc-lock-mode=2
61 changes: 61 additions & 0 deletions mysql-test/suite/innodb/t/auto_increment_lock_mode.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
--source include/have_innodb.inc

CREATE TABLE t1(a TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE t2(a TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE t3(a TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE t4(a TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE t5(a TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE t6(a SERIAL, b INT) ENGINE=InnoDB;

DELIMITER $$;
CREATE FUNCTION p1() RETURNS INT
BEGIN
INSERT INTO t1() VALUES();
INSERT INTO t2() VALUES();
INSERT INTO t3() VALUES();
INSERT INTO t4() VALUES();
INSERT INTO t5() VALUES();
RETURN 1;
END$$
DELIMITER ;$$

INSERT INTO t6(b) SELECT p1();

UPDATE t1,t2,t3,t4,t5 SET t1.a=2,t2.a=2,t3.a=2,t4.a=2,t5.a=2;
SELECT * FROM t1;
--source include/count_sessions.inc

--connect(con1,localhost,root,,)
let $ID1= `SELECT @id := CONNECTION_ID()`;
--connect(con2,localhost,root,,)
let $ID2= `SELECT @id := CONNECTION_ID()`;
--connect(con3,localhost,root,,)
let $ID3= `SELECT @id := CONNECTION_ID()`;
--connection con1
send INSERT INTO t6(b) SELECT SLEEP(p1());
--connection con2
send INSERT INTO t6(b) SELECT SLEEP(p1());
--connection con3
send UPDATE t1,t2,t3,t4,t5 SET t1.a=0,t2.a=0,t3.a=0,t4.a=0,t5.a=0
WHERE t1.a=2 AND t2.a=2 AND t3.a=2 AND t4.a=2 AND t5.a=2;
--connection default
evalp KILL QUERY $ID1;
evalp KILL QUERY $ID2;
evalp KILL QUERY $ID3;
--connection con1
--error 0,ER_QUERY_INTERRUPTED,ER_AUTOINC_READ_FAILED
--reap
--disconnect con1
--connection con2
--error 0,ER_QUERY_INTERRUPTED,ER_AUTOINC_READ_FAILED
--reap
--disconnect con2
--connection con3
--error 0,ER_QUERY_INTERRUPTED,ER_AUTOINC_READ_FAILED
--reap
--disconnect con3
--connection default

DROP FUNCTION p1;
DROP TABLE t1,t2,t3,t4,t5,t6;
--source include/wait_until_count_sessions.inc
36 changes: 24 additions & 12 deletions mysql-test/suite/innodb/t/innodb-autoinc-56228.test
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,45 @@
##
# Bug #56228: dropping tables from within an active statement crashes server
#
DROP TABLE IF EXISTS t1_56228;
DROP TABLE IF EXISTS t2_56228;
DROP FUNCTION IF EXISTS bug56228;

CREATE TEMPORARY TABLE t1_56228(
# This test used to use TEMPORARY TABLE, which before MySQL 5.7 or
# MariaDB Server 10.2 were covered by InnoDB locks.
# In MariaDB Server 10.6, the locking and logging was corrected for Atomic DDL.
# Hence, even if we tweaked create_table_info_t::innobase_table_flags()
# so that TEMPORARY TABLE are created as persistent tables,
# the DROP TEMPORARY TABLE statement inside the function would
# fail due to HA_ERR_LOCK_WAIT_TIMEOUT, instead of breaking locks
# like it used to do before MDEV-26603 and possibly other changes.
CREATE TABLE t1_56228(
c1 iNT AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TEMPORARY TABLE t2_56228(
CREATE TABLE t2_56228(
c1 iNT AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;

DELIMITER //;

--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
CREATE FUNCTION bug56228() RETURNS INT DETERMINISTIC
BEGIN
INSERT INTO t1_56228 VALUES(NULL);
INSERT INTO t2_56228 VALUES(NULL);
INSERT INTO t1_56228 VALUES(NULL);
INSERT INTO t2_56228 VALUES(NULL);
DROP TEMPORARY TABLE t1_56228;
DROP TABLE t1_56228;
RETURN 42;
END //

CREATE PROCEDURE bug56228()
BEGIN
INSERT INTO t1_56228 VALUES(NULL);
INSERT INTO t2_56228 VALUES(NULL);
INSERT INTO t1_56228 VALUES(NULL);
INSERT INTO t2_56228 VALUES(NULL);
DROP TABLE t1_56228;
END //

DELIMITER ;//

--disable_ps_protocol
SELECT bug56228();
--enable_ps2_protocol
CALL bug56228();

DROP FUNCTION bug56228;
DROP TEMPORARY TABLE t2_56228;
DROP TEMPORARY TABLE IF EXISTS t1_56228;
DROP PROCEDURE bug56228;
DROP TABLE t2_56228;
2 changes: 1 addition & 1 deletion storage/innobase/dict/drop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ void trx_t::commit(std::vector<pfs_os_file_t> &deleted)
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));
ut_ad(autoinc_locks.empty());
mem_heap_empty(lock.lock_heap);
lock.table_locks.clear();
/* commit_persist() already reset this. */
Expand Down
2 changes: 1 addition & 1 deletion storage/innobase/handler/ha_innodb.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5097,7 +5097,7 @@ static void innobase_kill_query(handlerton*, THD *thd, enum thd_kill_levels)
may be executed as part of a multi-transaction DDL operation, such
as rollback_inplace_alter_table() or ha_innobase::delete_table(). */;
trx->error_state= DB_INTERRUPTED;
lock_sys_t::cancel<false>(trx, lock);
lock_sys.cancel<false>(trx, lock);
}
lock_sys.deadlock_check();
}
Expand Down
4 changes: 2 additions & 2 deletions storage/innobase/include/lock0lock.h
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,7 @@ class lock_sys_t
@retval DB_DEADLOCK if trx->lock.was_chosen_as_deadlock_victim was set
@retval DB_LOCK_WAIT if the lock was canceled */
template<bool check_victim>
static dberr_t cancel(trx_t *trx, lock_t *lock);
dberr_t cancel(trx_t *trx, lock_t *lock) noexcept;

/** Note that a record lock wait started */
inline void wait_start();
Expand Down Expand Up @@ -924,7 +924,7 @@ class lock_sys_t
void prdt_page_free_from_discard(const page_id_t id, bool all= false);

/** Cancel possible lock waiting for a transaction */
static void cancel_lock_wait_for_trx(trx_t *trx);
inline void cancel_lock_wait_for_trx(trx_t *trx) noexcept;
#ifdef WITH_WSREP
/** Cancel lock waiting for a wsrep BF abort. */
static void cancel_lock_wait_for_wsrep_bf_abort(trx_t *trx);
Expand Down
65 changes: 47 additions & 18 deletions storage/innobase/include/small_vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ class small_vector_base
small_vector_base()= delete;
small_vector_base(void *small, size_t small_size)
: BeginX(small), Capacity(Size_T(small_size)) {}
ATTRIBUTE_COLD void grow_by_1(void *small, size_t element_size);
ATTRIBUTE_COLD void grow_by_1(void *small, size_t element_size) noexcept;
public:
size_t size() const { return Size; }
size_t capacity() const { return Capacity; }
bool empty() const { return !Size; }
void clear() { Size= 0; }
size_t size() const noexcept { return Size; }
size_t capacity() const noexcept { return Capacity; }
bool empty() const noexcept { return !Size; }
void clear() noexcept { Size= 0; }
protected:
void set_size(size_t N) { Size= Size_T(N); }
void set_size(size_t N) noexcept { Size= Size_T(N); }
};

template <typename T, unsigned N>
Expand All @@ -49,7 +49,7 @@ class small_vector : public small_vector_base

using small_vector_base::set_size;

void grow_if_needed()
void grow_if_needed() noexcept
{
if (unlikely(size() >= capacity()))
grow_by_1(small, sizeof *small);
Expand All @@ -60,38 +60,67 @@ class small_vector : public small_vector_base
{
TRASH_ALLOC(small, sizeof small);
}
~small_vector()
~small_vector() noexcept
{
if (small != begin())
my_free(begin());
MEM_MAKE_ADDRESSABLE(small, sizeof small);
}

void fake_defined() const noexcept
{
ut_ad(empty());
MEM_MAKE_DEFINED(small, sizeof small);
}
void make_undefined() const noexcept { MEM_UNDEFINED(small, sizeof small); }

bool is_small() const noexcept { return small == BeginX; }

void deep_clear() noexcept
{
if (!is_small())
{
my_free(BeginX);
BeginX= small;
Capacity= N;
}
ut_ad(capacity() == N);
set_size(0);
}

using iterator= T *;
using const_iterator= const T *;
using reverse_iterator= std::reverse_iterator<iterator>;
using reference= T &;
using const_reference= const T&;

iterator begin() { return static_cast<iterator>(BeginX); }
const_iterator begin() const { return static_cast<const_iterator>(BeginX); }
iterator end() { return begin() + size(); }
const_iterator end() const { return begin() + size(); }
iterator begin() noexcept { return static_cast<iterator>(BeginX); }
const_iterator begin() const noexcept
{ return static_cast<const_iterator>(BeginX); }
iterator end() noexcept { return begin() + size(); }
const_iterator end() const noexcept { return begin() + size(); }

reverse_iterator rbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
reverse_iterator rend() noexcept { return reverse_iterator(begin()); }

reference operator[](size_t i) { assert(i < size()); return begin()[i]; }
const_reference operator[](size_t i) const
reference operator[](size_t i) noexcept
{ assert(i < size()); return begin()[i]; }
const_reference operator[](size_t i) const noexcept
{ return const_cast<small_vector&>(*this)[i]; }

void erase(const_iterator S, const_iterator E)
void erase(const_iterator S, const_iterator E) noexcept
{
set_size(std::move(const_cast<iterator>(E), end(),
const_cast<iterator>(S)) - begin());
}

void emplace_back(T &&arg)
void emplace_back(T &&arg) noexcept
{
grow_if_needed();
::new (end()) T(arg);
set_size(size() + 1);
}
void emplace_back(T &arg) noexcept
{
grow_if_needed();
::new (end()) T(arg);
Expand Down
14 changes: 6 additions & 8 deletions storage/innobase/include/trx0trx.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ Created 3/26/1996 Heikki Tuuri
#include "que0types.h"
#include "mem0mem.h"
#include "trx0xa.h"
#include "ut0vec.h"
#include "fts0fts.h"
#include "read0types.h"
#include "ilist.h"
#include "small_vector.h"

#include <vector>

Expand Down Expand Up @@ -857,12 +857,10 @@ struct trx_t : ilist_node<>
ulint n_autoinc_rows; /*!< no. of AUTO-INC rows required for
an SQL statement. This is useful for
multi-row INSERTs */
ib_vector_t* autoinc_locks; /* AUTOINC locks held by this
transaction. Note that these are
also in the lock list trx_locks. This
vector needs to be freed explicitly
when the trx instance is destroyed.
Protected by lock_sys.latch. */
typedef small_vector<lock_t*, 4> autoinc_lock_vector;
/** AUTO_INCREMENT locks held by this transaction; a subset of trx_locks,
protected by lock_sys.latch. */
autoinc_lock_vector autoinc_locks;
/*------------------------------*/
bool read_only; /*!< true if transaction is flagged
as a READ-ONLY transaction.
Expand Down Expand Up @@ -1059,7 +1057,7 @@ struct trx_t : ilist_node<>
ut_ad(!lock.wait_lock);
ut_ad(UT_LIST_GET_LEN(lock.trx_locks) == 0);
ut_ad(lock.table_locks.empty());
ut_ad(!autoinc_locks || ib_vector_is_empty(autoinc_locks));
ut_ad(autoinc_locks.empty());
ut_ad(UT_LIST_GET_LEN(lock.evicted_tables) == 0);
ut_ad(!dict_operation);
ut_ad(!apply_online_log);
Expand Down
Loading