From a0e7bd735b5bc1fd458766c2005d5ac349682d9f Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Thu, 1 Jun 2023 14:06:06 +0300 Subject: [PATCH 1/6] MDEV-31380: Assertion `s->table->opt_range_condition_rows <= s->found_records' failed LooseScan code set opt_range_condition_rows to be the MIN(loose_scan_plan->records, table->records) totally ignoring possible quick range selects. If there was a quick select $QUICK on another index with $QUICK->records < loose_scan_plan->records this would create a situation where opt_range_condition_rows > $QUICK->records which causes an assert in 10.6+ and potentially wrong query plan choice in 10.5. Fixed by making opt_range_condition_rows to be the minimum #rows of any quick select. Approved-by: Monty --- mysql-test/main/group_min_max.result | 24 ++++++++++++++++++++++++ mysql-test/main/group_min_max.test | 25 +++++++++++++++++++++++++ sql/opt_range.cc | 4 ++-- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/mysql-test/main/group_min_max.result b/mysql-test/main/group_min_max.result index 712466c8afb..0df9abc7a25 100644 --- a/mysql-test/main/group_min_max.result +++ b/mysql-test/main/group_min_max.result @@ -4204,6 +4204,30 @@ a b s1 2 2 t2:t2a-null;min_t3_b:t3b-null 3 3 t2:1;min_t3_b:3 drop table t1,t2,t3; +# +# MDEV-31380: Assertion `s->table->opt_range_condition_rows <= s->found_records' failed +# (assertion in 10.6+, DBL_MAX costs in 10.5) +# +CREATE TABLE t1 (a INT, b INT, PRIMARY KEY(a), KEY(b)) ENGINE=InnoDB; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_100; +SET +@tmp=@@optimizer_use_condition_selectivity, +optimizer_use_condition_selectivity = 1, +@tmp2=@@optimizer_trace, +optimizer_trace=1; +SELECT DISTINCT * FROM t1 WHERE a IN (1, 2); +a b +1 1 +2 2 +select +CAST(json_value(json_extract(trace, '$**.chosen_access_method.cost'), '$[0]') +as DOUBLE) < 1.0e100 +from information_schema.optimizer_trace; +CAST(json_value(json_extract(trace, '$**.chosen_access_method.cost'), '$[0]') +as DOUBLE) < 1.0e100 +1 +set optimizer_use_condition_selectivity = @tmp, optimizer_trace=@tmp2; +drop table t1; # # End of 10.5 tests # diff --git a/mysql-test/main/group_min_max.test b/mysql-test/main/group_min_max.test index 1fc2be6231a..482235571db 100644 --- a/mysql-test/main/group_min_max.test +++ b/mysql-test/main/group_min_max.test @@ -6,6 +6,7 @@ --source include/no_valgrind_without_big.inc --source include/default_optimizer_switch.inc --source include/have_innodb.inc +--source include/have_sequence.inc # # TODO: # Add queries with: @@ -1858,6 +1859,30 @@ from t1; drop table t1,t2,t3; +--echo # +--echo # MDEV-31380: Assertion `s->table->opt_range_condition_rows <= s->found_records' failed +--echo # (assertion in 10.6+, DBL_MAX costs in 10.5) +--echo # + +CREATE TABLE t1 (a INT, b INT, PRIMARY KEY(a), KEY(b)) ENGINE=InnoDB; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_100; + +SET + @tmp=@@optimizer_use_condition_selectivity, + optimizer_use_condition_selectivity = 1, + @tmp2=@@optimizer_trace, + optimizer_trace=1; + +SELECT DISTINCT * FROM t1 WHERE a IN (1, 2); + +select + CAST(json_value(json_extract(trace, '$**.chosen_access_method.cost'), '$[0]') + as DOUBLE) < 1.0e100 +from information_schema.optimizer_trace; + +set optimizer_use_condition_selectivity = @tmp, optimizer_trace=@tmp2; +drop table t1; + --echo # --echo # End of 10.5 tests --echo # diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 7b6f373eea4..905d5aa5ef9 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -3030,8 +3030,8 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, restore_nonrange_trees(¶m, tree, backup_keys); if ((group_trp= get_best_group_min_max(¶m, tree, read_time))) { - param.table->opt_range_condition_rows= MY_MIN(group_trp->records, - head->stat_records()); + set_if_smaller(param.table->opt_range_condition_rows, + group_trp->records); Json_writer_object grp_summary(thd, "best_group_range_summary"); if (unlikely(thd->trace_started())) From 91367e82f10fde14ad2eb42928626cabcc0d49cd Mon Sep 17 00:00:00 2001 From: Daniel Bartholomew Date: Wed, 7 Jun 2023 08:10:48 -0400 Subject: [PATCH 2/6] bump the VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 2806542e3ac..e1357a38a48 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ MYSQL_VERSION_MAJOR=10 MYSQL_VERSION_MINOR=5 -MYSQL_VERSION_PATCH=21 +MYSQL_VERSION_PATCH=22 SERVER_MATURITY=stable From 3e40f9a7f3bbe82d96c8acccbb017deebfa00647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Thu, 8 Jun 2023 09:17:52 +0300 Subject: [PATCH 3/6] MDEV-31355 innodb_undo_log_truncate=ON fails to wait for purge of enough transaction history purge_sys_t::sees(): Wrapper for view.sees(). trx_purge_truncate_history(): Invoke purge_sys.sees() instead of comparing to head.trx_no, to determine if undo pages can be safely freed. The test innodb.cursor-restore-locking was adjusted by Vladislav Lesin, as was the the debug instrumentation in row_purge_del_mark(). Reviewed by: Vladislav Lesin --- .../gcol/r/innodb_virtual_debug_purge.result | 2 +- .../gcol/t/innodb_virtual_debug_purge.test | 2 ++ .../innodb/r/cursor-restore-locking.result | 13 ++++--- mysql-test/suite/innodb/r/dml_purge.result | 2 +- .../r/instant_alter_debug,dynamic.rdiff | 6 ---- .../suite/innodb/r/instant_alter_debug.result | 3 +- .../suite/innodb/r/instant_alter_purge.result | 3 +- .../innodb/t/cursor-restore-locking.test | 35 ++++++++++--------- mysql-test/suite/innodb/t/dml_purge.test | 2 +- .../suite/innodb/t/instant_alter_debug.test | 3 ++ .../suite/innodb/t/instant_alter_purge.test | 4 +-- storage/innobase/include/trx0purge.h | 12 +++++++ storage/innobase/row/row0purge.cc | 14 +++++++- storage/innobase/trx/trx0purge.cc | 4 +-- 14 files changed, 68 insertions(+), 37 deletions(-) delete mode 100644 mysql-test/suite/innodb/r/instant_alter_debug,dynamic.rdiff diff --git a/mysql-test/suite/gcol/r/innodb_virtual_debug_purge.result b/mysql-test/suite/gcol/r/innodb_virtual_debug_purge.result index 4a204532630..2820aa7cdbb 100644 --- a/mysql-test/suite/gcol/r/innodb_virtual_debug_purge.result +++ b/mysql-test/suite/gcol/r/innodb_virtual_debug_purge.result @@ -76,7 +76,7 @@ SET DEBUG_SYNC= 'now WAIT_FOR uncommitted'; # enable purge COMMIT; # wait for purge to process the deleted records. -InnoDB 0 transactions not purged +InnoDB 1 transactions not purged SET DEBUG_SYNC= 'now SIGNAL purged'; connection default; /* connection default */ ALTER TABLE t1 ADD COLUMN c INT GENERATED ALWAYS AS(a+b), ADD INDEX idx (c), ALGORITHM=INPLACE, LOCK=SHARED; diff --git a/mysql-test/suite/gcol/t/innodb_virtual_debug_purge.test b/mysql-test/suite/gcol/t/innodb_virtual_debug_purge.test index ca60ed84a98..c8f0cc4c414 100644 --- a/mysql-test/suite/gcol/t/innodb_virtual_debug_purge.test +++ b/mysql-test/suite/gcol/t/innodb_virtual_debug_purge.test @@ -111,7 +111,9 @@ SET DEBUG_SYNC= 'now WAIT_FOR uncommitted'; COMMIT; --echo # wait for purge to process the deleted records. +let $wait_all_purged = 1; --source ../../innodb/include/wait_all_purged.inc +let $wait_all_purged = 0; SET DEBUG_SYNC= 'now SIGNAL purged'; diff --git a/mysql-test/suite/innodb/r/cursor-restore-locking.result b/mysql-test/suite/innodb/r/cursor-restore-locking.result index fc56f0935fa..a792babe5cd 100644 --- a/mysql-test/suite/innodb/r/cursor-restore-locking.result +++ b/mysql-test/suite/innodb/r/cursor-restore-locking.result @@ -1,31 +1,34 @@ SET @save_freq=@@GLOBAL.innodb_purge_rseg_truncate_frequency; SET GLOBAL innodb_purge_rseg_truncate_frequency=1; -CREATE TABLE t (a int PRIMARY KEY, b int NOT NULL UNIQUE) engine = InnoDB; +CREATE TABLE t (a int PRIMARY KEY, b int NOT NULL UNIQUE) engine = InnoDB, STATS_PERSISTENT=0; InnoDB 0 transactions not purged connect prevent_purge,localhost,root,,; start transaction with consistent snapshot; connect con_del_1,localhost,root,,; INSERT INTO t VALUES (20,20); SET DEBUG_SYNC = 'innodb_row_search_for_mysql_exit SIGNAL first_del_row_search_mvcc_finished WAIT_FOR first_del_cont'; -DELETE FROM t WHERE b = 20; +DELETE FROM t WHERE b = 20 # trx_1; connect con_ins_1,localhost,root,,; SET DEBUG_SYNC = 'now WAIT_FOR first_del_row_search_mvcc_finished'; SET DEBUG_SYNC = 'lock_wait_suspend_thread_enter SIGNAL first_ins_locked'; SET DEBUG_SYNC = 'ib_after_row_insert SIGNAL first_ins_row_inserted WAIT_FOR first_ins_cont'; -INSERT INTO t VALUES(10, 20); +INSERT INTO t VALUES(10, 20) # trx_2; connect con_del_2,localhost,root,,; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET DEBUG_SYNC = 'now WAIT_FOR first_ins_locked'; SET DEBUG_SYNC = 'lock_wait_suspend_thread_enter SIGNAL second_del_locked'; -DELETE FROM t WHERE b = 20; +DELETE FROM t WHERE b = 20 # trx_3; connection default; SET DEBUG_SYNC = 'now WAIT_FOR second_del_locked'; +SET @saved_dbug = @@GLOBAL.debug_dbug; +SET @@GLOBAL.debug_dbug="d,enable_row_purge_del_mark_exit_sync_point"; SET DEBUG_SYNC = 'now SIGNAL first_del_cont'; SET DEBUG_SYNC = 'now WAIT_FOR first_ins_row_inserted'; connection con_del_1; connection default; disconnect prevent_purge; -InnoDB 0 transactions not purged +SET DEBUG_SYNC = 'now WAIT_FOR row_purge_del_mark_finished'; +SET @@GLOBAL.debug_dbug = @saved_dbug; SET DEBUG_SYNC = 'now SIGNAL first_ins_cont'; connection con_del_2; connection con_ins_1; diff --git a/mysql-test/suite/innodb/r/dml_purge.result b/mysql-test/suite/innodb/r/dml_purge.result index 38273d571c0..75a5f0fec6c 100644 --- a/mysql-test/suite/innodb/r/dml_purge.result +++ b/mysql-test/suite/innodb/r/dml_purge.result @@ -19,7 +19,7 @@ BEGIN; UPDATE t1 SET b=4 WHERE a=3; disconnect prevent_purge; connection default; -InnoDB 0 transactions not purged +SET GLOBAL innodb_max_purge_lag_wait=1; connection con1; ROLLBACK; disconnect con1; diff --git a/mysql-test/suite/innodb/r/instant_alter_debug,dynamic.rdiff b/mysql-test/suite/innodb/r/instant_alter_debug,dynamic.rdiff deleted file mode 100644 index 379514edad9..00000000000 --- a/mysql-test/suite/innodb/r/instant_alter_debug,dynamic.rdiff +++ /dev/null @@ -1,6 +0,0 @@ -@@ -470,4 +470,4 @@ - FROM information_schema.global_status - WHERE variable_name = 'innodb_instant_alter_column'; - instants --33 -+32 diff --git a/mysql-test/suite/innodb/r/instant_alter_debug.result b/mysql-test/suite/innodb/r/instant_alter_debug.result index 82230573c44..39846a0e329 100644 --- a/mysql-test/suite/innodb/r/instant_alter_debug.result +++ b/mysql-test/suite/innodb/r/instant_alter_debug.result @@ -391,11 +391,12 @@ connection stop_purge; COMMIT; disconnect stop_purge; connection default; -InnoDB 0 transactions not purged +InnoDB 1 transactions not purged SET DEBUG_SYNC='now SIGNAL s2'; connection dml; disconnect dml; connection default; +InnoDB 0 transactions not purged SET DEBUG_SYNC=RESET; DROP TABLE t1; # End of 10.3 tests diff --git a/mysql-test/suite/innodb/r/instant_alter_purge.result b/mysql-test/suite/innodb/r/instant_alter_purge.result index 1179ff62ecc..261356bad12 100644 --- a/mysql-test/suite/innodb/r/instant_alter_purge.result +++ b/mysql-test/suite/innodb/r/instant_alter_purge.result @@ -1,5 +1,6 @@ SET @saved_frequency = @@GLOBAL.innodb_purge_rseg_truncate_frequency; SET GLOBAL innodb_purge_rseg_truncate_frequency=1; +InnoDB 0 transactions not purged # # MDEV-17793 Crash in purge after instant DROP and emptying the table # @@ -16,7 +17,7 @@ COMMIT; START TRANSACTION WITH CONSISTENT SNAPSHOT; connection default; ALTER TABLE t1 ADD COLUMN extra TINYINT UNSIGNED NOT NULL DEFAULT 42; -InnoDB 1 transactions not purged +SET GLOBAL innodb_max_purge_lag_wait=1; ALTER TABLE t1 DROP extra; disconnect prevent_purge; InnoDB 0 transactions not purged diff --git a/mysql-test/suite/innodb/t/cursor-restore-locking.test b/mysql-test/suite/innodb/t/cursor-restore-locking.test index 0f083f9295b..815542c32db 100644 --- a/mysql-test/suite/innodb/t/cursor-restore-locking.test +++ b/mysql-test/suite/innodb/t/cursor-restore-locking.test @@ -5,7 +5,7 @@ source include/have_debug_sync.inc; SET @save_freq=@@GLOBAL.innodb_purge_rseg_truncate_frequency; SET GLOBAL innodb_purge_rseg_truncate_frequency=1; -CREATE TABLE t (a int PRIMARY KEY, b int NOT NULL UNIQUE) engine = InnoDB; +CREATE TABLE t (a int PRIMARY KEY, b int NOT NULL UNIQUE) engine = InnoDB, STATS_PERSISTENT=0; --source include/wait_all_purged.inc --connect(prevent_purge,localhost,root,,) @@ -14,20 +14,20 @@ start transaction with consistent snapshot; --connect(con_del_1,localhost,root,,) INSERT INTO t VALUES (20,20); SET DEBUG_SYNC = 'innodb_row_search_for_mysql_exit SIGNAL first_del_row_search_mvcc_finished WAIT_FOR first_del_cont'; ---send DELETE FROM t WHERE b = 20 +--send DELETE FROM t WHERE b = 20 # trx_1 --connect(con_ins_1,localhost,root,,) SET DEBUG_SYNC = 'now WAIT_FOR first_del_row_search_mvcc_finished'; # It's supposed the following INSERT will be suspended just after # lock_wait_suspend_thread_enter syncpoint, and will be awaken -# after the previous DELETE commits. ib_after_row_insert will be executed -# after the INSERT is woken up. The previous DELETE will wait for +# after trx_1 DELETE commits. ib_after_row_insert will be executed +# after the trx_2 INSERT is woken up. The trx_1 DELETE will wait for # first_del_cont signal before commit, and this signal will be sent later. # So it's safe to use two signals in a row here, it's guaranted the first # signal will be received before the second signal is sent. SET DEBUG_SYNC = 'lock_wait_suspend_thread_enter SIGNAL first_ins_locked'; SET DEBUG_SYNC = 'ib_after_row_insert SIGNAL first_ins_row_inserted WAIT_FOR first_ins_cont'; ---send INSERT INTO t VALUES(10, 20) +--send INSERT INTO t VALUES(10, 20) # trx_2 --connect(con_del_2,localhost,root,,) # After MDEV-30225 is fixed, the following DELETE creates next-key lock for @@ -36,24 +36,26 @@ SET DEBUG_SYNC = 'ib_after_row_insert SIGNAL first_ins_row_inserted WAIT_FOR fir SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET DEBUG_SYNC = 'now WAIT_FOR first_ins_locked'; SET DEBUG_SYNC = 'lock_wait_suspend_thread_enter SIGNAL second_del_locked'; -############################################################################### -# This DELETE is locked by the previous DELETE, after that DELETE is -# committed, it will still be locked by the next INSERT on delete-marked -# heap_no 2 record. After that INSERT inserted the record with heap_no 3, -# and after heap_no 2 record is purged, this DELETE will be unlocked and +############################################################################## +# trx_3 DELETE is locked by trx_1 DELETE, after trx_1 DELETE is +# committed, it will still be locked by trx_2 INSERT on delete-marked +# heap_no 2 record. After trx_2 INSERT inserted the record with heap_no 3, +# and after heap_no 2 record is purged, trx_3 DELETE will be unlocked and # must restore persistent cursor position at heap_no 3 record, as it has the # same secondary key value as former heap_no 2 record. Then it must be blocked -# by the previous INSERT, and after the INSERT is committed, it must -# delete the record, inserted by the previous INSERT, and the last INSERT(see +# by trx_2 INSERT, and after trx_2 INSERT is committed, it must +# delete the record, inserted by trx_2 INSERT, and trx_4 INSERT(see # below) must be finished without error. But instead this DELETE restores # persistent cursor position to supremum, as a result, it does not delete the -# record, inserted by the previous INSERT, and the last INSERT is finished with +# record, inserted by trx_2 INSERT, and trx_4 INSERT is finished with # duplicate key check error. ############################################################################### ---send DELETE FROM t WHERE b = 20 +--send DELETE FROM t WHERE b = 20 # trx_3 --connection default SET DEBUG_SYNC = 'now WAIT_FOR second_del_locked'; +SET @saved_dbug = @@GLOBAL.debug_dbug; +SET @@GLOBAL.debug_dbug="d,enable_row_purge_del_mark_exit_sync_point"; SET DEBUG_SYNC = 'now SIGNAL first_del_cont'; SET DEBUG_SYNC = 'now WAIT_FOR first_ins_row_inserted'; --connection con_del_1 @@ -61,7 +63,8 @@ SET DEBUG_SYNC = 'now WAIT_FOR first_ins_row_inserted'; --connection default --disconnect prevent_purge ---source include/wait_all_purged.inc +SET DEBUG_SYNC = 'now WAIT_FOR row_purge_del_mark_finished'; +SET @@GLOBAL.debug_dbug = @saved_dbug; SET DEBUG_SYNC = 'now SIGNAL first_ins_cont'; --connection con_del_2 @@ -74,7 +77,7 @@ SET DEBUG_SYNC = 'now SIGNAL first_ins_cont'; ############################################################################### # Duplicate key error is expected if the bug is not fixed. ############################################################################### -INSERT INTO t VALUES(30, 20); +INSERT INTO t VALUES(30, 20); # trx_4 --disconnect con_ins_1 --disconnect con_del_1 diff --git a/mysql-test/suite/innodb/t/dml_purge.test b/mysql-test/suite/innodb/t/dml_purge.test index 7034939aa4e..c13ff22572b 100644 --- a/mysql-test/suite/innodb/t/dml_purge.test +++ b/mysql-test/suite/innodb/t/dml_purge.test @@ -32,7 +32,7 @@ UPDATE t1 SET b=4 WHERE a=3; --connection default # Initiate a full purge, which should reset the DB_TRX_ID except for a=3. ---source include/wait_all_purged.inc +SET GLOBAL innodb_max_purge_lag_wait=1; # Initiate a ROLLBACK of the update, which should reset the DB_TRX_ID for a=3. --connection con1 ROLLBACK; diff --git a/mysql-test/suite/innodb/t/instant_alter_debug.test b/mysql-test/suite/innodb/t/instant_alter_debug.test index f102185c27f..c49ab758f24 100644 --- a/mysql-test/suite/innodb/t/instant_alter_debug.test +++ b/mysql-test/suite/innodb/t/instant_alter_debug.test @@ -451,7 +451,9 @@ COMMIT; disconnect stop_purge; connection default; +let $wait_all_purged = 1; --source include/wait_all_purged.inc +let $wait_all_purged = 0; SET DEBUG_SYNC='now SIGNAL s2'; connection dml; @@ -459,6 +461,7 @@ reap; disconnect dml; connection default; +--source include/wait_all_purged.inc SET DEBUG_SYNC=RESET; DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/instant_alter_purge.test b/mysql-test/suite/innodb/t/instant_alter_purge.test index 9ccf3347d7b..88a56141a1f 100644 --- a/mysql-test/suite/innodb/t/instant_alter_purge.test +++ b/mysql-test/suite/innodb/t/instant_alter_purge.test @@ -6,6 +6,7 @@ if ($have_debug) { SET @saved_frequency = @@GLOBAL.innodb_purge_rseg_truncate_frequency; SET GLOBAL innodb_purge_rseg_truncate_frequency=1; +--source include/wait_all_purged.inc --echo # --echo # MDEV-17793 Crash in purge after instant DROP and emptying the table @@ -27,8 +28,7 @@ START TRANSACTION WITH CONSISTENT SNAPSHOT; connection default; ALTER TABLE t1 ADD COLUMN extra TINYINT UNSIGNED NOT NULL DEFAULT 42; -let $wait_all_purged= 1; ---source include/wait_all_purged.inc +SET GLOBAL innodb_max_purge_lag_wait=1; ALTER TABLE t1 DROP extra; disconnect prevent_purge; let $wait_all_purged= 0; diff --git a/storage/innobase/include/trx0purge.h b/storage/innobase/include/trx0purge.h index 14cf6a2958b..b8d349af2b0 100644 --- a/storage/innobase/include/trx0purge.h +++ b/storage/innobase/include/trx0purge.h @@ -253,6 +253,18 @@ public: #endif return view.low_limit_no(); } + /** A wrapper around ReadView::sees(). */ + trx_id_t sees(trx_id_t id) const + { + /* This function may only be called by purge_coordinator_callback(). + + The purge coordinator task may call this without holding any latch, + because it is the only thread that may modify purge_sys.view. + + Any other threads that access purge_sys.view must hold purge_sys.latch, + typically via purge_sys_t::view_guard. */ + return view.sees(id); + } /** A wrapper around trx_sys_t::clone_oldest_view(). */ void clone_oldest_view() { diff --git a/storage/innobase/row/row0purge.cc b/storage/innobase/row/row0purge.cc index 74bbc61df52..6942f5b7af8 100644 --- a/storage/innobase/row/row0purge.cc +++ b/storage/innobase/row/row0purge.cc @@ -46,6 +46,7 @@ Created 3/14/1997 Heikki Tuuri #include "handler.h" #include "ha_innodb.h" #include "fil0fil.h" +#include "debug_sync.h" /************************************************************************* IMPORTANT NOTE: Any operation that generates redo MUST check that there @@ -646,7 +647,18 @@ row_purge_del_mark( mem_heap_free(heap); - return(row_purge_remove_clust_if_poss(node)); + bool result = row_purge_remove_clust_if_poss(node); + +#ifdef ENABLED_DEBUG_SYNC + DBUG_EXECUTE_IF( + "enable_row_purge_del_mark_exit_sync_point", + debug_sync_set_action( + current_thd, + STRING_WITH_LEN( + "now SIGNAL row_purge_del_mark_finished"));); +#endif + + return result; } /** Reset DB_TRX_ID, DB_ROLL_PTR of a clustered index record diff --git a/storage/innobase/trx/trx0purge.cc b/storage/innobase/trx/trx0purge.cc index 2f21a4de1e6..7140b2c8816 100644 --- a/storage/innobase/trx/trx0purge.cc +++ b/storage/innobase/trx/trx0purge.cc @@ -547,7 +547,7 @@ static void trx_purge_truncate_history() mutex_enter(&rseg->mutex); trx_purge_truncate_rseg_history(*rseg, head, !rseg->trx_ref_count && - rseg->needs_purge <= head.trx_no); + purge_sys.sees(rseg->needs_purge)); mutex_exit(&rseg->mutex); } } @@ -604,7 +604,7 @@ static void trx_purge_truncate_history() transactions to finish and to be purged. */ rseg->skip_allocation = true; - if (rseg->trx_ref_count || rseg->needs_purge > head.trx_no) + if (rseg->trx_ref_count || !purge_sys.sees(rseg->needs_purge)) { not_free: mutex_exit(&rseg->mutex); From c25b4967244b1ed7fefbc11ba7e069f5d56daed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Thu, 8 Jun 2023 09:18:21 +0300 Subject: [PATCH 4/6] MDEV-31382 SET GLOBAL innodb_undo_log_truncate=ON has no effect on logically empty undo logs innodb_undo_log_truncate_update(): A callback function. If SET GLOBAL innodb_undo_log_truncate=ON, invoke srv_wake_purge_thread_if_not_active(). srv_wake_purge_thread_if_not_active(): If innodb_undo_log_truncate=ON, always wake up the purge subsystem. srv_do_purge(): If the history is empty, invoke trx_purge_truncate_history() in order to free undo log pages. trx_purge_truncate_history(): If head.trx_no==0, consider the cached undo logs to be free. trx_purge(): Remove the parameter "bool truncate" and let the caller invoke trx_purge_truncate_history() directly. Reviewed by: Vladislav Lesin --- storage/innobase/handler/ha_innodb.cc | 9 ++++++- storage/innobase/include/trx0purge.h | 11 ++++++-- storage/innobase/srv/srv0srv.cc | 37 ++++++++++++++------------- storage/innobase/trx/trx0purge.cc | 18 +++++-------- 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 98385e3b5dd..20200515060 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -19684,10 +19684,17 @@ static MYSQL_SYSVAR_ULONG(purge_rseg_truncate_frequency, " purge rollback segment(s) on every Nth iteration of purge invocation", NULL, NULL, 128, 1, 128, 0); +static void innodb_undo_log_truncate_update(THD *thd, struct st_mysql_sys_var*, + void*, const void *save) +{ + if ((srv_undo_log_truncate= *static_cast(save))) + srv_wake_purge_thread_if_not_active(); +} + static MYSQL_SYSVAR_BOOL(undo_log_truncate, srv_undo_log_truncate, PLUGIN_VAR_OPCMDARG, "Enable or Disable Truncate of UNDO tablespace.", - NULL, NULL, FALSE); + NULL, innodb_undo_log_truncate_update, FALSE); static MYSQL_SYSVAR_LONG(autoinc_lock_mode, innobase_autoinc_lock_mode, PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, diff --git a/storage/innobase/include/trx0purge.h b/storage/innobase/include/trx0purge.h index b8d349af2b0..b693f784ffb 100644 --- a/storage/innobase/include/trx0purge.h +++ b/storage/innobase/include/trx0purge.h @@ -43,12 +43,19 @@ Remove the undo log segment from the rseg slot if it is too big for reuse. @param[in,out] mtr mini-transaction */ void trx_purge_add_undo_to_history(const trx_t* trx, trx_undo_t*& undo, mtr_t* mtr); + +/** +Remove unnecessary history data from rollback segments. NOTE that when this +function is called, the caller (purge_coordinator_callback) +must not have any latches on undo log pages! +*/ +void trx_purge_truncate_history(); + /** Run a purge batch. @param n_tasks number of purge tasks to submit to the queue -@param truncate whether to truncate the history at the end of the batch @return number of undo log pages handled in the batch */ -ulint trx_purge(ulint n_tasks, bool truncate); +ulint trx_purge(ulint n_tasks); /** Rollback segements from a given transaction with trx-no scheduled for purge. */ diff --git a/storage/innobase/srv/srv0srv.cc b/storage/innobase/srv/srv0srv.cc index 50569f810ea..c3e1f6084a8 100644 --- a/storage/innobase/srv/srv0srv.cc +++ b/storage/innobase/srv/srv0srv.cc @@ -1346,17 +1346,14 @@ static tpool::waitable_task purge_coordinator_task static tpool::timer *purge_coordinator_timer; /** Wake up the purge threads if there is work to do. */ -void -srv_wake_purge_thread_if_not_active() +void srv_wake_purge_thread_if_not_active() { - ut_ad(!srv_read_only_mode); + ut_ad(!srv_read_only_mode); - if (purge_sys.enabled() && !purge_sys.paused() - && trx_sys.rseg_history_len) { - if(++purge_state.m_running == 1) { - srv_thread_pool->submit_task(&purge_coordinator_task); - } - } + if (purge_sys.enabled() && !purge_sys.paused() && + (srv_undo_log_truncate || trx_sys.rseg_history_len) && + ++purge_state.m_running == 1) + srv_thread_pool->submit_task(&purge_coordinator_task); } /** @return whether the purge tasks are active */ @@ -1811,8 +1808,8 @@ static size_t srv_do_purge(ulint* n_total_purged) n_threads = n_use_threads = srv_n_purge_threads; srv_purge_thread_count_changed = 0; } else if (trx_sys.rseg_history_len > rseg_history_len - || (srv_max_purge_lag > 0 - && rseg_history_len > srv_max_purge_lag)) { + || (srv_max_purge_lag > 0 + && rseg_history_len > srv_max_purge_lag)) { /* History length is now longer than what it was when we took the last snapshot. Use more threads. */ @@ -1838,15 +1835,19 @@ static size_t srv_do_purge(ulint* n_total_purged) /* Take a snapshot of the history list before purge. */ if (!(rseg_history_len = trx_sys.rseg_history_len)) { - break; + n_pages_purged = 0; + goto truncate; } - n_pages_purged = trx_purge( - n_use_threads, - !(++count % srv_purge_rseg_truncate_frequency) - || purge_sys.truncate.current - || (srv_shutdown_state != SRV_SHUTDOWN_NONE - && srv_fast_shutdown == 0)); + n_pages_purged = trx_purge(n_use_threads); + + if (!(++count % srv_purge_rseg_truncate_frequency) + || purge_sys.truncate.current + || (srv_shutdown_state != SRV_SHUTDOWN_NONE + && srv_fast_shutdown == 0)) { +truncate: + trx_purge_truncate_history(); + } *n_total_purged += n_pages_purged; } while (n_pages_purged > 0 && !purge_sys.paused() diff --git a/storage/innobase/trx/trx0purge.cc b/storage/innobase/trx/trx0purge.cc index 7140b2c8816..c6adaf5f2bf 100644 --- a/storage/innobase/trx/trx0purge.cc +++ b/storage/innobase/trx/trx0purge.cc @@ -522,10 +522,11 @@ __attribute__((optimize(0))) # endif #endif /** -Removes unnecessary history data from rollback segments. NOTE that when this -function is called, the caller must not have any latches on undo log pages! +Remove unnecessary history data from rollback segments. NOTE that when this +function is called, the caller (purge_coordinator_callback) +must not have any latches on undo log pages! */ -static void trx_purge_truncate_history() +void trx_purge_truncate_history() { ut_ad(purge_sys.head <= purge_sys.tail); purge_sys_t::iterator &head= purge_sys.head.trx_no @@ -618,7 +619,7 @@ static void trx_purge_truncate_history() for (const trx_undo_t *undo= UT_LIST_GET_FIRST(rseg->undo_cached); undo; undo= UT_LIST_GET_NEXT(undo_list, undo)) { - if (head.trx_no < undo->trx_id) + if (head.trx_no && head.trx_no < undo->trx_id) goto not_free; else cached+= undo->size; @@ -731,7 +732,7 @@ static void trx_purge_truncate_history() ut_ad(rseg->id == i); ut_ad(rseg->is_persistent()); ut_ad(!rseg->trx_ref_count); - ut_ad(rseg->needs_purge <= head.trx_no); + ut_ad(!head.trx_no || rseg->needs_purge <= head.trx_no); ut_d(const auto old_page= rseg->page_no); buf_block_t *rblock= trx_rseg_header_create(&space, i, @@ -1235,9 +1236,8 @@ static void trx_purge_wait_for_workers_to_complete() /** Run a purge batch. @param n_tasks number of purge tasks to submit to the queue -@param truncate whether to truncate the history at the end of the batch @return number of undo log pages handled in the batch */ -ulint trx_purge(ulint n_tasks, bool truncate) +ulint trx_purge(ulint n_tasks) { que_thr_t* thr = NULL; ulint n_pages_handled; @@ -1271,10 +1271,6 @@ ulint trx_purge(ulint n_tasks, bool truncate) trx_purge_wait_for_workers_to_complete(); - if (truncate) { - trx_purge_truncate_history(); - } - MONITOR_INC_VALUE(MONITOR_PURGE_INVOKED, 1); MONITOR_INC_VALUE(MONITOR_PURGE_N_PAGE_HANDLED, n_pages_handled); From 21031b24fc6d10921edbe2f57a212bf48dc02969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Thu, 8 Jun 2023 09:38:03 +0300 Subject: [PATCH 5/6] Suppress an occasional buffer pool warning --- mysql-test/suite/innodb/t/purge_secondary.test | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mysql-test/suite/innodb/t/purge_secondary.test b/mysql-test/suite/innodb/t/purge_secondary.test index 1a0d178f66a..a7f75f56b53 100644 --- a/mysql-test/suite/innodb/t/purge_secondary.test +++ b/mysql-test/suite/innodb/t/purge_secondary.test @@ -1,6 +1,10 @@ --source include/have_innodb.inc --source include/have_sequence.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Difficult to find free blocks in the buffer pool"); +--enable_query_log + # Ensure that the history list length will actually be decremented by purge. SET @saved_frequency = @@GLOBAL.innodb_purge_rseg_truncate_frequency; SET GLOBAL innodb_purge_rseg_truncate_frequency = 1; From d3eefbaa55edb585e4fbf8f09ad4141c3be900e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Thu, 8 Jun 2023 10:40:48 +0300 Subject: [PATCH 6/6] MDEV-31355 fixup: Adjust one more test The test gcol.gcol_purge would reliably hang on 10.6 on a Microsoft Windows builder without this adjustment. A similar adjustment was applied in commit 3e40f9a7f3bbe82d96c8acccbb017deebfa00647 to the tests innodb.dml_purge and innodb.instant_alter_purge. --- mysql-test/suite/gcol/r/gcol_purge.result | 2 +- mysql-test/suite/gcol/t/gcol_purge.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql-test/suite/gcol/r/gcol_purge.result b/mysql-test/suite/gcol/r/gcol_purge.result index 11063c7cd6f..a130485f219 100644 --- a/mysql-test/suite/gcol/r/gcol_purge.result +++ b/mysql-test/suite/gcol/r/gcol_purge.result @@ -16,7 +16,7 @@ INSERT INTO t1 (f1, f2) VALUES(1,2); set global debug_dbug="+d,ib_purge_virtual_index_callback"; connection con1; COMMIT; -InnoDB 0 transactions not purged +SET GLOBAL innodb_max_purge_lag_wait=1; connection con2; commit; disconnect con1; diff --git a/mysql-test/suite/gcol/t/gcol_purge.test b/mysql-test/suite/gcol/t/gcol_purge.test index ecfd89f4469..8fff375cdc2 100644 --- a/mysql-test/suite/gcol/t/gcol_purge.test +++ b/mysql-test/suite/gcol/t/gcol_purge.test @@ -23,7 +23,7 @@ set global debug_dbug="+d,ib_purge_virtual_index_callback"; connection con1; COMMIT; ---source ../innodb/include/wait_all_purged.inc +SET GLOBAL innodb_max_purge_lag_wait=1; connection con2; commit;