From 446ec646511adf4327f9f5d9414fc4cb75c8161c Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Tue, 1 Mar 2022 13:01:48 +0530 Subject: [PATCH 01/10] MDEV-27962 Instant DDL downgrades the MDL when table is empty - Server incorrectly downgrading the MDL after prepare phase when table is empty. mdl_exclusive_after_prepare is being set in prepare phase only. But mdl_exclusive_after_prepare condition was misplaced and checked before prepare phase by commit d270525dfde86bcb92a2327234a0954083e14a94 and it is now changed to check after prepare phase. - main.innodb_mysql_sync test case was changed to avoid locking optimization when table is empty. --- mysql-test/main/innodb_mysql_sync.result | 12 ++++++++---- mysql-test/main/innodb_mysql_sync.test | 13 +++++++------ .../suite/innodb/r/instant_alter_debug.result | 17 ++++++++++++++++- .../suite/innodb/t/instant_alter_debug.test | 19 ++++++++++++++++++- sql/sql_table.cc | 10 +++++----- 5 files changed, 54 insertions(+), 17 deletions(-) diff --git a/mysql-test/main/innodb_mysql_sync.result b/mysql-test/main/innodb_mysql_sync.result index 3f284edde86..5e1f60d3e25 100644 --- a/mysql-test/main/innodb_mysql_sync.result +++ b/mysql-test/main/innodb_mysql_sync.result @@ -131,6 +131,7 @@ connection default; DROP DATABASE db1; # Test 2: Primary index (implicit), should block writes. CREATE TABLE t1(a INT NOT NULL, b INT NOT NULL) engine=innodb; +INSERT INTO t1 VALUES(1, 2); SET DEBUG_SYNC= "alter_table_inplace_after_lock_downgrade SIGNAL manage WAIT_FOR query"; # Sending: ALTER TABLE t1 ADD UNIQUE INDEX(a), LOCK=SHARED; @@ -139,15 +140,16 @@ SET DEBUG_SYNC= "now WAIT_FOR manage"; USE test; SELECT * FROM t1; a b +1 2 # Sending: -UPDATE t1 SET a=NULL; +UPDATE t1 SET a=3; connection con2; # Waiting for SELECT to be blocked by the metadata lock on t1 SET DEBUG_SYNC= "now SIGNAL query"; connection default; # Reaping: ALTER TABLE t1 ADD UNIQUE INDEX(a) connection con1; -# Reaping: UPDATE t1 SET a=NULL +# Reaping: UPDATE t1 SET a=3 # Test 3: Primary index (explicit), should block writes. connection default; ALTER TABLE t1 DROP INDEX a; @@ -158,15 +160,16 @@ connection con1; SET DEBUG_SYNC= "now WAIT_FOR manage"; SELECT * FROM t1; a b +3 2 # Sending: -UPDATE t1 SET a=NULL; +UPDATE t1 SET a=4; connection con2; # Waiting for SELECT to be blocked by the metadata lock on t1 SET DEBUG_SYNC= "now SIGNAL query"; connection default; # Reaping: ALTER TABLE t1 ADD PRIMARY KEY (a) connection con1; -# Reaping: UPDATE t1 SET a=NULL +# Reaping: UPDATE t1 SET a=4 # Test 4: Secondary unique index, should not block reads. connection default; SET DEBUG_SYNC= "alter_table_inplace_after_lock_downgrade SIGNAL manage WAIT_FOR query"; @@ -176,6 +179,7 @@ connection con1; SET DEBUG_SYNC= "now WAIT_FOR manage"; SELECT * FROM t1; a b +4 2 SET DEBUG_SYNC= "now SIGNAL query"; connection default; # Reaping: ALTER TABLE t1 ADD UNIQUE (b) diff --git a/mysql-test/main/innodb_mysql_sync.test b/mysql-test/main/innodb_mysql_sync.test index 4026080c4b4..466bcb360c5 100644 --- a/mysql-test/main/innodb_mysql_sync.test +++ b/mysql-test/main/innodb_mysql_sync.test @@ -176,6 +176,7 @@ DROP DATABASE db1; --echo # Test 2: Primary index (implicit), should block writes. CREATE TABLE t1(a INT NOT NULL, b INT NOT NULL) engine=innodb; +INSERT INTO t1 VALUES(1, 2); SET DEBUG_SYNC= "alter_table_inplace_after_lock_downgrade SIGNAL manage WAIT_FOR query"; --echo # Sending: --send ALTER TABLE t1 ADD UNIQUE INDEX(a), LOCK=SHARED @@ -185,13 +186,13 @@ SET DEBUG_SYNC= "now WAIT_FOR manage"; USE test; SELECT * FROM t1; --echo # Sending: ---send UPDATE t1 SET a=NULL +--send UPDATE t1 SET a=3 connection con2; --echo # Waiting for SELECT to be blocked by the metadata lock on t1 let $wait_condition= SELECT COUNT(*)= 1 FROM information_schema.processlist WHERE state= 'Waiting for table metadata lock' - AND info='UPDATE t1 SET a=NULL'; + AND info='UPDATE t1 SET a=3'; --source include/wait_condition.inc SET DEBUG_SYNC= "now SIGNAL query"; @@ -200,7 +201,7 @@ connection default; --reap connection con1; ---echo # Reaping: UPDATE t1 SET a=NULL +--echo # Reaping: UPDATE t1 SET a=3 --reap --echo # Test 3: Primary index (explicit), should block writes. @@ -215,13 +216,13 @@ connection con1; SET DEBUG_SYNC= "now WAIT_FOR manage"; SELECT * FROM t1; --echo # Sending: ---send UPDATE t1 SET a=NULL +--send UPDATE t1 SET a=4 connection con2; --echo # Waiting for SELECT to be blocked by the metadata lock on t1 let $wait_condition= SELECT COUNT(*)= 1 FROM information_schema.processlist WHERE state= 'Waiting for table metadata lock' - AND info='UPDATE t1 SET a=NULL'; + AND info='UPDATE t1 SET a=4'; --source include/wait_condition.inc SET DEBUG_SYNC= "now SIGNAL query"; @@ -230,7 +231,7 @@ connection default; --reap connection con1; ---echo # Reaping: UPDATE t1 SET a=NULL +--echo # Reaping: UPDATE t1 SET a=4 --reap --echo # Test 4: Secondary unique index, should not block reads. diff --git a/mysql-test/suite/innodb/r/instant_alter_debug.result b/mysql-test/suite/innodb/r/instant_alter_debug.result index 11acb2734e7..5b9cee57389 100644 --- a/mysql-test/suite/innodb/r/instant_alter_debug.result +++ b/mysql-test/suite/innodb/r/instant_alter_debug.result @@ -462,12 +462,27 @@ INSERT INTO t1 SET a=0, i=REPEAT('1', 10000); ROLLBACK; set DEBUG_SYNC='now SIGNAL go'; connection default; -disconnect con1; SELECT * FROM t1; a b c d e f g h i 1 2 3 4 5 6 7 8 test DROP TABLE t1; SET DEBUG_SYNC=RESET; +# +# MDEV-27962 Instant DDL downgrades the MDL when table is empty +# +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL)ENGINE=InnoDB; +SET DEBUG_SYNC="alter_table_inplace_after_lock_downgrade SIGNAL try_insert WAIT_FOR alter_progress"; +ALTER TABLE t1 ADD INDEX(f1), ADD INDEX(f2); +connection con1; +SET SESSION lock_wait_timeout=1; +SET DEBUG_SYNC="now WAIT_FOR try_insert"; +INSERT INTO t1 VALUES(1, 2); +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +SET DEBUG_SYNC="now SIGNAL alter_progress"; +disconnect con1; +connection default; +DROP TABLE t1; +SET DEBUG_SYNC=reset; # End of 10.4 tests SET GLOBAL innodb_purge_rseg_truncate_frequency = @save_frequency; SELECT variable_value-@old_instant instants diff --git a/mysql-test/suite/innodb/t/instant_alter_debug.test b/mysql-test/suite/innodb/t/instant_alter_debug.test index f960affc372..e31b378ff10 100644 --- a/mysql-test/suite/innodb/t/instant_alter_debug.test +++ b/mysql-test/suite/innodb/t/instant_alter_debug.test @@ -533,11 +533,28 @@ set DEBUG_SYNC='now SIGNAL go'; connection default; reap; -disconnect con1; SELECT * FROM t1; DROP TABLE t1; SET DEBUG_SYNC=RESET; +--echo # +--echo # MDEV-27962 Instant DDL downgrades the MDL when table is empty +--echo # +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL)ENGINE=InnoDB; +SET DEBUG_SYNC="alter_table_inplace_after_lock_downgrade SIGNAL try_insert WAIT_FOR alter_progress"; +send ALTER TABLE t1 ADD INDEX(f1), ADD INDEX(f2); +connection con1; +SET SESSION lock_wait_timeout=1; +SET DEBUG_SYNC="now WAIT_FOR try_insert"; +--error ER_LOCK_WAIT_TIMEOUT +INSERT INTO t1 VALUES(1, 2); +SET DEBUG_SYNC="now SIGNAL alter_progress"; +disconnect con1; +connection default; +reap; +DROP TABLE t1; +SET DEBUG_SYNC=reset; + --echo # End of 10.4 tests SET GLOBAL innodb_purge_rseg_truncate_frequency = @save_frequency; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 53adea74613..eb364f2bf10 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -7716,16 +7716,15 @@ static bool mysql_inplace_alter_table(THD *thd, lock for prepare phase under LOCK TABLES in the same way as when exclusive lock is required for duration of the whole statement. */ - if (!ha_alter_info->mdl_exclusive_after_prepare && - (inplace_supported == HA_ALTER_INPLACE_EXCLUSIVE_LOCK || - ((inplace_supported == HA_ALTER_INPLACE_COPY_NO_LOCK || + if (inplace_supported == HA_ALTER_INPLACE_EXCLUSIVE_LOCK || + ((inplace_supported == HA_ALTER_INPLACE_COPY_NO_LOCK || inplace_supported == HA_ALTER_INPLACE_COPY_LOCK || inplace_supported == HA_ALTER_INPLACE_NOCOPY_NO_LOCK || inplace_supported == HA_ALTER_INPLACE_NOCOPY_LOCK || inplace_supported == HA_ALTER_INPLACE_INSTANT) && (thd->locked_tables_mode == LTM_LOCK_TABLES || thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)) || - alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE)) + alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE) { if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) goto cleanup; @@ -7822,7 +7821,8 @@ static bool mysql_inplace_alter_table(THD *thd, necessary only for prepare phase (unless we are not under LOCK TABLES) and user has not explicitly requested exclusive lock. */ - if ((inplace_supported == HA_ALTER_INPLACE_COPY_NO_LOCK || + if (!ha_alter_info->mdl_exclusive_after_prepare && + (inplace_supported == HA_ALTER_INPLACE_COPY_NO_LOCK || inplace_supported == HA_ALTER_INPLACE_COPY_LOCK || inplace_supported == HA_ALTER_INPLACE_NOCOPY_LOCK || inplace_supported == HA_ALTER_INPLACE_NOCOPY_NO_LOCK) && From 4b37db70339b07f2357de0590cace45a318d9b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 1 Mar 2022 10:31:26 +0200 Subject: [PATCH 02/10] MDEV-27968 GCC 12 -Og -Wmaybe-uninitialized in udf_handler::fix_fields() udf_handler::fix_fields(): Execute an assignment outside "if" so that GCC 12 will not issue a bogus-looking warning. Also, deduplicate some error handling code. --- sql/item_func.cc | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sql/item_func.cc b/sql/item_func.cc index 6b8025c9d0f..7a7eaf9dc23 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2009, 2020, MariaDB + Copyright (c) 2009, 2022, MariaDB 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 @@ -3463,6 +3463,7 @@ udf_handler::fix_fields(THD *thd, Item_func_or_sum *func, thd->alloc(f_args.arg_count*sizeof(Item_result)))) { + err_exit: free_udf(u_d); DBUG_RETURN(TRUE); } @@ -3504,7 +3505,8 @@ udf_handler::fix_fields(THD *thd, Item_func_or_sum *func, f_args.arg_type[i]=item->result_type(); } //TODO: why all following memory is not allocated with 1 thd->alloc() call? - if (!(buffers=new String[arg_count]) || + buffers= new String[arg_count]; + if (!buffers || !(f_args.args= (char**) thd->alloc(arg_count * sizeof(char *))) || !(f_args.lengths= (ulong*) thd->alloc(arg_count * sizeof(long))) || !(f_args.maybe_null= (char*) thd->alloc(arg_count * sizeof(char))) || @@ -3514,10 +3516,7 @@ udf_handler::fix_fields(THD *thd, Item_func_or_sum *func, sizeof(char *))) || !(f_args.attribute_lengths= (ulong*) thd->alloc(arg_count * sizeof(long)))) - { - free_udf(u_d); - DBUG_RETURN(TRUE); - } + goto err_exit; } if (func->fix_length_and_dec()) DBUG_RETURN(TRUE); @@ -3583,8 +3582,7 @@ udf_handler::fix_fields(THD *thd, Item_func_or_sum *func, { my_error(ER_CANT_INITIALIZE_UDF, MYF(0), u_d->name.str, init_msg_buff); - free_udf(u_d); - DBUG_RETURN(TRUE); + goto err_exit; } func->max_length=MY_MIN(initid.max_length,MAX_BLOB_WIDTH); func->maybe_null=initid.maybe_null; From 1c74d1bcacee9d05999c7e3230ae5b6d7c03d440 Mon Sep 17 00:00:00 2001 From: Monty Date: Tue, 1 Mar 2022 11:36:39 +0200 Subject: [PATCH 03/10] federated.rpl failed if federatedx was not compiled --- mysql-test/suite/federated/rpl.test | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/suite/federated/rpl.test b/mysql-test/suite/federated/rpl.test index 6ec4bec5a1a..cc94ea8b716 100644 --- a/mysql-test/suite/federated/rpl.test +++ b/mysql-test/suite/federated/rpl.test @@ -1,3 +1,4 @@ +source have_federatedx.inc; source include/have_binlog_format_row.inc; source include/master-slave.inc; From 3fd79a04b69af46eddcdef947bef07b4c139ac75 Mon Sep 17 00:00:00 2001 From: Monty Date: Tue, 1 Mar 2022 11:46:57 +0200 Subject: [PATCH 04/10] MMDEV-27823 mariadb-install-db --group fails Fixed by not sending --group option to the server (for now) Reviwer: Sergei Golubchik --- scripts/mysql_install_db.sh | 9 +++++---- scripts/mysqld_safe.sh | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/mysql_install_db.sh b/scripts/mysql_install_db.sh index 63c014ea098..3df48f0eb95 100644 --- a/scripts/mysql_install_db.sh +++ b/scripts/mysql_install_db.sh @@ -484,10 +484,11 @@ then args="$args --user=$user" fi -if test -n "$group" -then - args="$args --group=$group" -fi +#To be enabled if/when we enable --group as an option to mysqld +#if test -n "$group" +#then +# args="$args --group=$group" +#fi # When doing a "cross bootstrap" install, no reference to the current # host should be added to the system tables. So we filter out any diff --git a/scripts/mysqld_safe.sh b/scripts/mysqld_safe.sh index 29b6d839685..5574587da6b 100644 --- a/scripts/mysqld_safe.sh +++ b/scripts/mysqld_safe.sh @@ -722,6 +722,7 @@ then if test "$user" != "root" -o $SET_USER = 1 then USER_OPTION="--user=$user" + # To be used if/when we enable --system-group as an option to mysqld GROUP_OPTION="--group=$group" fi if test -n "$open_files" From e8e755ea6cbac56d561375b940281a903c7db61c Mon Sep 17 00:00:00 2001 From: Rucha Deodhar Date: Mon, 21 Feb 2022 19:32:09 +0530 Subject: [PATCH 05/10] MDEV-26230: mysql_upgrade fails to load type_mysql_json due to insufficient maturity level Fix: Bumped maturity of the mysql_json plugin to gamma. --- plugin/type_mysql_json/type.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/type_mysql_json/type.cc b/plugin/type_mysql_json/type.cc index 65e62f60720..7cab4780ee5 100644 --- a/plugin/type_mysql_json/type.cc +++ b/plugin/type_mysql_json/type.cc @@ -211,6 +211,6 @@ maria_declare_plugin(type_mysql_json) NULL, NULL, "0.1", - MariaDB_PLUGIN_MATURITY_BETA + MariaDB_PLUGIN_MATURITY_GAMMA } maria_declare_plugin_end; From a92f07f4bd450f7368a998ce63443dd66374262a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Thu, 3 Mar 2022 11:51:25 +0200 Subject: [PATCH 06/10] MDEV-27993 Assertion failed in btr_page_reorganize_low() btr_cur_optimistic_insert(): Disregard DEBUG_DBUG injection to invoke btr_page_reorganize() if the page (and the table) is empty. Otherwise, an assertion would fail in btr_page_reorganize_low() because PAGE_MAX_TRX_ID is 0 in an empty secondary index leaf page. --- mysql-test/suite/innodb/r/page_reorganize.result | 7 +++++++ mysql-test/suite/innodb/t/page_reorganize.test | 8 ++++++++ storage/innobase/btr/btr0cur.cc | 3 ++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/mysql-test/suite/innodb/r/page_reorganize.result b/mysql-test/suite/innodb/r/page_reorganize.result index 1059fc78531..20e1600bd0d 100644 --- a/mysql-test/suite/innodb/r/page_reorganize.result +++ b/mysql-test/suite/innodb/r/page_reorganize.result @@ -25,3 +25,10 @@ f1 disconnect con1; connection default; drop table t1; +# +# MDEV-27993 Assertion failed in btr_page_reorganize_low() +# +CREATE TABLE t1(a INT PRIMARY KEY, b INT UNIQUE) ENGINE=InnoDB; +SET DEBUG_DBUG = '+d,do_page_reorganize'; +INSERT INTO t1 VALUES(0,0); +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/page_reorganize.test b/mysql-test/suite/innodb/t/page_reorganize.test index 7408353976d..c4e0160cb6d 100644 --- a/mysql-test/suite/innodb/t/page_reorganize.test +++ b/mysql-test/suite/innodb/t/page_reorganize.test @@ -53,4 +53,12 @@ connection default; drop table t1; +--echo # +--echo # MDEV-27993 Assertion failed in btr_page_reorganize_low() +--echo # +CREATE TABLE t1(a INT PRIMARY KEY, b INT UNIQUE) ENGINE=InnoDB; +SET DEBUG_DBUG = '+d,do_page_reorganize'; +INSERT INTO t1 VALUES(0,0); +DROP TABLE t1; + --source include/wait_until_count_sessions.inc diff --git a/storage/innobase/btr/btr0cur.cc b/storage/innobase/btr/btr0cur.cc index 8d0a34d07a1..195edb65e5a 100644 --- a/storage/innobase/btr/btr0cur.cc +++ b/storage/innobase/btr/btr0cur.cc @@ -3,7 +3,7 @@ Copyright (c) 1994, 2019, Oracle and/or its affiliates. All Rights Reserved. Copyright (c) 2008, Google Inc. Copyright (c) 2012, Facebook 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 @@ -3212,6 +3212,7 @@ fail_err: << ib::hex(thr ? thr->graph->trx->id : 0) << ' ' << rec_printer(entry).str()); DBUG_EXECUTE_IF("do_page_reorganize", + if (n_recs) btr_page_reorganize(page_cursor, index, mtr);); /* Now, try the insert */ From 1248fe727784bb6be73f70163353cf8457cfde69 Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Thu, 3 Feb 2022 12:57:32 +0530 Subject: [PATCH 07/10] MDEV-27582 Fulltext DDL decrements the FTS_DOC_ID value - InnoDB FTS DDL decrements the FTS_DOC_ID when there is a deleted marked record involved. FTS_DOC_ID must never be reused. The purpose of FTS_DOC_ID is to be a unique row identifier that will be changed whenever a fulltext indexed column is updated. --- .../suite/innodb_fts/r/innodb-fts-ddl.result | 29 +++++++++++ .../suite/innodb_fts/t/innodb-fts-ddl.opt | 1 + .../suite/innodb_fts/t/innodb-fts-ddl.test | 25 ++++++++++ storage/innobase/fts/fts0fts.cc | 49 +++---------------- storage/innobase/include/fts0fts.h | 23 ++++----- storage/innobase/row/row0merge.cc | 16 +++++- 6 files changed, 90 insertions(+), 53 deletions(-) diff --git a/mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result b/mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result index a64086c9802..daf552cde8b 100644 --- a/mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result +++ b/mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result @@ -289,3 +289,32 @@ ENGINE=InnoDB; ALTER TABLE t1 ADD c SERIAL; DROP TABLE t1; # End of 10.3 tests +# +# MDEV-27582 Fulltext DDL decrements the FTS_DOC_ID value +# +CREATE TABLE t1 ( +f1 INT NOT NULL PRIMARY KEY, +f2 VARCHAR(64), FULLTEXT ft(f2)) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1,'foo'),(2,'bar'); +connect con1,localhost,root,,,; +START TRANSACTION WITH CONSISTENT SNAPSHOT; +connection default; +DELETE FROM t1 WHERE f1 = 2; +ALTER TABLE t1 DROP INDEX ft; +ALTER TABLE t1 ADD FULLTEXT INDEX ft (f2); +INSERT INTO t1 VALUES (3, 'innodb fts search'); +SET GLOBAL innodb_optimize_fulltext_only=ON; +OPTIMIZE TABLE t1; +Table Op Msg_type Msg_text +test.t1 optimize status OK +SET GLOBAL innodb_ft_aux_table = 'test/t1'; +SELECT max(DOC_ID) FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE; +max(DOC_ID) +3 +SELECT * FROM t1 WHERE MATCH(f2) AGAINST("+innodb +search" IN BOOLEAN MODE); +f1 f2 +3 innodb fts search +DROP TABLE t1; +disconnect con1; +SET GLOBAL innodb_optimize_fulltext_only=OFF; +SET GLOBAL innodb_ft_aux_table = default; diff --git a/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.opt b/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.opt index e6ae8d0fe0a..5d5cca1c6f2 100644 --- a/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.opt +++ b/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.opt @@ -1 +1,2 @@ --enable-plugin-innodb-sys-tables +--innodb_ft_index_table diff --git a/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test b/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test index e8a7d603750..1ed164492d5 100644 --- a/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test +++ b/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test @@ -357,3 +357,28 @@ ALTER TABLE t1 ADD c SERIAL; DROP TABLE t1; --echo # End of 10.3 tests + +--echo # +--echo # MDEV-27582 Fulltext DDL decrements the FTS_DOC_ID value +--echo # +CREATE TABLE t1 ( + f1 INT NOT NULL PRIMARY KEY, + f2 VARCHAR(64), FULLTEXT ft(f2)) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1,'foo'),(2,'bar'); +connect(con1,localhost,root,,,); +START TRANSACTION WITH CONSISTENT SNAPSHOT; + +connection default; +DELETE FROM t1 WHERE f1 = 2; +ALTER TABLE t1 DROP INDEX ft; +ALTER TABLE t1 ADD FULLTEXT INDEX ft (f2); +INSERT INTO t1 VALUES (3, 'innodb fts search'); +SET GLOBAL innodb_optimize_fulltext_only=ON; +OPTIMIZE TABLE t1; +SET GLOBAL innodb_ft_aux_table = 'test/t1'; +SELECT max(DOC_ID) FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE; +SELECT * FROM t1 WHERE MATCH(f2) AGAINST("+innodb +search" IN BOOLEAN MODE); +DROP TABLE t1; +disconnect con1; +SET GLOBAL innodb_optimize_fulltext_only=OFF; +SET GLOBAL innodb_ft_aux_table = default; diff --git a/storage/innobase/fts/fts0fts.cc b/storage/innobase/fts/fts0fts.cc index acab4791d8b..c9610d29a55 100644 --- a/storage/innobase/fts/fts0fts.cc +++ b/storage/innobase/fts/fts0fts.cc @@ -243,18 +243,6 @@ fts_add_doc_by_id( /*==============*/ fts_trx_table_t*ftt, /*!< in: FTS trx table */ doc_id_t doc_id); /*!< in: doc id */ -/******************************************************************//** -Update the last document id. This function could create a new -transaction to update the last document id. -@return DB_SUCCESS if OK */ -static -dberr_t -fts_update_sync_doc_id( -/*===================*/ - const dict_table_t* table, /*!< in: table */ - doc_id_t doc_id, /*!< in: last document id */ - trx_t* trx) /*!< in: update trx, or NULL */ - MY_ATTRIBUTE((nonnull(1))); /** Tokenize a document. @param[in,out] doc document to tokenize @@ -2552,27 +2540,6 @@ fts_get_max_cache_size( } #endif -/*********************************************************************//** -Update the next and last Doc ID in the CONFIG table to be the input -"doc_id" value (+ 1). We would do so after each FTS index build or -table truncate */ -void -fts_update_next_doc_id( -/*===================*/ - trx_t* trx, /*!< in/out: transaction */ - const dict_table_t* table, /*!< in: table */ - doc_id_t doc_id) /*!< in: DOC ID to set */ -{ - table->fts->cache->synced_doc_id = doc_id; - table->fts->cache->next_doc_id = doc_id + 1; - - table->fts->cache->first_doc_id = table->fts->cache->next_doc_id; - - fts_update_sync_doc_id( - table, table->fts->cache->synced_doc_id, trx); - -} - /*********************************************************************//** Get the next available document id. @return DB_SUCCESS if OK */ @@ -2731,17 +2698,17 @@ func_exit: return(error); } -/*********************************************************************//** -Update the last document id. This function could create a new +/** Update the last document id. This function could create a new transaction to update the last document id. -@return DB_SUCCESS if OK */ -static +@param table table to be updated +@param doc_id last document id +@param trx update trx or null +@retval DB_SUCCESS if OK */ dberr_t fts_update_sync_doc_id( -/*===================*/ - const dict_table_t* table, /*!< in: table */ - doc_id_t doc_id, /*!< in: last document id */ - trx_t* trx) /*!< in: update trx, or NULL */ + const dict_table_t* table, + doc_id_t doc_id, + trx_t* trx) { byte id[FTS_MAX_ID_LEN]; pars_info_t* info; diff --git a/storage/innobase/include/fts0fts.h b/storage/innobase/include/fts0fts.h index 7a7c13a5384..326734c84c9 100644 --- a/storage/innobase/include/fts0fts.h +++ b/storage/innobase/include/fts0fts.h @@ -402,17 +402,6 @@ fts_get_next_doc_id( /*================*/ const dict_table_t* table, /*!< in: table */ doc_id_t* doc_id);/*!< out: new document id */ -/*********************************************************************//** -Update the next and last Doc ID in the CONFIG table to be the input -"doc_id" value (+ 1). We would do so after each FTS index build or -table truncate */ -void -fts_update_next_doc_id( -/*===================*/ - trx_t* trx, /*!< in/out: transaction */ - const dict_table_t* table, /*!< in: table */ - doc_id_t doc_id) /*!< in: DOC ID to set */ - MY_ATTRIBUTE((nonnull(2))); /******************************************************************//** Create a new fts_doc_ids_t. @@ -976,4 +965,16 @@ bool fts_check_aux_table(const char *name, table_id_t *table_id, index_id_t *index_id); +/** Update the last document id. This function could create a new +transaction to update the last document id. +@param table table to be updated +@param doc_id last document id +@param trx update trx or null +@retval DB_SUCCESS if OK */ +dberr_t +fts_update_sync_doc_id(const dict_table_t *table, + doc_id_t doc_id, + trx_t *trx) +MY_ATTRIBUTE((nonnull(1))); + #endif /*!< fts0fts.h */ diff --git a/storage/innobase/row/row0merge.cc b/storage/innobase/row/row0merge.cc index 6abfa848658..d6ace914c23 100644 --- a/storage/innobase/row/row0merge.cc +++ b/storage/innobase/row/row0merge.cc @@ -2862,7 +2862,21 @@ wait_again: err = fts_sync_table(const_cast(new_table)); if (err == DB_SUCCESS) { - fts_update_next_doc_id(NULL, new_table, max_doc_id); + new_table->fts->cache->synced_doc_id = max_doc_id; + + /* Update the max value as next FTS_DOC_ID */ + if (max_doc_id >= new_table->fts->cache->next_doc_id) { + new_table->fts->cache->next_doc_id = + max_doc_id + 1; + } + + new_table->fts->cache->first_doc_id = + new_table->fts->cache->next_doc_id; + + err= fts_update_sync_doc_id( + new_table, + new_table->fts->cache->synced_doc_id, + NULL); } } From 3c06a0b7dc121eb606f1d56d0509326e4e7d52b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Fri, 4 Mar 2022 14:23:33 +0200 Subject: [PATCH 08/10] MDEV-28004 ha_innobase::reset_auto_increment() is never executed The virtual member function handler::reset_auto_increment(ulonglong) is only ever invoked by the default implementation of the virtual member function handler::truncate(). Because ha_innobase::truncate() overrides handler::truncate() without ever invoking handler::truncate(), some InnoDB member functions are never called. ha_innobase::innobase_reset_autoinc(), ha_innobase::reset_auto_increment(): Removed (unreachable code). ha_innobase::delete_all_rows(): Removed. The default implementation handler::delete_all_rows() works just as fine. --- storage/innobase/handler/ha_innodb.cc | 68 --------------------------- storage/innobase/handler/ha_innodb.h | 6 +-- 2 files changed, 1 insertion(+), 73 deletions(-) diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index a128d7fb0bc..33aafa00113 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -2702,64 +2702,6 @@ overflow: return(~(ulonglong) 0); } -/********************************************************************//** -Reset the autoinc value in the table. -@return DB_SUCCESS if all went well else error code */ -UNIV_INTERN -dberr_t -ha_innobase::innobase_reset_autoinc( -/*================================*/ - ulonglong autoinc) /*!< in: value to store */ -{ - dberr_t error; - - error = innobase_lock_autoinc(); - - if (error == DB_SUCCESS) { - - dict_table_autoinc_initialize(m_prebuilt->table, autoinc); - - dict_table_autoinc_unlock(m_prebuilt->table); - } - - return(error); -} - -/*******************************************************************//** -Reset the auto-increment counter to the given value, i.e. the next row -inserted will get the given value. This is called e.g. after TRUNCATE -is emulated by doing a 'DELETE FROM t'. HA_ERR_WRONG_COMMAND is -returned by storage engines that don't support this operation. -@return 0 or error code */ -UNIV_INTERN -int -ha_innobase::reset_auto_increment( -/*==============================*/ - ulonglong value) /*!< in: new value for table autoinc */ -{ - DBUG_ENTER("ha_innobase::reset_auto_increment"); - - dberr_t error; - - update_thd(ha_thd()); - - error = row_lock_table_autoinc_for_mysql(m_prebuilt); - - if (error != DB_SUCCESS) { - DBUG_RETURN(convert_error_code_to_mysql( - error, m_prebuilt->table->flags, m_user_thd)); - } - - /* The next value can never be 0. */ - if (value == 0) { - value = 1; - } - - innobase_reset_autoinc(value); - - DBUG_RETURN(0); -} - /*********************************************************************//** Initializes some fields in an InnoDB transaction object. */ static @@ -9119,16 +9061,6 @@ ha_innobase::delete_row( error, m_prebuilt->table->flags, m_user_thd)); } -/** Delete all rows from the table. -@return error number or 0 */ - -int -ha_innobase::delete_all_rows() -{ - DBUG_ENTER("ha_innobase::delete_all_rows"); - DBUG_RETURN(HA_ERR_WRONG_COMMAND); -} - /**********************************************************************//** Removes a new lock set on a row, if it was not read optimistically. This can be called after a row has been read in the processing of an UPDATE or a DELETE diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index b11bb14714d..9468edcf226 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 2000, 2017, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2013, 2021, MariaDB Corporation. +Copyright (c) 2013, 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 @@ -113,8 +113,6 @@ public: double read_time(uint index, uint ranges, ha_rows rows); - int delete_all_rows(); - int write_row(uchar * buf); int update_row(const uchar * old_data, const uchar * new_data); @@ -253,7 +251,6 @@ public: ulonglong nb_desired_values, ulonglong* first_value, ulonglong* nb_reserved_values); - int reset_auto_increment(ulonglong value); virtual bool get_error_message(int error, String *buf); @@ -441,7 +438,6 @@ protected: dberr_t innobase_lock_autoinc(); ulonglong innobase_peek_autoinc(); dberr_t innobase_set_max_autoinc(ulonglong auto_inc); - dberr_t innobase_reset_autoinc(ulonglong auto_inc); /** Resets a query execution 'template'. @see build_template() */ From 5172f132bfc925009ec24f175c8d050d0329e3e5 Mon Sep 17 00:00:00 2001 From: Julius Goryavsky Date: Fri, 4 Mar 2022 14:28:21 +0100 Subject: [PATCH 09/10] galera_3nodes.galera_2_cluster: the test is temporarily disabled --- mysql-test/suite/galera_3nodes/disabled.def | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/suite/galera_3nodes/disabled.def b/mysql-test/suite/galera_3nodes/disabled.def index bbeddc8dcd8..a45e856d519 100644 --- a/mysql-test/suite/galera_3nodes/disabled.def +++ b/mysql-test/suite/galera_3nodes/disabled.def @@ -11,6 +11,7 @@ ############################################################################## GAL-501 : MDEV-24645 galera_3nodes.GAL-501 MTR failed: failed to open gcomm backend connection: 110 +galera_2_cluster : MDEV-22195 temporarily disabled due to issues to be fixed with MDEV-22195 galera_gtid_2_cluster : MDEV-23775 Galera test failure on galera_3nodes.galera_gtid_2_cluster galera_ist_gcache_rollover : MDEV-23578 WSREP: exception caused by message: {v=0,t=1,ut=255,o=4,s=0,sr=0,as=1,f=6,src=50524cfe,srcvid=view_id(REG,50524cfe,4),insvid=view_id(UNKNOWN,00000000,0),ru=00000000,r=[-1,-1],fs=75,nl=(} galera_load_data_ist : MDEV-24639 galera_3nodes.galera_load_data_ist MTR failed with SIGABRT: query 'reap' failed: 2013: Lost connection to MySQL server during query From 86c1bf118a48dd0bab80346f6d65c112ab2e486d Mon Sep 17 00:00:00 2001 From: Vlad Lesin Date: Mon, 7 Mar 2022 13:03:53 +0300 Subject: [PATCH 10/10] MDEV-27992 DELETE fails to delete record after blocking is released MDEV-27025 allows to insert records before the record on which DELETE is locked, as a result the DELETE misses those records, what causes serious ACID violation. Revert MDEV-27025, MDEV-27550. The test which shows the scenario of ACID violation is added. --- mysql-test/suite/galera/disabled.def | 1 - .../suite/innodb/r/lock_delete_updated.result | 20 ++ .../suite/innodb/r/lock_wait_conflict.result | 27 -- .../suite/innodb/t/lock_delete_updated.test | 34 +++ .../suite/innodb/t/lock_wait_conflict.test | 60 ---- mysql-test/suite/versioning/r/update.result | 1 + mysql-test/suite/versioning/t/update.test | 4 +- storage/innobase/include/hash0hash.h | 32 +- storage/innobase/include/lock0lock.h | 53 ++-- storage/innobase/include/lock0lock.inl | 43 ++- storage/innobase/include/lock0priv.h | 23 +- storage/innobase/include/lock0priv.inl | 65 ++-- storage/innobase/include/trx0trx.h | 4 +- storage/innobase/lock/lock0lock.cc | 287 +++++++----------- storage/innobase/lock/lock0prdt.cc | 22 +- 15 files changed, 249 insertions(+), 427 deletions(-) create mode 100644 mysql-test/suite/innodb/r/lock_delete_updated.result delete mode 100644 mysql-test/suite/innodb/r/lock_wait_conflict.result create mode 100644 mysql-test/suite/innodb/t/lock_delete_updated.test delete mode 100644 mysql-test/suite/innodb/t/lock_wait_conflict.test diff --git a/mysql-test/suite/galera/disabled.def b/mysql-test/suite/galera/disabled.def index a6a31348f8b..84babda2fa0 100644 --- a/mysql-test/suite/galera/disabled.def +++ b/mysql-test/suite/galera/disabled.def @@ -12,7 +12,6 @@ MW-328A : MDEV-22666 galera.MW-328A MTR failed: "Semaphore wait has lasted > 600 seconds" and do not release port 16002 MW-328B : MDEV-22666 galera.MW-328A MTR failed: "Semaphore wait has lasted > 600 seconds" and do not release port 16002 -MW-328D : MDEV-27550 ER_LOCK_DEADLOCK is gone after MDEV-27025 MW-329 : MDEV-19962 Galera test failure on MW-329 galera_as_slave_replication_bundle : MDEV-15785 OPTION_GTID_BEGIN is set in Gtid_log_event::do_apply_event() galera_concurrent_ctas : MDEV-24842 Galera test failure on galera_concurrent_ctas diff --git a/mysql-test/suite/innodb/r/lock_delete_updated.result b/mysql-test/suite/innodb/r/lock_delete_updated.result new file mode 100644 index 00000000000..c2cd47b5dd9 --- /dev/null +++ b/mysql-test/suite/innodb/r/lock_delete_updated.result @@ -0,0 +1,20 @@ +CREATE TABLE t(a INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t VALUES (3); +BEGIN; +connection default; +UPDATE t SET a = 2; +connect con1,localhost,root; +DELETE FROM t; +connection default; +UPDATE t SET a = 1; +COMMIT; +connection con1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +disconnect con1; +connection default; +# The above DELETE must delete all the rows in the table, so the +# following SELECT must show 0 rows. +SELECT count(*) FROM t; +count(*) +1 +DROP TABLE t; diff --git a/mysql-test/suite/innodb/r/lock_wait_conflict.result b/mysql-test/suite/innodb/r/lock_wait_conflict.result deleted file mode 100644 index 25d18c03ea1..00000000000 --- a/mysql-test/suite/innodb/r/lock_wait_conflict.result +++ /dev/null @@ -1,27 +0,0 @@ -# -# MDEV-27025 insert-intention lock conflicts with waiting ORDINARY lock -# -CREATE TABLE t (a INT PRIMARY KEY, b INT NOT NULL UNIQUE) ENGINE=InnoDB; -connect prevent_purge,localhost,root,,; -start transaction with consistent snapshot; -connection default; -INSERT INTO t VALUES (20,20); -DELETE FROM t WHERE b = 20; -connect con_ins,localhost,root,,; -SET DEBUG_SYNC = 'row_ins_sec_index_entry_dup_locks_created SIGNAL ins_set_locks WAIT_FOR ins_cont'; -INSERT INTO t VALUES(10, 20); -connect con_del,localhost,root,,; -SET DEBUG_SYNC = 'now WAIT_FOR ins_set_locks'; -SET DEBUG_SYNC = 'lock_wait_suspend_thread_enter SIGNAL del_locked'; -DELETE FROM t WHERE b = 20; -connection default; -SET DEBUG_SYNC = 'now WAIT_FOR del_locked'; -SET DEBUG_SYNC = 'now SIGNAL ins_cont'; -connection con_ins; -disconnect con_ins; -connection con_del; -disconnect con_del; -disconnect prevent_purge; -connection default; -SET DEBUG_SYNC = 'RESET'; -DROP TABLE t; diff --git a/mysql-test/suite/innodb/t/lock_delete_updated.test b/mysql-test/suite/innodb/t/lock_delete_updated.test new file mode 100644 index 00000000000..4621d5fcd2b --- /dev/null +++ b/mysql-test/suite/innodb/t/lock_delete_updated.test @@ -0,0 +1,34 @@ +--source include/have_innodb.inc +--source include/count_sessions.inc + +CREATE TABLE t(a INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t VALUES (3); + +BEGIN; + +connection default; +UPDATE t SET a = 2; + +connect con1,localhost,root; +send DELETE FROM t; + +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Updating" and info = "DELETE FROM t"; +--source include/wait_condition.inc + +UPDATE t SET a = 1; +COMMIT; + +connection con1; +error ER_LOCK_DEADLOCK; +reap; +disconnect con1; + +connection default; +--echo # The above DELETE must delete all the rows in the table, so the +--echo # following SELECT must show 0 rows. +SELECT count(*) FROM t; +DROP TABLE t; +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/innodb/t/lock_wait_conflict.test b/mysql-test/suite/innodb/t/lock_wait_conflict.test deleted file mode 100644 index 46a29e14b43..00000000000 --- a/mysql-test/suite/innodb/t/lock_wait_conflict.test +++ /dev/null @@ -1,60 +0,0 @@ ---source include/have_innodb.inc ---source include/count_sessions.inc ---source include/have_debug.inc ---source include/have_debug_sync.inc - ---echo # ---echo # MDEV-27025 insert-intention lock conflicts with waiting ORDINARY lock ---echo # - -# The test checks the ability to acquire exclusive record lock if the acquiring -# transaction already holds a shared lock on the record and another transaction -# is waiting for a lock. - -CREATE TABLE t (a INT PRIMARY KEY, b INT NOT NULL UNIQUE) ENGINE=InnoDB; - ---connect(prevent_purge,localhost,root,,) -start transaction with consistent snapshot; - ---connection default -INSERT INTO t VALUES (20,20); -DELETE FROM t WHERE b = 20; - ---connect(con_ins,localhost,root,,) -SET DEBUG_SYNC = 'row_ins_sec_index_entry_dup_locks_created SIGNAL ins_set_locks WAIT_FOR ins_cont'; -send -INSERT INTO t VALUES(10, 20); - ---connect(con_del,localhost,root,,) -SET DEBUG_SYNC = 'now WAIT_FOR ins_set_locks'; -SET DEBUG_SYNC = 'lock_wait_suspend_thread_enter SIGNAL del_locked'; -############################################################################### -# This DELETE creates waiting ORDINARY X-lock for heap_no 2 as the record is -# delete-marked, this lock conflicts with ORDINARY S-lock set by the the last -# INSERT. After the last INSERT creates insert-intention lock on -# heap_no 2, this lock will conflict with waiting ORDINARY X-lock of this -# DELETE, what causes DEADLOCK error for this DELETE. -############################################################################### -send -DELETE FROM t WHERE b = 20; - ---connection default -SET DEBUG_SYNC = 'now WAIT_FOR del_locked'; -SET DEBUG_SYNC = 'now SIGNAL ins_cont'; - ---connection con_ins ---reap ---disconnect con_ins - ---connection con_del -# Without the fix, ER_LOCK_DEADLOCK would be reported here. ---reap ---disconnect con_del - ---disconnect prevent_purge - ---connection default - -SET DEBUG_SYNC = 'RESET'; -DROP TABLE t; ---source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/versioning/r/update.result b/mysql-test/suite/versioning/r/update.result index db47b0357a0..d123331cc8c 100644 --- a/mysql-test/suite/versioning/r/update.result +++ b/mysql-test/suite/versioning/r/update.result @@ -283,6 +283,7 @@ connection default; update t1 set b = 'foo'; connection con1; update t1 set a = 'bar'; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction disconnect con1; connection default; drop table t1; diff --git a/mysql-test/suite/versioning/t/update.test b/mysql-test/suite/versioning/t/update.test index f496a287697..058d2f4c865 100644 --- a/mysql-test/suite/versioning/t/update.test +++ b/mysql-test/suite/versioning/t/update.test @@ -186,9 +186,7 @@ send update t1 set b = 'foo'; connection con1; let $wait_condition= select count(*) from information_schema.innodb_lock_waits; source include/wait_condition.inc; -# There must no be DEADLOCK here as con1 transaction already holds locks, and -# default's transaction lock is waiting, so the locks of the following "UPDATE" -# must not conflict with waiting lock. +error ER_LOCK_DEADLOCK; update t1 set a = 'bar'; disconnect con1; connection default; diff --git a/storage/innobase/include/hash0hash.h b/storage/innobase/include/hash0hash.h index 696c58cf1c7..e2565c62169 100644 --- a/storage/innobase/include/hash0hash.h +++ b/storage/innobase/include/hash0hash.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1997, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2018, 2022, MariaDB Corporation. +Copyright (c) 2018, 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 @@ -31,31 +31,7 @@ Created 5/20/1997 Heikki Tuuri #include "sync0rw.h" struct hash_table_t; - -struct hash_cell_t -{ - /** singly-linked, nullptr terminated list of hash buckets */ - void *node; - - /** Insert an element after another. - @tparam T type of the element - @param after the element after which to insert - @param insert the being-inserted element - @param next the next-element pointer in T */ - template - void insert_after(T &after, T &insert, T *T::*next) - { -#ifdef UNIV_DEBUG - for (const T *c= static_cast(node); c; c= c->*next) - if (c == &after) - goto found; - ut_error; - found: -#endif - insert.*next= after.*next; - after.*next= &insert; - } -}; +struct hash_cell_t; typedef void* hash_node_t; @@ -501,6 +477,10 @@ hash_unlock_x_all_but( hash_table_t* table, /*!< in: hash table */ rw_lock_t* keep_lock); /*!< in: lock to keep */ +struct hash_cell_t{ + void* node; /*!< hash chain node, NULL if none */ +}; + /* The hash table structure */ struct hash_table_t { enum hash_table_sync_t type; /*mutex -@param[in] insert_before_waiting if true, inserts new B-tree record lock -just after the last non-waiting lock of the current transaction which is -located before the first waiting for the current transaction lock, otherwise -the lock is inserted at the end of the queue @return created lock */ lock_t* lock_rec_create_low( - lock_t* c_lock, #ifdef WITH_WSREP + lock_t* c_lock, /*!< conflicting lock */ que_thr_t* thr, /*!< thread owning trx */ #endif ulint type_mode, @@ -951,12 +943,9 @@ lock_rec_create_low( ulint heap_no, dict_index_t* index, trx_t* trx, - bool holds_trx_mutex, - bool insert_before_waiting = false); - + bool holds_trx_mutex); /** Enqueue a waiting request for a lock which cannot be granted immediately. Check for deadlocks. -@param[in] c_lock conflicting lock @param[in] type_mode the requested lock mode (LOCK_S or LOCK_X) possibly ORed with LOCK_GAP or LOCK_REC_NOT_GAP, ORed with @@ -975,7 +964,9 @@ Check for deadlocks. (or it happened to commit) */ dberr_t lock_rec_enqueue_waiting( - lock_t* c_lock, +#ifdef WITH_WSREP + lock_t* c_lock, /*!< conflicting lock */ +#endif ulint type_mode, const buf_block_t* block, ulint heap_no, diff --git a/storage/innobase/include/lock0lock.inl b/storage/innobase/include/lock0lock.inl index c6719946b79..abe5052627b 100644 --- a/storage/innobase/include/lock0lock.inl +++ b/storage/innobase/include/lock0lock.inl @@ -101,37 +101,34 @@ lock_hash_get( /*********************************************************************//** Creates a new record lock and inserts it to the lock queue. Does NOT check for deadlocks or lock compatibility! -@param[in] c_lock conflicting lock -@param[in] thr thread owning trx -@param[in] type_mode lock mode and wait flag, type is ignored and replaced by -LOCK_REC -@param[in] block buffer block containing the record -@param[in] heap_no heap number of the record -@param[in] index index of record -@param[in,out] trx transaction -@param[in] caller_owns_trx_mutex TRUE if caller owns trx mutex -@param[in] insert_before_waiting if true, inserts new B-tree record lock -just after the last non-waiting lock of the current transaction which is -located before the first waiting for the current transaction lock, otherwise -the lock is inserted at the end of the queue @return created lock */ UNIV_INLINE -lock_t *lock_rec_create(lock_t *c_lock, +lock_t* +lock_rec_create( +/*============*/ #ifdef WITH_WSREP - que_thr_t *thr, + lock_t* c_lock, /*!< conflicting lock */ + que_thr_t* thr, /*!< thread owning trx */ #endif - ulint type_mode, const buf_block_t *block, - ulint heap_no, dict_index_t *index, trx_t *trx, - bool caller_owns_trx_mutex, - bool insert_before_waiting) + ulint type_mode,/*!< in: lock mode and wait + flag, type is ignored and + replaced by LOCK_REC */ + const buf_block_t* block, /*!< in: buffer block containing + the record */ + ulint heap_no,/*!< in: heap number of the record */ + dict_index_t* index, /*!< in: index of record */ + trx_t* trx, /*!< in,out: transaction */ + bool caller_owns_trx_mutex) + /*!< in: TRUE if caller owns + trx mutex */ { btr_assert_not_corrupted(block, index); - return lock_rec_create_low(c_lock, + return lock_rec_create_low( #ifdef WITH_WSREP - thr, + c_lock, thr, #endif type_mode, block->page.id.space(), block->page.id.page_no(), - block->frame, heap_no, index, trx, - caller_owns_trx_mutex, insert_before_waiting); + block->frame, heap_no, + index, trx, caller_owns_trx_mutex); } diff --git a/storage/innobase/include/lock0priv.h b/storage/innobase/include/lock0priv.h index db3689a2281..b7dcbfa2b86 100644 --- a/storage/innobase/include/lock0priv.h +++ b/storage/innobase/include/lock0priv.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 2007, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2015, 2018, 2022 MariaDB Corporation. +Copyright (c) 2015, 2018, 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 @@ -585,9 +585,6 @@ lock_rec_get_next_const( /*********************************************************************//** Gets the first explicit lock request on a record. -@param[in] hash hash chain the lock on -@param[in] page_id page id -@param[in] heap_no heap number of the record @return first lock, NULL if none exists */ UNIV_INLINE lock_t* @@ -663,26 +660,15 @@ lock_table_has( /** Set the wait status of a lock. @param[in,out] lock lock that will be waited for -@param[in,out] trx transaction that will wait for the lock -@param[in] c_lock conflicting lock */ -inline void lock_set_lock_and_trx_wait(lock_t* lock, trx_t* trx, - const lock_t *c_lock) +@param[in,out] trx transaction that will wait for the lock */ +inline void lock_set_lock_and_trx_wait(lock_t* lock, trx_t* trx) { ut_ad(lock); ut_ad(lock->trx == trx); + ut_ad(trx->lock.wait_lock == NULL); ut_ad(lock_mutex_own()); ut_ad(trx_mutex_own(trx)); - if (trx->lock.wait_trx) { - ut_ad(!c_lock || trx->lock.wait_trx == c_lock->trx); - ut_ad(trx->lock.wait_lock); - ut_ad((*trx->lock.wait_lock).trx == trx); - } else { - ut_ad(c_lock); - trx->lock.wait_trx = c_lock->trx; - ut_ad(!trx->lock.wait_lock); - } - trx->lock.wait_lock = lock; lock->type_mode |= LOCK_WAIT; } @@ -695,7 +681,6 @@ inline void lock_reset_lock_and_trx_wait(lock_t* lock) ut_ad(lock_mutex_own()); ut_ad(lock->trx->lock.wait_lock == NULL || lock->trx->lock.wait_lock == lock); - lock->trx->lock.wait_trx= NULL; lock->trx->lock.wait_lock = NULL; lock->type_mode &= ~LOCK_WAIT; } diff --git a/storage/innobase/include/lock0priv.inl b/storage/innobase/include/lock0priv.inl index 61e8ff18ab1..8bb145e41fc 100644 --- a/storage/innobase/include/lock0priv.inl +++ b/storage/innobase/include/lock0priv.inl @@ -145,19 +145,22 @@ lock_rec_get_first_on_page_addr( return(NULL); } -/** Gets the first record lock on a page, where the page is identified by a +/*********************************************************************//** +Gets the first record lock on a page, where the page is identified by a pointer to it. -@param[in] lock_hash lock hash table -@param[in] space page's space id -@param[in] page_no page number -@param[in] hash page's hash value in records hash table @return first lock, NULL if none exists */ UNIV_INLINE -lock_t *lock_rec_get_first_on_page(hash_table_t *lock_hash, ulint space, - ulint page_no, ulint hash) +lock_t* +lock_rec_get_first_on_page( +/*=======================*/ + hash_table_t* lock_hash, /*!< in: lock hash table */ + const buf_block_t* block) /*!< in: buffer block */ { ut_ad(lock_mutex_own()); + ulint space = block->page.id.space(); + ulint page_no = block->page.id.page_no(); + ulint hash = buf_block_get_lock_hash_val(block); for (lock_t* lock = static_cast( HASH_GET_FIRST(lock_hash, hash)); @@ -174,20 +177,6 @@ lock_t *lock_rec_get_first_on_page(hash_table_t *lock_hash, ulint space, return(NULL); } -/** Gets the first record lock on a page, where the page is identified by a -pointer to it. -@param[in] lock_hash lock hash table -@param[in] block buffer block -@return first lock, NULL if none exists */ -UNIV_INLINE -lock_t *lock_rec_get_first_on_page(hash_table_t *lock_hash, - const buf_block_t *block) -{ - return lock_rec_get_first_on_page(lock_hash, block->page.id.space(), - block->page.id.page_no(), - buf_block_get_lock_hash_val(block)); -} - /*********************************************************************//** Gets the next explicit lock request on a record. @return next lock, NULL if none exists or if heap_no == ULINT_UNDEFINED */ @@ -221,21 +210,21 @@ lock_rec_get_next_const( return(lock_rec_get_next(heap_no, (lock_t*) lock)); } -/** Gets the first explicit lock request on a record. -@param[in] hash hash chain the lock on -@param[in] space record's space id -@param[in] page_no record's page number -@param[in] lock_hash_val page's hash value in records hash table -@param[in] heap_no heap number of the record +/*********************************************************************//** +Gets the first explicit lock request on a record. @return first lock, NULL if none exists */ UNIV_INLINE -lock_t *lock_rec_get_first(hash_table_t *hash, ulint space, ulint page_no, - uint32_t lock_hash_val, ulint heap_no) +lock_t* +lock_rec_get_first( +/*===============*/ + hash_table_t* hash, /*!< in: hash chain the lock on */ + const buf_block_t* block, /*!< in: block containing the record */ + ulint heap_no)/*!< in: heap number of the record */ { ut_ad(lock_mutex_own()); - for (lock_t* lock = lock_rec_get_first_on_page(hash, space, page_no, - lock_hash_val); lock; lock = lock_rec_get_next_on_page(lock)) { + for (lock_t* lock = lock_rec_get_first_on_page(hash, block); lock; + lock = lock_rec_get_next_on_page(lock)) { if (lock_rec_get_nth_bit(lock, heap_no)) { return(lock); } @@ -244,20 +233,6 @@ lock_t *lock_rec_get_first(hash_table_t *hash, ulint space, ulint page_no, return(NULL); } -/** Gets the first explicit lock request on a record. -@param[in] hash hash chain the lock on -@param[in] block block containing the record -@param[in] heap_no heap number of the record -@return first lock, NULL if none exists */ -UNIV_INLINE -lock_t *lock_rec_get_first(hash_table_t *hash, const buf_block_t *block, - ulint heap_no) -{ - return lock_rec_get_first(hash, block->page.id.space(), - block->page.id.page_no(), - buf_block_get_lock_hash_val(block), heap_no); -} - /*********************************************************************//** Gets the nth bit of a record lock. @return TRUE if bit set also if i == ULINT_UNDEFINED return FALSE*/ diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index 1d77297b0e7..d75095ed048 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -436,9 +436,7 @@ struct trx_lock_t { trx_que_t que_state; /*!< valid when trx->state == TRX_STATE_ACTIVE: TRX_QUE_RUNNING, TRX_QUE_LOCK_WAIT, ... */ - /** Transaction being waited for; protected by the same mutexes as - wait_lock */ - trx_t* wait_trx; + lock_t* wait_lock; /*!< if trx execution state is TRX_QUE_LOCK_WAIT, this points to the lock request, otherwise this is diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index 77e98e966c4..37e23b56dfc 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2017, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2014, 2021, 2022, MariaDB Corporation. +Copyright (c) 2014, 2021, 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 @@ -1144,18 +1144,19 @@ static void wsrep_kill_victim(const trx_t * const trx, const lock_t *lock) /*********************************************************************//** Checks if some other transaction has a conflicting explicit lock request in the queue, so that we have to wait. -@param[in] mode LOCK_S or LOCK_X, possibly ORed to LOCK_GAP or LOC_REC_NOT_GAP, -LOCK_INSERT_INTENTION -@param[in] block buffer block containing the record -@param[in] heap_no heap number of the record -@param[in] trx our transaction -@param[out] was_ignored true if conflicting locks waiting for the current -transaction were ignored @return lock or NULL */ -static lock_t *lock_rec_other_has_conflicting(ulint mode, - const buf_block_t *block, - ulint heap_no, const trx_t *trx, - bool *was_ignored= NULL) +static +lock_t* +lock_rec_other_has_conflicting( +/*===========================*/ + ulint mode, /*!< in: LOCK_S or LOCK_X, + possibly ORed to LOCK_GAP or + LOC_REC_NOT_GAP, + LOCK_INSERT_INTENTION */ + const buf_block_t* block, /*!< in: buffer block containing + the record */ + ulint heap_no,/*!< in: heap number of the record */ + const trx_t* trx) /*!< in: our transaction */ { lock_t* lock; @@ -1167,16 +1168,6 @@ static lock_t *lock_rec_other_has_conflicting(ulint mode, lock != NULL; lock = lock_rec_get_next(heap_no, lock)) { - /* There can't be lock loops for one record, because - all waiting locks of the record will always wait for the same - lock of the record in a cell array, and check for - conflicting lock will always start with the first lock for the - heap_no, and go ahead with the same order(the order of the - locks in the cell array) */ - if (lock_get_wait(lock) && lock->trx->lock.wait_trx == trx) { - if (was_ignored) *was_ignored= true; - continue; - } if (lock_rec_has_to_wait(true, trx, mode, lock, is_supremum)) { #ifdef WITH_WSREP if (trx->is_wsrep()) { @@ -1333,7 +1324,6 @@ static void check_trx_state(const trx_t *trx) /** Create a new record lock and inserts it to the lock queue, without checking for deadlocks or conflicts. -@param[in] c_lock conflicting lock @param[in] type_mode lock mode and wait flag; type will be replaced with LOCK_REC @param[in] space tablespace id @@ -1343,15 +1333,11 @@ without checking for deadlocks or conflicts. @param[in] index the index tree @param[in,out] trx transaction @param[in] holds_trx_mutex whether the caller holds trx->mutex -@param[in] insert_before_waiting if true, inserts new B-tree record lock -just after the last non-waiting lock of the current transaction which is -located before the first waiting for the current transaction lock, otherwise -the lock is inserted at the end of the queue @return created lock */ lock_t* lock_rec_create_low( - lock_t* c_lock, #ifdef WITH_WSREP + lock_t* c_lock, /*!< conflicting lock */ que_thr_t* thr, /*!< thread owning trx */ #endif ulint type_mode, @@ -1361,8 +1347,7 @@ lock_rec_create_low( ulint heap_no, dict_index_t* index, trx_t* trx, - bool holds_trx_mutex, - bool insert_before_waiting) + bool holds_trx_mutex) { lock_t* lock; ulint n_bits; @@ -1472,7 +1457,7 @@ lock_rec_create_low( } trx->lock.que_state = TRX_QUE_LOCK_WAIT; - lock_set_lock_and_trx_wait(lock, trx, c_lock); + lock_set_lock_and_trx_wait(lock, trx); UT_LIST_ADD_LAST(trx->lock.trx_locks, lock); trx->lock.wait_thr = thr; @@ -1500,46 +1485,15 @@ lock_rec_create_low( trx_mutex_exit(c_lock->trx); } else #endif /* WITH_WSREP */ - if (insert_before_waiting - && !(type_mode & (LOCK_PREDICATE | LOCK_PRDT_PAGE))) { - /* Try to insert the lock just after the last non-waiting - lock of the current transaction which immediately - precedes the first waiting lock request. */ - uint32_t lock_hash_val = lock_rec_hash(space, page_no); - hash_cell_t& cell = lock_sys.rec_hash->array[lock_hash_val]; - - lock_t* last_non_waiting = NULL; - - for (lock_t* l = lock_rec_get_first(lock_sys.rec_hash, space, - page_no, lock_hash_val, heap_no); l; - l = lock_rec_get_next(heap_no, l)) { - if (lock_get_wait(lock) - && l->trx->lock.wait_trx == trx) { - break; - } - if (l->trx == trx) { - last_non_waiting = l; - } - } - - if (!last_non_waiting) { - goto append_last; - } - - cell.insert_after(*last_non_waiting, *lock, &lock_t::hash); - } - else { -append_last: - if (!(type_mode & (LOCK_WAIT | LOCK_PREDICATE | LOCK_PRDT_PAGE)) - && innodb_lock_schedule_algorithm - == INNODB_LOCK_SCHEDULE_ALGORITHM_VATS - && !thd_is_replication_slave_thread(trx->mysql_thd)) { - HASH_PREPEND(lock_t, hash, lock_sys.rec_hash, - lock_rec_fold(space, page_no), lock); - } else { - HASH_INSERT(lock_t, hash, lock_hash_get(type_mode), - lock_rec_fold(space, page_no), lock); - } + if (!(type_mode & (LOCK_WAIT | LOCK_PREDICATE | LOCK_PRDT_PAGE)) + && innodb_lock_schedule_algorithm + == INNODB_LOCK_SCHEDULE_ALGORITHM_VATS + && !thd_is_replication_slave_thread(trx->mysql_thd)) { + HASH_PREPEND(lock_t, hash, lock_sys.rec_hash, + lock_rec_fold(space, page_no), lock); + } else { + HASH_INSERT(lock_t, hash, lock_hash_get(type_mode), + lock_rec_fold(space, page_no), lock); } if (!holds_trx_mutex) { @@ -1547,7 +1501,7 @@ append_last: } ut_ad(trx_mutex_own(trx)); if (type_mode & LOCK_WAIT) { - lock_set_lock_and_trx_wait(lock, trx, c_lock); + lock_set_lock_and_trx_wait(lock, trx); } UT_LIST_ADD_LAST(trx->lock.trx_locks, lock); if (!holds_trx_mutex) { @@ -1707,7 +1661,6 @@ lock_rec_insert_to_head( /** Enqueue a waiting request for a lock which cannot be granted immediately. Check for deadlocks. -@param[in] c_lock conflicting lock @param[in] type_mode the requested lock mode (LOCK_S or LOCK_X) possibly ORed with LOCK_GAP or LOCK_REC_NOT_GAP, ORed with @@ -1726,7 +1679,9 @@ Check for deadlocks. (or it happened to commit) */ dberr_t lock_rec_enqueue_waiting( - lock_t* c_lock, +#ifdef WITH_WSREP + lock_t* c_lock, /*!< conflicting lock */ +#endif ulint type_mode, const buf_block_t* block, ulint heap_no, @@ -1764,9 +1719,9 @@ lock_rec_enqueue_waiting( /* Enqueue the lock request that will wait to be granted, note that we already own the trx mutex. */ - lock_t* lock = lock_rec_create(c_lock, + lock_t* lock = lock_rec_create( #ifdef WITH_WSREP - thr, + c_lock, thr, #endif type_mode | LOCK_WAIT, block, heap_no, index, trx, TRUE); @@ -1830,20 +1785,22 @@ on the record, and the request to be added is not a waiting request, we can reuse a suitable record lock object already existing on the same page, just setting the appropriate bit in its bitmap. This is a low-level function which does NOT check for deadlocks or lock compatibility! -@param[in] type_mode lock mode, wait, gap etc. flags; type is ignored and -replaced by LOCK_REC -@param[in] block buffer block containing the record -@param[in] heap_no heap number of the record -@param[in] index index of record -@param[in,out] trx transaction -@param[in] caller_owns_trx_mutex, TRUE if caller owns the transaction mutex -@param[in] insert_before_waiting true=insert B-tree record lock right before -a waiting lock request; false=insert the lock at the end of the queue @return lock where the bit was set */ -static void lock_rec_add_to_queue(ulint type_mode, const buf_block_t *block, - ulint heap_no, dict_index_t *index, - trx_t *trx, bool caller_owns_trx_mutex, - bool insert_before_waiting= false) +static +void +lock_rec_add_to_queue( +/*==================*/ + ulint type_mode,/*!< in: lock mode, wait, gap + etc. flags; type is ignored + and replaced by LOCK_REC */ + const buf_block_t* block, /*!< in: buffer block containing + the record */ + ulint heap_no,/*!< in: heap number of the record */ + dict_index_t* index, /*!< in: index of record */ + trx_t* trx, /*!< in/out: transaction */ + bool caller_owns_trx_mutex) + /*!< in: TRUE if caller owns the + transaction mutex */ { #ifdef UNIV_DEBUG ut_ad(lock_mutex_own()); @@ -1932,16 +1889,11 @@ static void lock_rec_add_to_queue(ulint type_mode, const buf_block_t *block, } } - /* Note: We will not pass any conflicting lock to lock_rec_create(), - because we should be moving an existing waiting lock request. */ - ut_ad(!(type_mode & LOCK_WAIT) || trx->lock.wait_trx); - - lock_rec_create(NULL, + lock_rec_create( #ifdef WITH_WSREP - NULL, + NULL, NULL, #endif - type_mode, block, heap_no, index, trx, caller_owns_trx_mutex, - insert_before_waiting); + type_mode, block, heap_no, index, trx, caller_owns_trx_mutex); } /*********************************************************************//** @@ -1997,23 +1949,28 @@ lock_rec_lock( /* Do nothing if the trx already has a strong enough lock on rec */ if (!lock_rec_has_expl(mode, block, heap_no, trx)) { - bool was_ignored = false; - if (lock_t *c_lock= lock_rec_other_has_conflicting( - mode, block, heap_no, trx, &was_ignored)) + if ( +#ifdef WITH_WSREP + lock_t *c_lock= +#endif + lock_rec_other_has_conflicting(mode, block, heap_no, trx)) { /* If another transaction has a non-gap conflicting request in the queue, as this transaction does not have a lock strong enough already granted on the record, we have to wait. */ - err = lock_rec_enqueue_waiting(c_lock, mode, block, heap_no, index, - thr, NULL); + err = lock_rec_enqueue_waiting( +#ifdef WITH_WSREP + c_lock, +#endif /* WITH_WSREP */ + mode, block, heap_no, index, thr, NULL); } else if (!impl) { /* Set the requested lock on the record. */ lock_rec_add_to_queue(LOCK_REC | mode, block, heap_no, index, trx, - true, was_ignored); + true); err= DB_SUCCESS_LOCKED_REC; } } @@ -2039,9 +1996,9 @@ lock_rec_lock( Note that we don't own the trx mutex. */ if (!impl) - lock_rec_create(NULL, + lock_rec_create( #ifdef WITH_WSREP - NULL, + NULL, NULL, #endif mode, block, heap_no, index, trx, false); @@ -2280,17 +2237,8 @@ static void lock_rec_dequeue_from_page(lock_t* in_lock) if (!lock_get_wait(lock)) { continue; } - - ut_ad(lock->trx->lock.wait_trx); - ut_ad(lock->trx->lock.wait_lock); - - if (const lock_t* c = lock_rec_has_to_wait_in_queue( - lock)) { - trx_mutex_enter(lock->trx); - lock->trx->lock.wait_trx = c->trx; - trx_mutex_exit(lock->trx); - } - else { + const lock_t* c = lock_rec_has_to_wait_in_queue(lock); + if (!c) { /* Grant the lock */ ut_ad(lock->trx != in_lock->trx); lock_grant(lock); @@ -2564,8 +2512,7 @@ lock_rec_move_low( lock_rec_reset_nth_bit(lock, donator_heap_no); if (type_mode & LOCK_WAIT) { - ut_ad(lock->trx->lock.wait_lock == lock); - lock->type_mode &= ~LOCK_WAIT; + lock_reset_lock_and_trx_wait(lock); } /* Note that we FIRST reset the bit, and then set the lock: @@ -2682,8 +2629,8 @@ lock_move_reorganize_page( lock_rec_bitmap_reset(lock); if (lock_get_wait(lock)) { - ut_ad(lock->trx->lock.wait_lock == lock); - lock->type_mode&= ~LOCK_WAIT; + + lock_reset_lock_and_trx_wait(lock); } lock = lock_rec_get_next_on_page(lock); @@ -2858,9 +2805,7 @@ lock_move_rec_list_end( ut_ad(!page_rec_is_metadata(orec)); if (type_mode & LOCK_WAIT) { - ut_ad(lock->trx->lock.wait_lock == - lock); - lock->type_mode&= ~LOCK_WAIT; + lock_reset_lock_and_trx_wait(lock); } lock_rec_add_to_queue( @@ -2957,9 +2902,7 @@ lock_move_rec_list_start( ut_ad(!page_rec_is_metadata(prev)); if (type_mode & LOCK_WAIT) { - ut_ad(lock->trx->lock.wait_lock - == lock); - lock->type_mode&= ~LOCK_WAIT; + lock_reset_lock_and_trx_wait(lock); } lock_rec_add_to_queue( @@ -3054,9 +2997,7 @@ lock_rtr_move_rec_list( if (rec1_heap_no < lock->un_member.rec_lock.n_bits && lock_rec_reset_nth_bit(lock, rec1_heap_no)) { if (type_mode & LOCK_WAIT) { - ut_ad(lock->trx->lock.wait_lock - == lock); - lock->type_mode&= ~LOCK_WAIT; + lock_reset_lock_and_trx_wait(lock); } lock_rec_add_to_queue( @@ -3510,8 +3451,10 @@ lock_table_create( in dictionary cache */ ulint type_mode,/*!< in: lock mode possibly ORed with LOCK_WAIT */ - trx_t* trx, /*!< in: trx */ - lock_t* c_lock = NULL /*!< in: conflicting lock */ + trx_t* trx /*!< in: trx */ +#ifdef WITH_WSREP + , lock_t* c_lock = NULL /*!< in: conflicting lock */ +#endif ) { lock_t* lock; @@ -3594,7 +3537,8 @@ lock_table_create( ut_list_append(table->locks, lock, TableLockGetNode()); if (type_mode & LOCK_WAIT) { - lock_set_lock_and_trx_wait(lock, trx, c_lock); + + lock_set_lock_and_trx_wait(lock, trx); } lock->trx->lock.table_locks.push_back(lock); @@ -3749,8 +3693,10 @@ lock_table_enqueue_waiting( ulint mode, /*!< in: lock mode this transaction is requesting */ dict_table_t* table, /*!< in/out: table */ - que_thr_t* thr, /*!< in: query thread */ - lock_t* c_lock /*!< in: conflicting lock or NULL */ + que_thr_t* thr /*!< in: query thread */ +#ifdef WITH_WSREP + , lock_t* c_lock /*!< in: conflicting lock or NULL */ +#endif ) { trx_t* trx; @@ -3781,7 +3727,11 @@ lock_table_enqueue_waiting( #endif /* WITH_WSREP */ /* Enqueue the lock request that will wait to be granted */ - lock = lock_table_create(table, ulint(mode) | LOCK_WAIT, trx, c_lock); + lock = lock_table_create(table, ulint(mode) | LOCK_WAIT, trx +#ifdef WITH_WSREP + , c_lock +#endif + ); const trx_t* victim_trx = DeadlockChecker::check_and_resolve(lock, trx); @@ -3937,7 +3887,11 @@ lock_table( if (wait_for != NULL) { err = lock_table_enqueue_waiting(ulint(mode) | flags, table, - thr, wait_for); + thr +#ifdef WITH_WSREP + , wait_for +#endif + ); } else { lock_table_create(table, ulint(mode) | flags, trx); @@ -3985,7 +3939,7 @@ lock_table_ix_resurrect( Checks if a waiting table lock request still has to wait in a queue. @return TRUE if still has to wait */ static -const lock_t* +bool lock_table_has_to_wait_in_queue( /*============================*/ const lock_t* wait_lock) /*!< in: waiting table lock */ @@ -4004,11 +3958,11 @@ lock_table_has_to_wait_in_queue( if (lock_has_to_wait(wait_lock, lock)) { - return(lock); + return(true); } } - return(NULL); + return(false); } /*************************************************************//** @@ -4037,17 +3991,9 @@ lock_table_dequeue( lock != NULL; lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock)) { - if (!lock_get_wait(lock)) - continue; + if (lock_get_wait(lock) + && !lock_table_has_to_wait_in_queue(lock)) { - ut_ad(lock->trx->lock.wait_trx); - ut_ad(lock->trx->lock.wait_lock); - - if (const lock_t *c = lock_table_has_to_wait_in_queue(lock)) { - trx_mutex_enter(lock->trx); - lock->trx->lock.wait_trx = c->trx; - trx_mutex_exit(lock->trx); - } else { /* Grant the lock */ ut_ad(in_lock->trx != lock->trx); lock_grant(lock); @@ -4243,16 +4189,8 @@ released: if (!lock_get_wait(lock)) { continue; } - ut_ad(lock->trx->lock.wait_trx); - ut_ad(lock->trx->lock.wait_lock); - if (const lock_t* c = lock_rec_has_to_wait_in_queue( - lock)) { - if (lock->trx != trx) - trx_mutex_enter(lock->trx); - lock->trx->lock.wait_trx = c->trx; - if (lock->trx != trx) - trx_mutex_exit(lock->trx); - } else { + const lock_t* c = lock_rec_has_to_wait_in_queue(lock); + if (!c) { /* Grant the lock */ ut_ad(trx != lock->trx); lock_grant(lock); @@ -4983,7 +4921,7 @@ func_exit: wsrep_report_bf_lock_wait(impl_trx->mysql_thd, impl_trx->id); wsrep_report_bf_lock_wait(other_lock->trx->mysql_thd, other_lock->trx->id); - if (!lock_rec_has_expl(LOCK_S | LOCK_REC_NOT_GAP, + if (!lock_rec_has_expl(LOCK_X | LOCK_REC_NOT_GAP, block, heap_no, impl_trx)) { ib::info() << "WSREP impl BF lock conflict"; @@ -4992,20 +4930,7 @@ func_exit: #endif /* WITH_WSREP */ { ut_ad(lock_get_wait(other_lock)); - /* After MDEV-27025 fix the following case is - possible: - 1. trx 1 acquires S-lock; - 2. trx 2 creates X-lock waiting for trx 1; - 3. trx 1 creates implicit lock, as - lock_rec_other_has_conflicting() returns no - conflicting trx 2 X-lock, the explicit lock - will not be created; - 4. trx 3 creates waiting X-lock, - it will wait for S-lock of trx 1. - That is why we relaxing the condition here and - check only for S-lock. - */ - ut_ad(lock_rec_has_expl(LOCK_S | LOCK_REC_NOT_GAP, + ut_ad(lock_rec_has_expl(LOCK_X | LOCK_REC_NOT_GAP, block, heap_no, impl_trx)); } } @@ -5411,13 +5336,19 @@ lock_rec_insert_check_and_lock( const ulint type_mode = LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION; - if (lock_t* c_lock = + if ( +#ifdef WITH_WSREP + lock_t* c_lock = +#endif /* WITH_WSREP */ lock_rec_other_has_conflicting(type_mode, block, heap_no, trx)) { /* Note that we may get DB_SUCCESS also here! */ trx_mutex_enter(trx); - err = lock_rec_enqueue_waiting(c_lock, type_mode, block, - heap_no, index, thr, NULL); + err = lock_rec_enqueue_waiting( +#ifdef WITH_WSREP + c_lock, +#endif /* WITH_WSREP */ + type_mode, block, heap_no, index, thr, NULL); trx_mutex_exit(trx); } else { @@ -5494,7 +5425,7 @@ lock_rec_convert_impl_to_expl_for_trx( && !lock_rec_has_expl(LOCK_X | LOCK_REC_NOT_GAP, block, heap_no, trx)) { lock_rec_add_to_queue(LOCK_REC | LOCK_X | LOCK_REC_NOT_GAP, - block, heap_no, index, trx, true, true); + block, heap_no, index, trx, true); } lock_mutex_exit(); diff --git a/storage/innobase/lock/lock0prdt.cc b/storage/innobase/lock/lock0prdt.cc index 15624cf79af..9827243177d 100644 --- a/storage/innobase/lock/lock0prdt.cc +++ b/storage/innobase/lock/lock0prdt.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 2014, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2018, 2022 MariaDB Corporation. +Copyright (c) 2018, 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 @@ -487,13 +487,9 @@ lock_prdt_add_to_queue( } } - /* Note: We will not pass any conflicting lock to lock_rec_create(), - because we should be moving an existing waiting lock request. */ - ut_ad(!(type_mode & LOCK_WAIT) || trx->lock.wait_trx); - - lock = lock_rec_create(NULL, + lock = lock_rec_create( #ifdef WITH_WSREP - NULL, /* FIXME: replicate SPATIAL INDEX locks */ + NULL, NULL, /* FIXME: replicate SPATIAL INDEX locks */ #endif type_mode, block, PRDT_HEAPNO, index, trx, caller_owns_trx_mutex); @@ -583,7 +579,9 @@ lock_prdt_insert_check_and_lock( trx_mutex_enter(trx); err = lock_rec_enqueue_waiting( +#ifdef WITH_WSREP NULL, /* FIXME: replicate SPATIAL INDEX locks */ +#endif LOCK_X | LOCK_PREDICATE | LOCK_INSERT_INTENTION, block, PRDT_HEAPNO, index, thr, prdt); @@ -831,9 +829,9 @@ lock_prdt_lock( lock_t* lock = lock_rec_get_first_on_page(hash, block); if (lock == NULL) { - lock = lock_rec_create(NULL, + lock = lock_rec_create( #ifdef WITH_WSREP - NULL, /* FIXME: replicate SPATIAL INDEX locks */ + NULL, NULL, /* FIXME: replicate SPATIAL INDEX locks */ #endif ulint(mode) | type_mode, block, PRDT_HEAPNO, index, trx, FALSE); @@ -863,8 +861,10 @@ lock_prdt_lock( if (wait_for != NULL) { err = lock_rec_enqueue_waiting( +#ifdef WITH_WSREP NULL, /* FIXME: replicate SPATIAL INDEX locks */ +#endif ulint(mode) | type_mode, block, PRDT_HEAPNO, index, thr, prdt); @@ -948,9 +948,9 @@ lock_place_prdt_page_lock( } if (lock == NULL) { - lock = lock_rec_create_low(NULL, + lock = lock_rec_create_low( #ifdef WITH_WSREP - NULL, /* FIXME: replicate SPATIAL INDEX locks */ + NULL, NULL, /* FIXME: replicate SPATIAL INDEX locks */ #endif mode, space, page_no, NULL, PRDT_HEAPNO, index, trx, FALSE);