From 446ec646511adf4327f9f5d9414fc4cb75c8161c Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Tue, 1 Mar 2022 13:01:48 +0530 Subject: [PATCH 01/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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 b6a2472489accf0ae9ac3655ffe9b2997ab267ba Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Tue, 22 Feb 2022 17:42:59 +1100 Subject: [PATCH 10/14] MDEV-27891: SIGSEGV in InnoDB buffer pool resize During an increase in resize, the new curr_size got a value less than old_size. As n_chunks_new and n_chunks have a strong correlation to the resizing operation in progress, we can use them and remove the need for old_size. For convienece the n_chunks_new < n_chunks is now the is_shrinking function. The volatile compiler optimization on n_chunks{,_new} is removed as real mutex uses are needed. Other n_chunks_new/n_chunks methods: n_chunks_new and n_chunks almost always read and altered under the pool mutex. Exceptions are: * i_s_innodb_buffer_page_fill, * buf_pool_t::is_uncompressed (via is_blocked_field) These need reexamining for the need of a mutex, however comments indicates this already. get_n_pages has uses in buffer pool load, recover log memory exhaustion estimates and innodb status so take the minimum number of chunks for safety. The buf_pool_t::running_out function also uses curr_size/old_size. We replace this hot function calculation with just n_chunks_new. This is the new size of the chunks before the resizing occurs. If we are resizing down, we've already got the case we had previously (as the minimum). If we are resizing upwards, we are taking an optimistic view that there will be buffer chunks available for locks. As this memory allocation is occurring immediately next the resizing function it seems likely. Compiler hint UNIV_UNLIKELY removed to leave it to the branch predictor to make an informed decision. Added test case of a smaller size than the Marko/Roel original in JIRA reducing the size to 256M. SEGV hits roughly 1/10 times but its better than a 21G memory size. Reviewer: Marko --- .../innodb_buffer_pool_resize_bigtest.result | 13 +++++++++ .../t/innodb_buffer_pool_resize_bigtest.opt | 1 + .../t/innodb_buffer_pool_resize_bigtest.test | 28 +++++++++++++++++++ storage/innobase/buf/buf0buddy.cc | 6 ++-- storage/innobase/buf/buf0buf.cc | 16 ++++------- storage/innobase/buf/buf0flu.cc | 2 +- storage/innobase/buf/buf0lru.cc | 6 ++-- storage/innobase/include/buf0buf.h | 25 +++++++++++------ 8 files changed, 71 insertions(+), 26 deletions(-) create mode 100644 mysql-test/suite/innodb/r/innodb_buffer_pool_resize_bigtest.result create mode 100644 mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.opt create mode 100644 mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.test diff --git a/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_bigtest.result b/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_bigtest.result new file mode 100644 index 00000000000..6035105547f --- /dev/null +++ b/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_bigtest.result @@ -0,0 +1,13 @@ + +MDEV-27891: Delayed SIGSEGV in InnoDB buffer pool resize +after or during DROP TABLE + +select @@innodb_buffer_pool_chunk_size; +@@innodb_buffer_pool_chunk_size +1048576 +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; +SET GLOBAL innodb_buffer_pool_size=256*1024*1024; +DROP TABLE t1; +SET GLOBAL innodb_buffer_pool_size=@@innodb_buffer_pool_size + @@innodb_buffer_pool_chunk_size; +# end of 10.6 test +set global innodb_buffer_pool_size = 8388608;; diff --git a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.opt b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.opt new file mode 100644 index 00000000000..fbc8b098c0d --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.opt @@ -0,0 +1 @@ +--innodb-buffer-pool-chunk-size=1M diff --git a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.test b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.test new file mode 100644 index 00000000000..a4a4b6bb447 --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.test @@ -0,0 +1,28 @@ +--source include/have_innodb.inc +--source include/big_test.inc + +--let $save_size= `SELECT @@GLOBAL.innodb_buffer_pool_size` + +let $wait_timeout = 60; +let $wait_condition = + SELECT SUBSTR(variable_value, 1, 30) = 'Completed resizing buffer pool' + FROM information_schema.global_status + WHERE variable_name = 'INNODB_BUFFER_POOL_RESIZE_STATUS'; + +--echo +--echo MDEV-27891: Delayed SIGSEGV in InnoDB buffer pool resize +--echo after or during DROP TABLE +--echo + +select @@innodb_buffer_pool_chunk_size; +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; +SET GLOBAL innodb_buffer_pool_size=256*1024*1024; +DROP TABLE t1; +--source include/wait_condition.inc +SET GLOBAL innodb_buffer_pool_size=@@innodb_buffer_pool_size + @@innodb_buffer_pool_chunk_size; +--source include/wait_condition.inc + +--echo # end of 10.6 test + +--eval set global innodb_buffer_pool_size = $save_size; +--source include/wait_condition.inc diff --git a/storage/innobase/buf/buf0buddy.cc b/storage/innobase/buf/buf0buddy.cc index 3d476fbac77..85a698bc875 100644 --- a/storage/innobase/buf/buf0buddy.cc +++ b/storage/innobase/buf/buf0buddy.cc @@ -298,7 +298,7 @@ static buf_buddy_free_t* buf_buddy_alloc_zip(ulint i) buf = UT_LIST_GET_FIRST(buf_pool.zip_free[i]); - if (buf_pool.curr_size < buf_pool.old_size + if (buf_pool.is_shrinking() && UT_LIST_GET_LEN(buf_pool.withdraw) < buf_pool.withdraw_target) { @@ -609,7 +609,7 @@ recombine: We may waste up to 15360*max_len bytes to free blocks (1024 + 2048 + 4096 + 8192 = 15360) */ if (UT_LIST_GET_LEN(buf_pool.zip_free[i]) < 16 - && buf_pool.curr_size >= buf_pool.old_size) { + && !buf_pool.is_shrinking()) { goto func_exit; } @@ -715,7 +715,7 @@ buf_buddy_realloc(void* buf, ulint size) void buf_buddy_condense_free() { mysql_mutex_assert_owner(&buf_pool.mutex); - ut_ad(buf_pool.curr_size < buf_pool.old_size); + ut_ad(buf_pool.is_shrinking()); for (ulint i = 0; i < UT_ARR_SIZE(buf_pool.zip_free); ++i) { buf_buddy_free_t* buf = diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index 228db3c4d4e..7b7ab819ff9 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -1200,7 +1200,6 @@ bool buf_pool_t::create() for (size_t i= 0; i < UT_ARR_SIZE(zip_free); ++i) UT_LIST_INIT(zip_free[i], &buf_buddy_free_t::list); ulint s= curr_size; - old_size= s; s/= BUF_READ_AHEAD_PORTION; read_ahead_area= s >= READ_AHEAD_PAGES ? READ_AHEAD_PAGES @@ -1669,7 +1668,6 @@ inline void buf_pool_t::resize() #endif /* BTR_CUR_HASH_ADAPT */ mysql_mutex_lock(&mutex); - ut_ad(curr_size == old_size); ut_ad(n_chunks_new == n_chunks); ut_ad(UT_LIST_GET_LEN(withdraw) == 0); @@ -1678,7 +1676,7 @@ inline void buf_pool_t::resize() curr_size = n_chunks_new * chunks->size; mysql_mutex_unlock(&mutex); - if (curr_size < old_size) { + if (is_shrinking()) { /* set withdraw target */ size_t w = 0; @@ -1699,7 +1697,7 @@ inline void buf_pool_t::resize() withdraw_retry: /* wait for the number of blocks fit to the new size (if needed)*/ - bool should_retry_withdraw = curr_size < old_size + bool should_retry_withdraw = is_shrinking() && withdraw_blocks(); if (srv_shutdown_state != SRV_SHUTDOWN_NONE) { @@ -1782,7 +1780,7 @@ withdraw_retry: ULINTPF " to " ULINTPF ".", n_chunks, n_chunks_new); - if (n_chunks_new < n_chunks) { + if (is_shrinking()) { /* delete chunks */ chunk_t* chunk = chunks + n_chunks_new; const chunk_t* const echunk = chunks + n_chunks; @@ -1846,8 +1844,7 @@ withdraw_retry: goto calc_buf_pool_size; } - ulint n_chunks_copy = ut_min(n_chunks_new, - n_chunks); + ulint n_chunks_copy = ut_min(n_chunks_new, n_chunks); memcpy(new_chunks, chunks, n_chunks_copy * sizeof *new_chunks); @@ -1914,7 +1911,6 @@ calc_buf_pool_size: /* set size */ ut_ad(UT_LIST_GET_LEN(withdraw) == 0); ulint s= curr_size; - old_size= s; s/= BUF_READ_AHEAD_PORTION; read_ahead_area= s >= READ_AHEAD_PAGES ? READ_AHEAD_PAGES @@ -3876,7 +3872,7 @@ void buf_pool_t::validate() mysql_mutex_unlock(&flush_list_mutex); - if (curr_size == old_size + if (n_chunks_new == n_chunks && n_lru + n_free > curr_size + n_zip) { ib::fatal() << "n_LRU " << n_lru << ", n_free " << n_free @@ -3886,7 +3882,7 @@ void buf_pool_t::validate() ut_ad(UT_LIST_GET_LEN(LRU) >= n_lru); - if (curr_size == old_size + if (n_chunks_new == n_chunks && UT_LIST_GET_LEN(free) != n_free) { ib::fatal() << "Free list len " diff --git a/storage/innobase/buf/buf0flu.cc b/storage/innobase/buf/buf0flu.cc index 80ce1d8f2ed..7f7b00b77d5 100644 --- a/storage/innobase/buf/buf0flu.cc +++ b/storage/innobase/buf/buf0flu.cc @@ -1225,7 +1225,7 @@ static void buf_flush_LRU_list_batch(ulint max, flush_counters_t *n) ulint free_limit= srv_LRU_scan_depth; mysql_mutex_assert_owner(&buf_pool.mutex); - if (buf_pool.withdraw_target && buf_pool.curr_size < buf_pool.old_size) + if (buf_pool.withdraw_target && buf_pool.is_shrinking()) free_limit+= buf_pool.withdraw_target - UT_LIST_GET_LEN(buf_pool.withdraw); const auto neighbors= UT_LIST_GET_LEN(buf_pool.LRU) < BUF_LRU_OLD_MIN_LEN diff --git a/storage/innobase/buf/buf0lru.cc b/storage/innobase/buf/buf0lru.cc index 9846b4a6af5..6f8e975bd03 100644 --- a/storage/innobase/buf/buf0lru.cc +++ b/storage/innobase/buf/buf0lru.cc @@ -288,7 +288,7 @@ buf_block_t* buf_LRU_get_free_only() ut_a(!block->page.in_file()); UT_LIST_REMOVE(buf_pool.free, &block->page); - if (buf_pool.curr_size >= buf_pool.old_size + if (!buf_pool.is_shrinking() || UT_LIST_GET_LEN(buf_pool.withdraw) >= buf_pool.withdraw_target || !buf_pool.will_be_withdrawn(block->page)) { @@ -321,7 +321,7 @@ static void buf_LRU_check_size_of_non_data_objects() { mysql_mutex_assert_owner(&buf_pool.mutex); - if (recv_recovery_is_on() || buf_pool.curr_size != buf_pool.old_size) + if (recv_recovery_is_on() || buf_pool.n_chunks_new != buf_pool.n_chunks) return; const auto s= UT_LIST_GET_LEN(buf_pool.free) + UT_LIST_GET_LEN(buf_pool.LRU); @@ -1012,7 +1012,7 @@ buf_LRU_block_free_non_file_page( page_zip_set_size(&block->page.zip, 0); } - if (buf_pool.curr_size < buf_pool.old_size + if (buf_pool.is_shrinking() && UT_LIST_GET_LEN(buf_pool.withdraw) < buf_pool.withdraw_target && buf_pool.will_be_withdrawn(block->page)) { /* This should be withdrawn */ diff --git a/storage/innobase/include/buf0buf.h b/storage/innobase/include/buf0buf.h index c512cd62e2e..a7ecc888137 100644 --- a/storage/innobase/include/buf0buf.h +++ b/storage/innobase/include/buf0buf.h @@ -1353,7 +1353,7 @@ public: { ut_ad(is_initialised()); size_t size= 0; - for (auto j= n_chunks; j--; ) + for (auto j= ut_min(n_chunks_new, n_chunks); j--; ) size+= chunks[j].size; return size; } @@ -1363,7 +1363,7 @@ public: @return whether the frame will be withdrawn */ bool will_be_withdrawn(const byte *ptr) const { - ut_ad(curr_size < old_size); + ut_ad(n_chunks_new < n_chunks); #ifdef SAFE_MUTEX if (resize_in_progress()) mysql_mutex_assert_owner(&mutex); @@ -1383,7 +1383,7 @@ public: @return whether the frame will be withdrawn */ bool will_be_withdrawn(const buf_page_t &bpage) const { - ut_ad(curr_size < old_size); + ut_ad(n_chunks_new < n_chunks); #ifdef SAFE_MUTEX if (resize_in_progress()) mysql_mutex_assert_owner(&mutex); @@ -1540,11 +1540,18 @@ public: inline void watch_remove(buf_page_t *watch, hash_chain &chain); /** @return whether less than 1/4 of the buffer pool is available */ + TPOOL_SUPPRESS_TSAN bool running_out() const { return !recv_recovery_is_on() && - UNIV_UNLIKELY(UT_LIST_GET_LEN(free) + UT_LIST_GET_LEN(LRU) < - std::min(curr_size, old_size) / 4); + UT_LIST_GET_LEN(free) + UT_LIST_GET_LEN(LRU) < + n_chunks_new / 4 * chunks->size; + } + + /** @return whether the buffer pool is shrinking */ + inline bool is_shrinking() const + { + return n_chunks_new < n_chunks; } #ifdef UNIV_DEBUG @@ -1603,15 +1610,15 @@ public: ut_allocator allocator; /*!< Allocator used for allocating memory for the the "chunks" member. */ - volatile ulint n_chunks; /*!< number of buffer pool chunks */ - volatile ulint n_chunks_new; /*!< new number of buffer pool chunks */ + ulint n_chunks; /*!< number of buffer pool chunks */ + ulint n_chunks_new; /*!< new number of buffer pool chunks. + both n_chunks{,new} are protected under + mutex */ chunk_t* chunks; /*!< buffer pool chunks */ chunk_t* chunks_old; /*!< old buffer pool chunks to be freed after resizing buffer pool */ /** current pool size in pages */ Atomic_counter curr_size; - /** previous pool size in pages */ - Atomic_counter old_size; /** read-ahead request size in pages */ Atomic_counter read_ahead_area; From 86c1bf118a48dd0bab80346f6d65c112ab2e486d Mon Sep 17 00:00:00 2001 From: Vlad Lesin Date: Mon, 7 Mar 2022 13:03:53 +0300 Subject: [PATCH 11/14] 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); From ed20e5b111c32e0913d789f8c8b9fc2d8b2b40a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 8 Mar 2022 09:04:03 +0200 Subject: [PATCH 12/14] After-merge fixes --- mysql-test/suite/galera_3nodes/disabled.def | 1 + storage/innobase/include/hash0hash.h | 64 ++++++++++++--------- storage/innobase/include/lock0lock.h | 2 +- storage/innobase/include/lock0lock.inl | 2 +- storage/innobase/include/lock0priv.h | 2 +- storage/innobase/include/lock0priv.inl | 2 +- storage/innobase/include/trx0trx.h | 2 +- storage/innobase/lock/lock0lock.cc | 57 ++++++++---------- storage/innobase/lock/lock0prdt.cc | 2 +- 9 files changed, 70 insertions(+), 64 deletions(-) diff --git a/mysql-test/suite/galera_3nodes/disabled.def b/mysql-test/suite/galera_3nodes/disabled.def index ed13e3e4d87..d3e8808a4ef 100644 --- a/mysql-test/suite/galera_3nodes/disabled.def +++ b/mysql-test/suite/galera_3nodes/disabled.def @@ -12,6 +12,7 @@ GAL-501 : MDEV-24645 galera_3nodes.GAL-501 MTR failed: failed to open gcomm backend connection: 110 GCF-354 : MDEV-25614 Galera test failure on GCF-354 +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 server during query diff --git a/storage/innobase/include/hash0hash.h b/storage/innobase/include/hash0hash.h index 8e7b8dfd1e6..6eb5bb3f183 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, 2021, MariaDB Corporation. +Copyright (c) 2018, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -29,8 +29,43 @@ Created 5/20/1997 Heikki Tuuri #include "ut0new.h" struct hash_table_t; -struct hash_cell_t{ - void* node; /*!< hash chain node, NULL if none */ +struct hash_cell_t +{ + /** singly-linked, nullptr terminated list of hash buckets */ + void *node; + + /** Append an element. + @tparam T type of the element + @param insert the being-inserted element + @param next the next-element pointer in T */ + template + void append(T &insert, T *T::*next) + { + void **after; + for (after= &node; *after; + after= reinterpret_cast(&(static_cast(*after)->*next))); + insert.*next= nullptr; + *after= &insert; + } + + /** 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; + } }; /*******************************************************************//** @@ -59,29 +94,6 @@ do {\ }\ } while (0) -/*******************************************************************//** -Inserts a struct to the head of hash table. */ - -#define HASH_PREPEND(TYPE, NAME, TABLE, FOLD, DATA) \ -do { \ - hash_cell_t* cell3333; \ - TYPE* struct3333; \ - \ - (DATA)->NAME = NULL; \ - \ - cell3333 = &(TABLE)->array[(TABLE)->calc_hash(FOLD)]; \ - \ - if (cell3333->node == NULL) { \ - cell3333->node = DATA; \ - DATA->NAME = NULL; \ - } else { \ - struct3333 = (TYPE*) cell3333->node; \ - \ - DATA->NAME = struct3333; \ - \ - cell3333->node = DATA; \ - } \ -} while (0) #ifdef UNIV_HASH_DEBUG # define HASH_ASSERT_VALID(DATA) ut_a((void*) (DATA) != (void*) -1) # define HASH_INVALIDATE(DATA, NAME) *(void**) (&DATA->NAME) = (void*) -1 diff --git a/storage/innobase/include/lock0lock.h b/storage/innobase/include/lock0lock.h index 296aaccbb6f..28d75517d45 100644 --- a/storage/innobase/include/lock0lock.h +++ b/storage/innobase/include/lock0lock.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, 2021, MariaDB Corporation. +Copyright (c) 2017, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software diff --git a/storage/innobase/include/lock0lock.inl b/storage/innobase/include/lock0lock.inl index 7d08cbb930c..ca64587628a 100644 --- a/storage/innobase/include/lock0lock.inl +++ b/storage/innobase/include/lock0lock.inl @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2015, 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 diff --git a/storage/innobase/include/lock0priv.h b/storage/innobase/include/lock0priv.h index 4e469c6598c..b0a5f7aaf3b 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, 2021, MariaDB Corporation. +Copyright (c) 2015, 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 diff --git a/storage/innobase/include/lock0priv.inl b/storage/innobase/include/lock0priv.inl index 21e7c7c95dc..3b4ebcc835b 100644 --- a/storage/innobase/include/lock0priv.inl +++ b/storage/innobase/include/lock0priv.inl @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 2007, 2014, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2018, 2021, MariaDB Corporation. +Copyright (c) 2018, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index 498b6bb4571..6b9ffdc6374 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2015, 2021, MariaDB Corporation. +Copyright (c) 2015, 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 diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index bebd2acfa0d..fa1ea357fe6 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -1004,19 +1004,18 @@ func_exit: /*********************************************************************//** Checks if some other transaction has a conflicting explicit lock request in the queue, so that we have to wait. -@return lock or NULL */ -static -lock_t* -lock_rec_other_has_conflicting( -/*===========================*/ - unsigned mode, /*!< in: LOCK_S or LOCK_X, - possibly ORed to LOCK_GAP or - LOC_REC_NOT_GAP, - LOCK_INSERT_INTENTION */ - const hash_cell_t& cell, /*!< in: lock hash table cell */ - const page_id_t id, /*!< in: page identifier */ - ulint heap_no,/*!< in: heap number of the record */ - const trx_t* trx) /*!< in: our transaction */ +@param[in] mode LOCK_S or LOCK_X, possibly ORed to LOCK_GAP or LOC_REC_NOT_GAP, +LOCK_INSERT_INTENTION +@param[in] cell lock hash table cell +@param[in] id page identifier +@param[in] heap_no heap number of the record +@param[in] trx our transaction +@return conflicting lock and the flag which indicated if conflicting locks +which wait for the current transaction were ignored */ +static lock_t *lock_rec_other_has_conflicting(unsigned mode, + const hash_cell_t &cell, + const page_id_t id, + ulint heap_no, const trx_t *trx) { bool is_supremum = (heap_no == PAGE_HEAP_NO_SUPREMUM); @@ -1232,7 +1231,7 @@ lock_rec_create_low( ut_ad(index->table->get_ref_count() || !index->table->can_be_evicted); const auto lock_hash = &lock_sys.hash_get(type_mode); - HASH_INSERT(lock_t, hash, lock_hash, page_id.fold(), lock); + lock_hash->cell_get(page_id.fold())->append(*lock, &lock_t::hash); if (type_mode & LOCK_WAIT) { if (trx->lock.wait_trx) { @@ -1258,7 +1257,6 @@ lock_rec_create_low( /** 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 @@ -1357,24 +1355,19 @@ 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! -@return lock where the bit was set */ +@param[in] type_mode lock mode, wait, gap etc. flags +@param[in,out] cell first hash table cell +@param[in] id page identifier +@param[in] page 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 */ TRANSACTIONAL_TARGET -static -void -lock_rec_add_to_queue( -/*==================*/ - unsigned type_mode,/*!< in: lock mode, wait, gap - etc. flags */ - hash_cell_t& cell, /*!< in,out: first hash table cell */ - const page_id_t id, /*!< in: page identifier */ - const page_t* page, /*!< 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 */ +static void lock_rec_add_to_queue(unsigned type_mode, hash_cell_t &cell, + const page_id_t id, const page_t *page, + ulint heap_no, dict_index_t *index, + trx_t *trx, bool caller_owns_trx_mutex) { ut_d(lock_sys.hash_get(type_mode).assert_locked(id)); ut_ad(xtest() || caller_owns_trx_mutex == trx->mutex_is_owner()); diff --git a/storage/innobase/lock/lock0prdt.cc b/storage/innobase/lock/lock0prdt.cc index e924d46bd3f..5a12d97411f 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, 2021, MariaDB Corporation. +Copyright (c) 2018, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software From af345b72a95dbd92c28034d4a0d33c3e27eca437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 8 Mar 2022 09:04:24 +0200 Subject: [PATCH 13/14] MDEV-27891: Make the test work with debug builds --- .../r/innodb_buffer_pool_resize_bigtest.result | 13 +++++++------ .../innodb/t/innodb_buffer_pool_resize_bigtest.opt | 1 + .../t/innodb_buffer_pool_resize_bigtest.test | 14 +++++++------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_bigtest.result b/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_bigtest.result index 6035105547f..d6b29060dc7 100644 --- a/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_bigtest.result +++ b/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_bigtest.result @@ -1,7 +1,8 @@ - -MDEV-27891: Delayed SIGSEGV in InnoDB buffer pool resize -after or during DROP TABLE - +SET @save_size=@@innodb_buffer_pool_size; +# +# MDEV-27891: Delayed SIGSEGV in InnoDB buffer pool resize +# after or during DROP TABLE +# select @@innodb_buffer_pool_chunk_size; @@innodb_buffer_pool_chunk_size 1048576 @@ -9,5 +10,5 @@ CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; SET GLOBAL innodb_buffer_pool_size=256*1024*1024; DROP TABLE t1; SET GLOBAL innodb_buffer_pool_size=@@innodb_buffer_pool_size + @@innodb_buffer_pool_chunk_size; -# end of 10.6 test -set global innodb_buffer_pool_size = 8388608;; +# End of 10.6 tests +SET GLOBAL innodb_buffer_pool_size=@save_size; diff --git a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.opt b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.opt index fbc8b098c0d..72f055d3b58 100644 --- a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.opt +++ b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.opt @@ -1 +1,2 @@ --innodb-buffer-pool-chunk-size=1M +--loose-skip-innodb-disable-resize_buffer_pool_debug diff --git a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.test b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.test index a4a4b6bb447..db5da2924fa 100644 --- a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.test +++ b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_bigtest.test @@ -1,7 +1,7 @@ --source include/have_innodb.inc --source include/big_test.inc ---let $save_size= `SELECT @@GLOBAL.innodb_buffer_pool_size` +SET @save_size=@@innodb_buffer_pool_size; let $wait_timeout = 60; let $wait_condition = @@ -9,10 +9,10 @@ let $wait_condition = FROM information_schema.global_status WHERE variable_name = 'INNODB_BUFFER_POOL_RESIZE_STATUS'; ---echo ---echo MDEV-27891: Delayed SIGSEGV in InnoDB buffer pool resize ---echo after or during DROP TABLE ---echo +--echo # +--echo # MDEV-27891: Delayed SIGSEGV in InnoDB buffer pool resize +--echo # after or during DROP TABLE +--echo # select @@innodb_buffer_pool_chunk_size; CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; @@ -22,7 +22,7 @@ DROP TABLE t1; SET GLOBAL innodb_buffer_pool_size=@@innodb_buffer_pool_size + @@innodb_buffer_pool_chunk_size; --source include/wait_condition.inc ---echo # end of 10.6 test +--echo # End of 10.6 tests ---eval set global innodb_buffer_pool_size = $save_size; +SET GLOBAL innodb_buffer_pool_size=@save_size; --source include/wait_condition.inc From fbef10053087325b055bd4fc68130a98159da14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 8 Mar 2022 09:04:48 +0200 Subject: [PATCH 14/14] Fix an uninitialized variable in debug builds --- storage/innobase/dict/drop.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storage/innobase/dict/drop.cc b/storage/innobase/dict/drop.cc index 0093601f32f..0263c08118e 100644 --- a/storage/innobase/dict/drop.cc +++ b/storage/innobase/dict/drop.cc @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 2021, MariaDB Corporation. +Copyright (c) 2021, 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 @@ -152,7 +152,7 @@ dberr_t trx_t::drop_table(const dict_table_t &table) ut_ad(table.n_lock_x_or_s == 1); ut_ad(UT_LIST_GET_LEN(table.locks) >= 1); #ifdef UNIV_DEBUG - bool found_x; + bool found_x= false; for (lock_t *lock= UT_LIST_GET_FIRST(table.locks); lock; lock= UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock)) {