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] 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);