diff --git a/include/handler_ername.h b/include/handler_ername.h index e969daa7c77..32faa84b52d 100644 --- a/include/handler_ername.h +++ b/include/handler_ername.h @@ -78,5 +78,7 @@ { "HA_ERR_ABORTED_BY_USER", HA_ERR_ABORTED_BY_USER, "" }, { "HA_ERR_DISK_FULL", HA_ERR_DISK_FULL, "" }, { "HA_ERR_INCOMPATIBLE_DEFINITION", HA_ERR_INCOMPATIBLE_DEFINITION, "" }, -{ "HA_ERR_NO_ENCRYPTION", HA_ERR_NO_ENCRYPTION, "" }, { "HA_ERR_COMMIT_ERROR", HA_ERR_COMMIT_ERROR, "" }, +{ "HA_ERR_PARTITION_LIST", HA_ERR_PARTITION_LIST, ""}, +{ "HA_ERR_NO_ENCRYPTION", HA_ERR_NO_ENCRYPTION, ""}, +{ "HA_ERR_ROLLBACK", HA_ERR_ROLLBACK, "" }, diff --git a/include/my_base.h b/include/my_base.h index 44889c4ca96..2ae107c4799 100644 --- a/include/my_base.h +++ b/include/my_base.h @@ -535,7 +535,8 @@ enum ha_base_keytype { #define HA_ERR_COMMIT_ERROR 197 #define HA_ERR_PARTITION_LIST 198 #define HA_ERR_NO_ENCRYPTION 199 -#define HA_ERR_LAST 199 /* Copy of last error nr * */ +#define HA_ERR_ROLLBACK 200 /* Automatic rollback done */ +#define HA_ERR_LAST 200 /* Copy of last error nr * */ /* Number of different errors */ #define HA_ERR_ERRORS (HA_ERR_LAST - HA_ERR_FIRST + 1) diff --git a/include/my_handler_errors.h b/include/my_handler_errors.h index df414888907..4e96945e134 100644 --- a/include/my_handler_errors.h +++ b/include/my_handler_errors.h @@ -110,7 +110,8 @@ static const char *handler_error_messages[]= "Sequence values are conflicting", "Error during commit", "Cannot select partitions", - "Cannot initialize encryption. Check that all encryption parameters have been set" + "Cannot initialize encryption. Check that all encryption parameters have been set", + "Transaction was aborted", }; #endif /* MYSYS_MY_HANDLER_ERRORS_INCLUDED */ diff --git a/mysql-test/include/innodb_rollback_on_timeout.inc b/mysql-test/include/innodb_rollback_on_timeout.inc index d41e1f1ec9f..5fbdaa1c62a 100644 --- a/mysql-test/include/innodb_rollback_on_timeout.inc +++ b/mysql-test/include/innodb_rollback_on_timeout.inc @@ -2,6 +2,10 @@ # Bug #24200: Provide backwards compatibility mode for 4.x "rollback on # transaction timeout" # +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --disable_warnings drop table if exists t1; --enable_warnings diff --git a/mysql-test/main/bug39022.test b/mysql-test/main/bug39022.test index 211a40234b4..81dd6f14bed 100644 --- a/mysql-test/main/bug39022.test +++ b/mysql-test/main/bug39022.test @@ -2,6 +2,10 @@ -- source include/have_innodb.inc -- source include/not_binlog_format_row.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --echo # --echo # Bug #39022: Mysql randomly crashing in lock_sec_rec_cons_read_sees --echo # diff --git a/mysql-test/main/innodb_mysql_lock.test b/mysql-test/main/innodb_mysql_lock.test index 5ee688b1d6e..e61b2fde055 100644 --- a/mysql-test/main/innodb_mysql_lock.test +++ b/mysql-test/main/innodb_mysql_lock.test @@ -1,6 +1,10 @@ --source include/have_metadata_lock_info.inc -- source include/have_innodb.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + # Save the initial number of concurrent sessions. --source include/count_sessions.inc diff --git a/mysql-test/main/long_unique_bugs.test b/mysql-test/main/long_unique_bugs.test index 2f85328bda3..78cf072ed8b 100644 --- a/mysql-test/main/long_unique_bugs.test +++ b/mysql-test/main/long_unique_bugs.test @@ -2,6 +2,10 @@ --source include/have_partition.inc --source include/have_sequence.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --echo # --echo # MDEV-18707 Server crash in my_hash_sort_bin, ASAN heap-use-after-free in Field::is_null, server hang, corrupted double-linked list --echo # diff --git a/mysql-test/main/partition_innodb_plugin.test b/mysql-test/main/partition_innodb_plugin.test index c24945152b3..2699ca2ca1a 100644 --- a/mysql-test/main/partition_innodb_plugin.test +++ b/mysql-test/main/partition_innodb_plugin.test @@ -5,6 +5,9 @@ let $MYSQLD_DATADIR= `SELECT @@datadir`; call mtr.add_suppression("InnoDB: Table .* does not exist in the InnoDB internal data dictionary .*"); call mtr.add_suppression("Deadlock found when trying to get lock; try restarting transaction"); +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log --echo # --echo # Bug#11766879/Bug#60106: DIFF BETWEEN # OF INDEXES IN MYSQL VS INNODB, diff --git a/mysql-test/main/rowid_filter_innodb.test b/mysql-test/main/rowid_filter_innodb.test index d82dccd0551..6b3f9c783e6 100644 --- a/mysql-test/main/rowid_filter_innodb.test +++ b/mysql-test/main/rowid_filter_innodb.test @@ -4,6 +4,10 @@ --source include/have_sequence.inc --source include/innodb_stable_estimates.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + SET SESSION DEFAULT_STORAGE_ENGINE='InnoDB'; SET @save_stats_persistent=@@GLOBAL.innodb_stats_persistent; diff --git a/mysql-test/main/xa.test b/mysql-test/main/xa.test index 77df6831355..a4c60ef316d 100644 --- a/mysql-test/main/xa.test +++ b/mysql-test/main/xa.test @@ -2,13 +2,16 @@ # WL#1756 # -- source include/have_innodb.inc - --source include/not_embedded.inc # Save the initial number of concurrent sessions --source include/count_sessions.inc call mtr.add_suppression("Deadlock found when trying to get lock; try restarting transaction"); +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --disable_warnings drop table if exists t1, t2; diff --git a/mysql-test/suite/galera/r/galera_bf_kill.result b/mysql-test/suite/galera/r/galera_bf_kill.result index 71b0366081b..e6ddf3fc3c5 100644 --- a/mysql-test/suite/galera/r/galera_bf_kill.result +++ b/mysql-test/suite/galera/r/galera_bf_kill.result @@ -1,10 +1,12 @@ connection node_2; connection node_1; connection node_2; +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); CREATE TABLE t1(a int not null primary key auto_increment,b int) engine=InnoDB; insert into t1 values (NULL,1); connect node_2a, 127.0.0.1, root, , test, $NODE_MYPORT_2; connection node_2a; +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); begin; update t1 set a = 5; connection node_2; diff --git a/mysql-test/suite/galera/r/galera_bf_lock_wait.result b/mysql-test/suite/galera/r/galera_bf_lock_wait.result index 9e5cb2d9266..590fcea2997 100644 --- a/mysql-test/suite/galera/r/galera_bf_lock_wait.result +++ b/mysql-test/suite/galera/r/galera_bf_lock_wait.result @@ -1,8 +1,10 @@ connection node_2; connection node_1; connection node_2; +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); call mtr.add_suppression("WSREP: Trying to continue unpaused monitor"); connection node_1; +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); call mtr.add_suppression("WSREP: Trying to continue unpaused monitor"); CREATE TABLE t1 ENGINE=InnoDB select 1 as a, 1 as b union select 2, 2; ALTER TABLE t1 add primary key(a); diff --git a/mysql-test/suite/galera/t/galera_bf_kill.test b/mysql-test/suite/galera/t/galera_bf_kill.test index 1cb2e866cb4..4655a35fde5 100644 --- a/mysql-test/suite/galera/t/galera_bf_kill.test +++ b/mysql-test/suite/galera/t/galera_bf_kill.test @@ -7,11 +7,13 @@ # --connection node_2 +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); CREATE TABLE t1(a int not null primary key auto_increment,b int) engine=InnoDB; insert into t1 values (NULL,1); --connect node_2a, 127.0.0.1, root, , test, $NODE_MYPORT_2 --connection node_2a +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); begin; update t1 set a = 5; diff --git a/mysql-test/suite/galera/t/galera_bf_lock_wait.test b/mysql-test/suite/galera/t/galera_bf_lock_wait.test index 8ef2fee78ed..78a460eda09 100644 --- a/mysql-test/suite/galera/t/galera_bf_lock_wait.test +++ b/mysql-test/suite/galera/t/galera_bf_lock_wait.test @@ -3,9 +3,11 @@ --source include/big_test.inc --connection node_2 +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); call mtr.add_suppression("WSREP: Trying to continue unpaused monitor"); --connection node_1 +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); call mtr.add_suppression("WSREP: Trying to continue unpaused monitor"); CREATE TABLE t1 ENGINE=InnoDB select 1 as a, 1 as b union select 2, 2; diff --git a/mysql-test/suite/innodb/r/innodb-wl5522-debug.result b/mysql-test/suite/innodb/r/innodb-wl5522-debug.result index 6b7bb9b9584..aa8a683620f 100644 --- a/mysql-test/suite/innodb/r/innodb-wl5522-debug.result +++ b/mysql-test/suite/innodb/r/innodb-wl5522-debug.result @@ -456,7 +456,7 @@ restore: t1 .ibd and .cfg files SET SESSION debug_dbug=@saved_debug_dbug; SET SESSION debug_dbug="+d,ib_import_open_tablespace_failure"; ALTER TABLE t1 IMPORT TABLESPACE; -ERROR HY000: Got error 42 'Tablespace not found' from ./test/t1.ibd +ERROR HY000: Got error 41 'Tablespace not found' from ./test/t1.ibd SET SESSION debug_dbug=@saved_debug_dbug; restore: t1 .ibd and .cfg files SET SESSION debug_dbug="+d,ib_import_check_bitmap_failure"; diff --git a/mysql-test/suite/innodb/t/autoinc_debug.test b/mysql-test/suite/innodb/t/autoinc_debug.test index d38a70b3376..f62cfd8afce 100644 --- a/mysql-test/suite/innodb/t/autoinc_debug.test +++ b/mysql-test/suite/innodb/t/autoinc_debug.test @@ -3,6 +3,10 @@ --source include/have_debug_sync.inc --source include/not_embedded.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + # Two parallel connection with autoinc column after restart. CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY)ENGINE=INNODB; diff --git a/mysql-test/suite/innodb/t/deadlock_detect.test b/mysql-test/suite/innodb/t/deadlock_detect.test index a84e6fc328f..39ea174a3ec 100644 --- a/mysql-test/suite/innodb/t/deadlock_detect.test +++ b/mysql-test/suite/innodb/t/deadlock_detect.test @@ -5,6 +5,10 @@ --source include/have_innodb.inc --source include/count_sessions.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + let $have_deadlock=`select @@GLOBAL.innodb_deadlock_detect`; connection default; diff --git a/mysql-test/suite/innodb/t/deadlock_in_subqueries_join.test b/mysql-test/suite/innodb/t/deadlock_in_subqueries_join.test index b3adfb3b02d..beded05198e 100644 --- a/mysql-test/suite/innodb/t/deadlock_in_subqueries_join.test +++ b/mysql-test/suite/innodb/t/deadlock_in_subqueries_join.test @@ -1,6 +1,10 @@ --source include/have_innodb.inc --source include/count_sessions.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + CREATE TABLE t1 ( pkey int NOT NULL PRIMARY KEY, c int diff --git a/mysql-test/suite/innodb/t/deadlock_victim_race.test b/mysql-test/suite/innodb/t/deadlock_victim_race.test index b9a442fc5b9..dac6be698fc 100644 --- a/mysql-test/suite/innodb/t/deadlock_victim_race.test +++ b/mysql-test/suite/innodb/t/deadlock_victim_race.test @@ -2,6 +2,10 @@ --source include/have_debug_sync.inc --source include/count_sessions.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --connect(cancel_purge,localhost,root,,) # Purge can cause deadlock in the test, requesting page's RW_X_LATCH for trx # ids reseting, after trx 2 acqured RW_S_LATCH and suspended in debug sync point diff --git a/mysql-test/suite/innodb/t/deadlock_wait_lock_race.test b/mysql-test/suite/innodb/t/deadlock_wait_lock_race.test index 79a62b098c9..27d02898b72 100644 --- a/mysql-test/suite/innodb/t/deadlock_wait_lock_race.test +++ b/mysql-test/suite/innodb/t/deadlock_wait_lock_race.test @@ -2,6 +2,10 @@ --source include/have_debug_sync.inc --source include/count_sessions.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + # Purge can cause deadlock in the test, requesting page's RW_X_LATCH for trx # ids reseting, after trx 2 acqured RW_S_LATCH and suspended in debug sync point # lock_trx_handle_wait_enter, waiting for upd_cont signal, which must be diff --git a/mysql-test/suite/innodb/t/deadlock_wait_thr_race.test b/mysql-test/suite/innodb/t/deadlock_wait_thr_race.test index 42576f35baf..318db88a470 100644 --- a/mysql-test/suite/innodb/t/deadlock_wait_thr_race.test +++ b/mysql-test/suite/innodb/t/deadlock_wait_thr_race.test @@ -2,6 +2,10 @@ --source include/have_debug_sync.inc --source include/count_sessions.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + # Purge can cause deadlock in the test, requesting page's RW_X_LATCH for trx # ids reseting, after trx 2 acqured RW_S_LATCH and suspended in debug sync point # lock_trx_handle_wait_enter, waiting for upd_cont signal, which must be diff --git a/mysql-test/suite/innodb/t/foreign_key.test b/mysql-test/suite/innodb/t/foreign_key.test index e793e261abd..506cca3245c 100644 --- a/mysql-test/suite/innodb/t/foreign_key.test +++ b/mysql-test/suite/innodb/t/foreign_key.test @@ -2,6 +2,10 @@ --source include/count_sessions.inc --source include/default_charset.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + SET GLOBAL innodb_stats_persistent = 0; --echo # diff --git a/mysql-test/suite/innodb/t/innodb-lock.test b/mysql-test/suite/innodb/t/innodb-lock.test index fbbb1035481..394681702c0 100644 --- a/mysql-test/suite/innodb/t/innodb-lock.test +++ b/mysql-test/suite/innodb/t/innodb-lock.test @@ -1,6 +1,10 @@ --source include/have_innodb.inc --source include/have_partition.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + # # Check and select innodb lock type # diff --git a/mysql-test/suite/innodb/t/innodb_bug42419.test b/mysql-test/suite/innodb/t/innodb_bug42419.test index ef350529604..2314a2b43ab 100644 --- a/mysql-test/suite/innodb/t/innodb_bug42419.test +++ b/mysql-test/suite/innodb/t/innodb_bug42419.test @@ -4,6 +4,10 @@ # Bug#42419 Server crash with "Pure virtual method called" on two concurrent connections # +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --source include/not_embedded.inc let $innodb_lock_wait_timeout= query_get_value(SHOW VARIABLES LIKE 'innodb_lock_wait_timeout%', Value, 1); diff --git a/mysql-test/suite/innodb/t/innodb_mysql_rbk.test b/mysql-test/suite/innodb/t/innodb_mysql_rbk.test index 85a9769732f..ffdfaf48ea6 100644 --- a/mysql-test/suite/innodb/t/innodb_mysql_rbk.test +++ b/mysql-test/suite/innodb/t/innodb_mysql_rbk.test @@ -1,5 +1,9 @@ --source include/have_innodb.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + # # Bug #41453: Assertion `m_status == DA_ERROR' failed in # Diagnostics_area::sql_errno diff --git a/mysql-test/suite/innodb/t/innodb_trx_weight.test b/mysql-test/suite/innodb/t/innodb_trx_weight.test index 819f05f331e..a8c29a1a8a8 100644 --- a/mysql-test/suite/innodb/t/innodb_trx_weight.test +++ b/mysql-test/suite/innodb/t/innodb_trx_weight.test @@ -6,6 +6,10 @@ # be heavier than ones that had not. # +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + -- source include/have_innodb.inc SET default_storage_engine=InnoDB; diff --git a/mysql-test/suite/innodb/t/insert-before-delete.test b/mysql-test/suite/innodb/t/insert-before-delete.test index 2547b94e5ea..1cadbf7c0c6 100644 --- a/mysql-test/suite/innodb/t/insert-before-delete.test +++ b/mysql-test/suite/innodb/t/insert-before-delete.test @@ -3,6 +3,10 @@ --source include/have_debug_sync.inc --source include/count_sessions.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --connect (pause_purge,localhost,root) START TRANSACTION WITH CONSISTENT SNAPSHOT; diff --git a/mysql-test/suite/innodb/t/lock_delete_updated.test b/mysql-test/suite/innodb/t/lock_delete_updated.test index 1f7b13144e4..8697ff595ab 100644 --- a/mysql-test/suite/innodb/t/lock_delete_updated.test +++ b/mysql-test/suite/innodb/t/lock_delete_updated.test @@ -3,6 +3,10 @@ --source include/have_debug.inc --source include/have_debug_sync.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + CREATE TABLE t(a INT PRIMARY KEY) ENGINE=InnoDB; INSERT INTO t VALUES (3); diff --git a/mysql-test/suite/innodb/t/lock_isolation.test b/mysql-test/suite/innodb/t/lock_isolation.test index c9658c0a362..bac034a50e4 100644 --- a/mysql-test/suite/innodb/t/lock_isolation.test +++ b/mysql-test/suite/innodb/t/lock_isolation.test @@ -1,5 +1,9 @@ --source include/have_innodb.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --echo # --echo # MDEV-26642 Weird SELECT view when a record is --echo # modified to the same value by two transactions diff --git a/mysql-test/suite/innodb/t/lock_memory_debug.test b/mysql-test/suite/innodb/t/lock_memory_debug.test index 51e0e8fd57e..588356f7fa4 100644 --- a/mysql-test/suite/innodb/t/lock_memory_debug.test +++ b/mysql-test/suite/innodb/t/lock_memory_debug.test @@ -6,6 +6,9 @@ --echo # call mtr.add_suppression("\\[Warning\\] InnoDB: Over 67 percent of the buffer pool"); +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log CREATE TABLE t1 (col1 INT) ENGINE=InnoDB; INSERT INTO t1 VALUES (1),(2),(3),(4),(5); diff --git a/mysql-test/suite/innodb/t/mdev-14846.test b/mysql-test/suite/innodb/t/mdev-14846.test index e9698cc0594..75b634a0908 100644 --- a/mysql-test/suite/innodb/t/mdev-14846.test +++ b/mysql-test/suite/innodb/t/mdev-14846.test @@ -4,6 +4,10 @@ --source include/innodb_stable_estimates.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + CREATE TABLE t1 ( pk INT, f1 VARCHAR(10) NOT NULL, diff --git a/mysql-test/suite/innodb/t/row_lock.test b/mysql-test/suite/innodb/t/row_lock.test index 361bce461f7..2598189d0e2 100644 --- a/mysql-test/suite/innodb/t/row_lock.test +++ b/mysql-test/suite/innodb/t/row_lock.test @@ -4,6 +4,10 @@ # concurrent CREATE OR REPLACE and transactional UPDATE # +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --source include/have_innodb.inc CREATE TABLE t1 (a INT, b INT) ENGINE=InnoDB; diff --git a/mysql-test/suite/innodb/t/stat_tables.test b/mysql-test/suite/innodb/t/stat_tables.test index 602d05915a0..0aaabb26803 100644 --- a/mysql-test/suite/innodb/t/stat_tables.test +++ b/mysql-test/suite/innodb/t/stat_tables.test @@ -1,5 +1,9 @@ source include/have_innodb.inc; +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + # # MDEV-20354 All but last insert ignored in InnoDB tables when table locked # diff --git a/mysql-test/suite/innodb_fts/r/savepoint.result b/mysql-test/suite/innodb_fts/r/savepoint.result index 1abfc961d0a..b2d53c36fbe 100644 --- a/mysql-test/suite/innodb_fts/r/savepoint.result +++ b/mysql-test/suite/innodb_fts/r/savepoint.result @@ -249,9 +249,33 @@ id title 7 mysql TRUNCATE TABLE articles; INSERT INTO articles(id, title) VALUES(1, 'mysql'); +CREATE TABLE t(a INT PRIMARY KEY) ENGINE=InnoDB; BEGIN; +INSERT INTO t SET a=1; +SAVEPOINT t; +INSERT INTO articles(id, title) VALUES(2, 'mysql'); +SELECT * FROM articles WHERE MATCH(title) AGAINST('mysql'); +id title +1 mysql +ROLLBACK TO SAVEPOINT t; +SELECT * FROM articles WHERE MATCH(title) AGAINST('mysql'); +id title +1 mysql +SELECT * FROM t; +a +1 +COMMIT; +SELECT * FROM articles WHERE MATCH(title) AGAINST('mysql'); +id title +1 mysql +BEGIN; +INSERT INTO t SET a=2; INSERT INTO articles(id, title) VALUES(2, 'mysql'); ROLLBACK; +SELECT * FROM t; +a +1 +DROP TABLE t; INSERT INTO articles(id, title) VALUES(3, 'mysql'); SELECT * FROM articles WHERE MATCH(title) AGAINST('mysql'); id title diff --git a/mysql-test/suite/innodb_fts/t/savepoint.test b/mysql-test/suite/innodb_fts/t/savepoint.test index 09ccb383bb9..ae83c23d20c 100644 --- a/mysql-test/suite/innodb_fts/t/savepoint.test +++ b/mysql-test/suite/innodb_fts/t/savepoint.test @@ -366,12 +366,28 @@ TRUNCATE TABLE articles; INSERT INTO articles(id, title) VALUES(1, 'mysql'); +CREATE TABLE t(a INT PRIMARY KEY) ENGINE=InnoDB; BEGIN; +INSERT INTO t SET a=1; +SAVEPOINT t; +INSERT INTO articles(id, title) VALUES(2, 'mysql'); +SELECT * FROM articles WHERE MATCH(title) AGAINST('mysql'); +ROLLBACK TO SAVEPOINT t; +SELECT * FROM articles WHERE MATCH(title) AGAINST('mysql'); +SELECT * FROM t; +COMMIT; +SELECT * FROM articles WHERE MATCH(title) AGAINST('mysql'); + +BEGIN; +INSERT INTO t SET a=2; INSERT INTO articles(id, title) VALUES(2, 'mysql'); ROLLBACK; +SELECT * FROM t; +DROP TABLE t; + INSERT INTO articles(id, title) VALUES(3, 'mysql'); SELECT * FROM articles WHERE MATCH(title) AGAINST('mysql'); diff --git a/mysql-test/suite/innodb_zip/r/wl5522_debug_zip.result b/mysql-test/suite/innodb_zip/r/wl5522_debug_zip.result index 5ce6affeb0c..1f4d9e592ab 100644 --- a/mysql-test/suite/innodb_zip/r/wl5522_debug_zip.result +++ b/mysql-test/suite/innodb_zip/r/wl5522_debug_zip.result @@ -91,7 +91,7 @@ restore: t1 .ibd and .cfg files SET SESSION debug_dbug=@saved_debug_dbug; SET SESSION debug_dbug="+d,ib_import_open_tablespace_failure"; ALTER TABLE t1 IMPORT TABLESPACE; -ERROR HY000: Got error 42 'Tablespace not found' from ./test/t1.ibd +ERROR HY000: Got error 41 'Tablespace not found' from ./test/t1.ibd SET SESSION debug_dbug=@saved_debug_dbug; restore: t1 .ibd and .cfg files SET SESSION debug_dbug="+d,ib_import_check_bitmap_failure"; diff --git a/mysql-test/suite/multi_source/gtid_ignore_duplicates.test b/mysql-test/suite/multi_source/gtid_ignore_duplicates.test index cbc06920b41..634ff002cbc 100644 --- a/mysql-test/suite/multi_source/gtid_ignore_duplicates.test +++ b/mysql-test/suite/multi_source/gtid_ignore_duplicates.test @@ -2,6 +2,9 @@ --source include/have_innodb.inc --source include/have_debug.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log --echo *** Test all-to-all replication with --gtid-ignore-duplicates *** diff --git a/mysql-test/suite/rpl/r/rpl_mark_optimize_tbl_ddl.result b/mysql-test/suite/rpl/r/rpl_mark_optimize_tbl_ddl.result index 09edd28827b..6296196104b 100644 --- a/mysql-test/suite/rpl/r/rpl_mark_optimize_tbl_ddl.result +++ b/mysql-test/suite/rpl/r/rpl_mark_optimize_tbl_ddl.result @@ -36,9 +36,9 @@ Table Op Msg_type Msg_text test.t1 analyze status Engine-independent statistics collected test.t1 analyze status OK FLUSH LOGS; -FOUND 1 /GTID 0-1-8 ddl/ in mysqlbinlog.out FOUND 1 /GTID 0-1-9 ddl/ in mysqlbinlog.out FOUND 1 /GTID 0-1-10 ddl/ in mysqlbinlog.out +FOUND 1 /GTID 0-1-11 ddl/ in mysqlbinlog.out # # Clean up # @@ -63,9 +63,9 @@ ALTER TABLE t1 REPAIR PARTITION p0; Table Op Msg_type Msg_text test.t1 repair status OK FLUSH LOGS; -FOUND 1 /GTID 0-1-14 ddl/ in mysqlbinlog.out FOUND 1 /GTID 0-1-15 ddl/ in mysqlbinlog.out FOUND 1 /GTID 0-1-16 ddl/ in mysqlbinlog.out +FOUND 1 /GTID 0-1-17 ddl/ in mysqlbinlog.out # # Clean up # diff --git a/mysql-test/suite/rpl/t/rpl_mark_optimize_tbl_ddl.test b/mysql-test/suite/rpl/t/rpl_mark_optimize_tbl_ddl.test index 6d66e3fd088..4c9b57e4bda 100644 --- a/mysql-test/suite/rpl/t/rpl_mark_optimize_tbl_ddl.test +++ b/mysql-test/suite/rpl/t/rpl_mark_optimize_tbl_ddl.test @@ -22,6 +22,10 @@ --let $rpl_topology=1->2 --source include/rpl_init.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --connection server_1 FLUSH TABLES; ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB; diff --git a/mysql-test/suite/rpl/t/rpl_parallel_deadlock_victim.test b/mysql-test/suite/rpl/t/rpl_parallel_deadlock_victim.test index ab634d2953e..c1876a43dca 100644 --- a/mysql-test/suite/rpl/t/rpl_parallel_deadlock_victim.test +++ b/mysql-test/suite/rpl/t/rpl_parallel_deadlock_victim.test @@ -2,6 +2,9 @@ --source include/have_debug.inc --source include/master-slave.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log --echo MDEV-31655: Parallel replication deadlock victim preference code erroneously removed # The problem was that InnoDB would choose the wrong deadlock victim. diff --git a/mysql-test/suite/rpl/t/rpl_parallel_deadlock_victim2.test b/mysql-test/suite/rpl/t/rpl_parallel_deadlock_victim2.test index 522cec18bbc..68d5ad6a481 100644 --- a/mysql-test/suite/rpl/t/rpl_parallel_deadlock_victim2.test +++ b/mysql-test/suite/rpl/t/rpl_parallel_deadlock_victim2.test @@ -3,6 +3,10 @@ --source include/have_debug.inc --source include/have_binlog_format_statement.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --connection master ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB; CREATE TABLE t1(a INT) ENGINE=INNODB; diff --git a/mysql-test/suite/rpl/t/rpl_parallel_innodb_lock_conflict.test b/mysql-test/suite/rpl/t/rpl_parallel_innodb_lock_conflict.test index 90304937445..532eb58571c 100644 --- a/mysql-test/suite/rpl/t/rpl_parallel_innodb_lock_conflict.test +++ b/mysql-test/suite/rpl/t/rpl_parallel_innodb_lock_conflict.test @@ -5,6 +5,10 @@ --source include/have_debug_sync.inc --source include/master-slave.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --connection server_2 SET sql_log_bin=0; CALL mtr.add_suppression("Commit failed due to failure of an earlier commit on which this one depends"); diff --git a/mysql-test/suite/rpl/t/rpl_parallel_optimistic.test b/mysql-test/suite/rpl/t/rpl_parallel_optimistic.test index 09da8c2df15..988221d4c18 100644 --- a/mysql-test/suite/rpl/t/rpl_parallel_optimistic.test +++ b/mysql-test/suite/rpl/t/rpl_parallel_optimistic.test @@ -7,6 +7,9 @@ call mtr.add_suppression("Deadlock found when trying to get lock; try restarting transaction"); call mtr.add_suppression("Can't find record in 't1'"); call mtr.add_suppression("Can't find record in 't2'"); +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log --connection server_1 ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB; diff --git a/mysql-test/suite/rpl/t/rpl_parallel_optimistic_nobinlog.test b/mysql-test/suite/rpl/t/rpl_parallel_optimistic_nobinlog.test index ee0de499edf..0b7a76aa78b 100644 --- a/mysql-test/suite/rpl/t/rpl_parallel_optimistic_nobinlog.test +++ b/mysql-test/suite/rpl/t/rpl_parallel_optimistic_nobinlog.test @@ -3,6 +3,10 @@ --let $rpl_topology=1->2 --source include/rpl_init.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --connection server_1 ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB; CREATE TABLE t1 (a int PRIMARY KEY, b INT) ENGINE=InnoDB; diff --git a/mysql-test/suite/rpl/t/rpl_parallel_xa_same_xid.test b/mysql-test/suite/rpl/t/rpl_parallel_xa_same_xid.test index 888dd2f177b..17c404adef8 100644 --- a/mysql-test/suite/rpl/t/rpl_parallel_xa_same_xid.test +++ b/mysql-test/suite/rpl/t/rpl_parallel_xa_same_xid.test @@ -9,6 +9,10 @@ --source include/have_perfschema.inc --source include/master-slave.inc +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + --let $xid_num = 19 --let $repeat = 17 --let $workers = 7 diff --git a/mysql-test/suite/rpl/t/rpl_temporary_error2.test b/mysql-test/suite/rpl/t/rpl_temporary_error2.test index 3537499d562..23a84a02760 100644 --- a/mysql-test/suite/rpl/t/rpl_temporary_error2.test +++ b/mysql-test/suite/rpl/t/rpl_temporary_error2.test @@ -2,6 +2,9 @@ --source include/master-slave.inc call mtr.add_suppression("Deadlock found when trying to get lock; try restarting transaction"); +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log --echo *** Provoke a deadlock on the slave, check that transaction retry succeeds. *** --connection master diff --git a/mysql-test/suite/versioning/t/update.test b/mysql-test/suite/versioning/t/update.test index e7a57b393ad..e3b07bfe47a 100644 --- a/mysql-test/suite/versioning/t/update.test +++ b/mysql-test/suite/versioning/t/update.test @@ -1,6 +1,10 @@ source suite/versioning/engines.inc; source suite/versioning/common.inc; +--disable_query_log +call mtr.add_suppression("InnoDB: Transaction was aborted due to "); +--enable_query_log + replace_result $sys_datatype_expl SYS_DATATYPE; eval create table t1( x int unsigned, diff --git a/sql/handler.cc b/sql/handler.cc index 09f5131626b..286f65db1ae 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -4676,6 +4676,12 @@ void handler::print_error(int error, myf errflag) case HA_ERR_PARTITION_LIST: my_error(ER_VERS_NOT_ALLOWED, errflag, table->s->db.str, table->s->table_name.str); DBUG_VOID_RETURN; + case HA_ERR_ROLLBACK: + /* Crash if we run with --debug-assert-on-error */ + DBUG_ASSERT(!debug_assert_if_crashed_table); + SET_FATAL_ERROR; + textno= ER_ROLLBACK_ONLY; + break; default: { /* The error was "unknown" to this function. @@ -4710,7 +4716,7 @@ void handler::print_error(int error, myf errflag) /* Ensure this becomes a true error */ errflag&= ~(ME_WARNING | ME_NOTE); if ((debug_assert_if_crashed_table || - global_system_variables.log_warnings > 1)) + global_system_variables.log_warnings > 1)) { /* Log error to log before we crash or if extended warnings are requested diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index 4cc3cdf38c4..69e3032a2db 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -1541,7 +1541,7 @@ struct find_interesting_trx { void operator()(const trx_t &trx) { - if (trx.state == TRX_STATE_NOT_STARTED) + if (!trx.is_started()) return; if (trx.mysql_thd == nullptr) return; @@ -1550,12 +1550,12 @@ struct find_interesting_trx if (!found) { - ib::warn() << "The following trx might hold " + sql_print_warning("InnoDB: The following trx might hold " "the blocks in buffer pool to " "be withdrawn. Buffer pool " "resizing can complete only " "after all the transactions " - "below release the blocks."; + "below release the blocks."); found= true; } diff --git a/storage/innobase/fts/fts0fts.cc b/storage/innobase/fts/fts0fts.cc index 4ff034e0e3c..df2cb568d4d 100644 --- a/storage/innobase/fts/fts0fts.cc +++ b/storage/innobase/fts/fts0fts.cc @@ -2211,7 +2211,7 @@ fts_savepoint_t* fts_savepoint_create( /*=================*/ ib_vector_t* savepoints, /*!< out: InnoDB transaction */ - const char* name, /*!< in: savepoint name */ + const void* name, /*!< in: savepoint */ mem_heap_t* heap) /*!< in: heap */ { fts_savepoint_t* savepoint; @@ -2220,11 +2220,7 @@ fts_savepoint_create( ib_vector_push(savepoints, NULL)); memset(savepoint, 0x0, sizeof(*savepoint)); - - if (name) { - savepoint->name = mem_heap_strdup(heap, name); - } - + savepoint->name = name; static_assert(!offsetof(fts_trx_table_t, table), "ABI"); savepoint->tables = rbt_create(sizeof(fts_trx_table_t*), fts_ptr2_cmp); @@ -2243,7 +2239,6 @@ fts_trx_create( fts_trx_t* ftt; ib_alloc_t* heap_alloc; mem_heap_t* heap = mem_heap_create(1024); - trx_named_savept_t* savep; ut_a(trx->fts_trx == NULL); @@ -2263,14 +2258,6 @@ fts_trx_create( fts_savepoint_create(ftt->savepoints, NULL, NULL); fts_savepoint_create(ftt->last_stmt, NULL, NULL); - /* Copy savepoints that already set before. */ - for (savep = UT_LIST_GET_FIRST(trx->trx_savepoints); - savep != NULL; - savep = UT_LIST_GET_NEXT(trx_savepoints, savep)) { - - fts_savepoint_take(ftt, savep->name); - } - return(ftt); } @@ -5410,7 +5397,7 @@ void fts_savepoint_take( /*===============*/ fts_trx_t* fts_trx, /*!< in: fts transaction */ - const char* name) /*!< in: savepoint name */ + const void* name) /*!< in: savepoint */ { mem_heap_t* heap; fts_savepoint_t* savepoint; @@ -5433,31 +5420,21 @@ fts_savepoint_take( } /*********************************************************************//** -Lookup a savepoint instance by name. -@return ULINT_UNDEFINED if not found */ -UNIV_INLINE +Lookup a savepoint instance. +@return 0 if not found */ +static ulint fts_savepoint_lookup( /*==================*/ ib_vector_t* savepoints, /*!< in: savepoints */ - const char* name) /*!< in: savepoint name */ + const void* name) /*!< in: savepoint */ { - ulint i; - - ut_a(ib_vector_size(savepoints) > 0); - - for (i = 1; i < ib_vector_size(savepoints); ++i) { - fts_savepoint_t* savepoint; - - savepoint = static_cast( - ib_vector_get(savepoints, i)); - - if (strcmp(name, savepoint->name) == 0) { - return(i); - } - } - - return(ULINT_UNDEFINED); + ut_a(ib_vector_size(savepoints) > 0); + for (ulint i= 1; i < ib_vector_size(savepoints); ++i) + if (name == static_cast + (ib_vector_get(savepoints, i))->name) + return i; + return 0; } /*********************************************************************//** @@ -5468,7 +5445,7 @@ void fts_savepoint_release( /*==================*/ trx_t* trx, /*!< in: transaction */ - const char* name) /*!< in: savepoint name */ + const void* name) /*!< in: savepoint name */ { ut_a(name != NULL); @@ -5476,10 +5453,7 @@ fts_savepoint_release( ut_a(ib_vector_size(savepoints) > 0); - ulint i = fts_savepoint_lookup(savepoints, name); - if (i != ULINT_UNDEFINED) { - ut_a(i >= 1); - + if (ulint i = fts_savepoint_lookup(savepoints, name)) { fts_savepoint_t* savepoint; savepoint = static_cast( ib_vector_get(savepoints, i)); @@ -5634,9 +5608,8 @@ void fts_savepoint_rollback( /*===================*/ trx_t* trx, /*!< in: transaction */ - const char* name) /*!< in: savepoint name */ + const void* name) /*!< in: savepoint */ { - ulint i; ib_vector_t* savepoints; ut_a(name != NULL); @@ -5645,16 +5618,19 @@ fts_savepoint_rollback( /* We pop all savepoints from the the top of the stack up to and including the instance that was found. */ - i = fts_savepoint_lookup(savepoints, name); + ulint i = fts_savepoint_lookup(savepoints, name); - if (i != ULINT_UNDEFINED) { + if (i == 0) { + /* fts_trx_create() must have been invoked after + this savepoint had been created, and we must roll back + everything. */ + i = 1; + } + + { fts_savepoint_t* savepoint; - ut_a(i > 0); - while (ib_vector_size(savepoints) > i) { - fts_savepoint_t* savepoint; - savepoint = static_cast( ib_vector_pop(savepoints)); diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 4005b9345fa..ffed80b1341 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -1115,17 +1115,6 @@ static SHOW_VAR innodb_status_variables[]= { {NullS, NullS, SHOW_LONG} }; -/*****************************************************************//** -Frees a possible InnoDB trx object associated with the current THD. -@return 0 or error number */ -static -int -innobase_close_connection( -/*======================*/ - handlerton* hton, /*!< in/out: InnoDB handlerton */ - THD* thd); /*!< in: MySQL thread handle for - which to close the connection */ - /** Cancel any pending lock request associated with the current THD. @sa THD::awake() @sa ha_kill_query() */ static void innobase_kill_query(handlerton*, THD* thd, enum thd_kill_levels); @@ -1190,33 +1179,6 @@ innobase_rollback_to_savepoint_can_release_mdl( the user whose XA transaction should be rolled back to savepoint */ -/*****************************************************************//** -Sets a transaction savepoint. -@return always 0, that is, always succeeds */ -static -int -innobase_savepoint( -/*===============*/ - handlerton* hton, /*!< in/out: InnoDB handlerton */ - THD* thd, /*!< in: handle to the MySQL thread of - the user's XA transaction for which - we need to take a savepoint */ - void* savepoint); /*!< in: savepoint data */ - -/*****************************************************************//** -Release transaction savepoint name. -@return 0 if success, HA_ERR_NO_SAVEPOINT if no savepoint with the -given name */ -static -int -innobase_release_savepoint( -/*=======================*/ - handlerton* hton, /*!< in/out: handlerton for InnoDB */ - THD* thd, /*!< in: handle to the MySQL thread - of the user whose transaction's - savepoint should be released */ - void* savepoint); /*!< in: savepoint data */ - /** Request notification of log writes */ static void innodb_log_flush_request(void *cookie); @@ -2179,6 +2141,25 @@ static int innodb_ddl_recovery_done(handlerton*) return 0; } +/** Report an aborted transaction or statement. +@param thd execution context +@param all true=transaction, false=statement +@param err InnoDB error code */ +static void innodb_transaction_abort(THD *thd, bool all, dberr_t err) noexcept +{ + if (!thd) + return; + if (!all); + else if (trx_t *trx = thd_to_trx(thd)) + { + ut_ad(trx->state == TRX_STATE_NOT_STARTED); + trx->state= TRX_STATE_ABORTED; + sql_print_error("InnoDB: Transaction was aborted due to %s", + ut_strerr(err)); + } + thd_mark_transaction_to_rollback(thd, all); +} + /********************************************************************//** Converts an InnoDB error code to a MySQL error code and also tells to MySQL about a possible transaction rollback inside InnoDB caused by a lock wait @@ -2241,14 +2222,9 @@ convert_error_code_to_mysql( case DB_DEADLOCK: case DB_RECORD_CHANGED: - /* Since we rolled back the whole transaction, we must - tell it also to MySQL so that MySQL knows to empty the - cached binlog for this transaction */ - - if (thd != NULL) { - thd_mark_transaction_to_rollback(thd, 1); - } - + /* Since we rolled back the whole transaction, the + cached binlog must be emptied. */ + innodb_transaction_abort(thd, true, error); return error == DB_DEADLOCK ? HA_ERR_LOCK_DEADLOCK : HA_ERR_RECORD_CHANGED; @@ -2257,11 +2233,8 @@ convert_error_code_to_mysql( latest SQL statement in a lock wait timeout. Previously, we rolled back the whole transaction. */ - if (thd) { - thd_mark_transaction_to_rollback( - thd, innobase_rollback_on_timeout); - } - + innodb_transaction_abort(thd, innobase_rollback_on_timeout, + error); return(HA_ERR_LOCK_WAIT_TIMEOUT); case DB_NO_REFERENCED_ROW: @@ -2340,9 +2313,6 @@ convert_error_code_to_mysql( (ulong) DICT_MAX_FIELD_LEN_BY_FORMAT_FLAG(flags)); return(HA_ERR_INDEX_COL_TOO_LONG); - case DB_NO_SAVEPOINT: - return(HA_ERR_NO_SAVEPOINT); - case DB_LOCK_TABLE_FULL: /* Since we rolled back the whole transaction, we must tell it also to MySQL so that MySQL knows to empty the @@ -2928,7 +2898,7 @@ Gets the InnoDB transaction handle for a MySQL handler object, creates an InnoDB transaction struct if the corresponding MySQL thread struct still lacks one. @return InnoDB transaction handle */ -static inline +static trx_t* check_trx_exists( /*=============*/ @@ -2986,6 +2956,137 @@ trx_deregister_from_2pc( trx->active_commit_ordered= false; } +/** + Set a transaction savepoint. + + @param thd server thread descriptor + @param savepoint transaction savepoint storage area + + @retval 0 on success + @retval HA_ERR_NO_SAVEPOINT if the transaction is in an inconsistent state +*/ +static int innobase_savepoint(handlerton*, THD *thd, void *savepoint) noexcept +{ + DBUG_ENTER("innobase_savepoint"); + + /* In the autocommit mode there is no sense to set a savepoint + (unless we are in sub-statement), so SQL layer ensures that + this method is never called in such situation. */ + trx_t *trx= check_trx_exists(thd); + + /* Cannot happen outside of transaction */ + DBUG_ASSERT(trx_is_registered_for_2pc(trx)); + + switch (UNIV_EXPECT(trx->state, TRX_STATE_ACTIVE)) { + default: + ut_ad("invalid state" == 0); + DBUG_RETURN(HA_ERR_NO_SAVEPOINT); + case TRX_STATE_NOT_STARTED: + trx_start_if_not_started_xa(trx, false); + /* fall through */ + case TRX_STATE_ACTIVE: + const undo_no_t savept{trx->undo_no}; + *static_cast(savepoint)= savept; + trx->last_stmt_start= savept; + trx->end_bulk_insert(); + + if (trx->fts_trx) + fts_savepoint_take(trx->fts_trx, savepoint); + + DBUG_RETURN(0); + } +} + +/** + Releases a transaction savepoint. + + @param thd server thread descriptor + @param savepoint transaction savepoint to be released + + @return 0 always +*/ +static int innobase_release_savepoint(handlerton*, THD *thd, void *savepoint) + noexcept +{ + DBUG_ENTER("innobase_release_savepoint"); + trx_t *trx= check_trx_exists(thd); + ut_ad(trx->mysql_thd == thd); + if (trx->fts_trx) + fts_savepoint_release(trx, savepoint); + DBUG_RETURN(0); +} + +/** + Frees a possible InnoDB trx object associated with the current THD. + + @param thd server thread descriptor, which resources should be free'd + + @return 0 always +*/ +static int innobase_close_connection(handlerton *, THD *thd) noexcept +{ + if (auto trx= thd_to_trx(thd)) + { + thd_set_ha_data(thd, innodb_hton_ptr, nullptr); + switch (trx->state) { + case TRX_STATE_ABORTED: + trx->state= TRX_STATE_NOT_STARTED; + /* fall through */ + case TRX_STATE_NOT_STARTED: + ut_ad(!trx->id); + trx->will_lock= false; + break; + default: + ut_ad("invalid state" == 0); + return 0; + case TRX_STATE_PREPARED: + if (trx->has_logged_persistent()) + { + trx_disconnect_prepared(trx); + return 0; + } + /* fall through */ + case TRX_STATE_ACTIVE: + /* If we had reserved the auto-inc lock for some table (if + we come here to roll back the latest SQL statement) we + release it now before a possibly lengthy rollback */ + lock_unlock_table_autoinc(trx); + trx_rollback_for_mysql(trx); + } + trx_deregister_from_2pc(trx); + trx->free(); + DEBUG_SYNC(thd, "innobase_connection_closed"); + } + return 0; +} + +/** + XA ROLLBACK after XA PREPARE + + @param xid X/Open XA transaction identification + + @retval 0 if the transaction was found and rolled back + @retval XAER_NOTA if no such transaction exists + @retval XAER_RMFAIL if InnoDB is in read-only mode +*/ +static int innobase_rollback_by_xid(handlerton*, XID *xid) noexcept +{ + DBUG_EXECUTE_IF("innobase_xa_fail", return XAER_RMFAIL;); + if (high_level_read_only) + return XAER_RMFAIL; + if (trx_t *trx= trx_get_trx_by_xid(xid)) + { + ut_ad(trx->xid.is_null()); /* should have been cleared by the lookup */ + trx_deregister_from_2pc(trx); + THD* thd= trx->mysql_thd; + dberr_t err= trx_rollback_for_mysql(trx); + ut_ad(!trx->will_lock); + trx->free(); + return convert_error_code_to_mysql(err, 0, thd); + } + return XAER_NOTA; +} + /*********************************************************************//** Copy table flags from MySQL's HA_CREATE_INFO into an InnoDB table object. Those flags are stored in .frm file and end up in the MySQL table object, @@ -4190,7 +4291,7 @@ static int innodb_init(void* p) innodb_hton_ptr = innobase_hton; innobase_hton->db_type = DB_TYPE_INNODB; - innobase_hton->savepoint_offset = sizeof(trx_named_savept_t); + innobase_hton->savepoint_offset = sizeof(undo_no_t); innobase_hton->close_connection = innobase_close_connection; innobase_hton->kill_query = innobase_kill_query; innobase_hton->savepoint_set = innobase_savepoint; @@ -4409,12 +4510,7 @@ innobase_commit_low( tmp = thd_proc_info(trx->mysql_thd, "innobase_commit_low()"); } #endif /* WITH_WSREP */ - if (trx_is_started(trx)) { - trx_commit_for_mysql(trx); - } else { - trx->will_lock = false; - } - + trx_commit_for_mysql(trx); #ifdef WITH_WSREP if (is_wsrep) { thd_proc_info(trx->mysql_thd, tmp); @@ -4445,7 +4541,7 @@ innobase_start_trx_and_assign_read_view( /* The transaction should not be active yet, start it */ - ut_ad(!trx_is_started(trx)); + ut_ad(!trx->is_started()); trx_start_if_not_started_xa(trx, false); @@ -4468,7 +4564,7 @@ innobase_start_trx_and_assign_read_view( /* Set the MySQL flag to mark that there is an active transaction */ - innobase_register_trx(hton, current_thd, trx); + innobase_register_trx(hton, thd, trx); DBUG_RETURN(0); } @@ -4546,7 +4642,7 @@ innobase_commit_ordered( trx = check_trx_exists(thd); - if (!trx_is_registered_for_2pc(trx) && trx_is_started(trx)) { + if (!trx_is_registered_for_2pc(trx) && trx->is_started()) { /* We cannot throw error here; instead we will catch this error again in innobase_commit() and report it from there. */ DBUG_VOID_RETURN; @@ -4563,6 +4659,23 @@ innobase_commit_ordered( DBUG_VOID_RETURN; } +/** Marks the latest SQL statement ended. */ +static void trx_mark_stmt_end(trx_t *trx) noexcept +{ + ut_d(const trx_state_t trx_state{trx->state}); + ut_ad(trx_state == TRX_STATE_ACTIVE || trx_state == TRX_STATE_NOT_STARTED); + if (trx->fts_trx) + fts_savepoint_laststmt_refresh(trx); + if (trx->is_bulk_insert()) + /* Allow a subsequent INSERT into an empty table + if !unique_checks && !foreign_key_checks. */; + else + { + trx->last_stmt_start= trx->undo_no; + trx->end_bulk_insert(); + } +} + /*****************************************************************//** Commits a transaction in an InnoDB database or marks an SQL statement ended. @@ -4590,20 +4703,36 @@ innobase_commit( ut_ad(!trx->dict_operation_lock_mode); ut_ad(!trx->dict_operation); - /* Transaction is deregistered only in a commit or a rollback. If - it is deregistered we know there cannot be resources to be freed - and we could return immediately. For the time being, we play safe - and do the cleanup though there should be nothing to clean up. */ - - if (!trx_is_registered_for_2pc(trx) && trx_is_started(trx)) { - - sql_print_error("Transaction not registered for MariaDB 2PC," - " but transaction is active"); + switch (UNIV_EXPECT(trx->state, TRX_STATE_ACTIVE)) { + case TRX_STATE_ABORTED: + trx->state = TRX_STATE_NOT_STARTED; + /* fall through */ + case TRX_STATE_NOT_STARTED: + break; + default: + case TRX_STATE_COMMITTED_IN_MEMORY: + case TRX_STATE_PREPARED_RECOVERED: + ut_ad("invalid state" == 0); + /* fall through */ + case TRX_STATE_PREPARED: + ut_ad(commit_trx); + ut_ad(thd_test_options(thd, OPTION_NOT_AUTOCOMMIT + | OPTION_BEGIN)); + /* fall through */ + case TRX_STATE_ACTIVE: + /* Transaction is deregistered only in a commit or a + rollback. If it is deregistered we know there cannot + be resources to be freed and we could return + immediately. For the time being, we play safe and do + the cleanup though there should be nothing to clean + up. */ + if (!trx_is_registered_for_2pc(trx)) { + sql_print_error("Transaction not registered" + " for MariaDB 2PC," + " but transaction is active"); + } } - bool read_only = trx->read_only || trx->id == 0; - DBUG_PRINT("info", ("readonly: %d", read_only)); - if (commit_trx || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) { @@ -4631,19 +4760,8 @@ innobase_commit( } else { /* We just mark the SQL statement ended and do not do a transaction commit */ - - /* If we had reserved the auto-inc lock for some - table in this SQL statement we release it now */ - - if (!read_only) { - lock_unlock_table_autoinc(trx); - } - - /* Store the current undo_no of the transaction so that we - know where to roll back if we have to roll back the next - SQL statement */ - - trx_mark_sql_stat_end(trx); + lock_unlock_table_autoinc(trx); + trx_mark_stmt_end(trx); } /* Reset the number AUTO-INC rows required */ @@ -4662,7 +4780,7 @@ static int innobase_rollback( /*==============*/ - handlerton* hton, /*!< in: InnoDB handlerton */ + handlerton*, THD* thd, /*!< in: handle to the MySQL thread of the user whose transaction should be rolled back */ @@ -4670,74 +4788,79 @@ innobase_rollback( transaction FALSE - rollback the current statement only */ { - DBUG_ENTER("innobase_rollback"); - DBUG_ASSERT(hton == innodb_hton_ptr); - DBUG_PRINT("trans", ("aborting transaction")); + DBUG_ENTER("innobase_rollback"); + DBUG_PRINT("trans", ("aborting transaction")); - trx_t* trx = check_trx_exists(thd); + if (!rollback_trx) + rollback_trx= !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN); - ut_ad(!trx->dict_operation_lock_mode); - ut_ad(!trx->dict_operation); + trx_t *trx= check_trx_exists(thd); - /* Reset the number AUTO-INC rows required */ + ut_ad(trx->mysql_thd == thd); + ut_ad(!trx->is_recovered); + ut_ad(!trx->dict_operation_lock_mode); + ut_ad(!trx->dict_operation); - trx->n_autoinc_rows = 0; + /* Reset the number AUTO-INC rows required */ + trx->n_autoinc_rows= 0; + /* This is a statement level variable. */ + trx->fts_next_doc_id= 0; - /* If we had reserved the auto-inc lock for some table (if - we come here to roll back the latest SQL statement) we - release it now before a possibly lengthy rollback */ - lock_unlock_table_autoinc(trx); - - /* This is a statement level variable. */ - - trx->fts_next_doc_id = 0; - - dberr_t error; + const trx_state_t trx_state{trx->state}; + switch (UNIV_EXPECT(trx_state, TRX_STATE_ACTIVE)) { + case TRX_STATE_ABORTED: + if (rollback_trx) + trx->state= TRX_STATE_NOT_STARTED; + /* fall through */ + case TRX_STATE_NOT_STARTED: + ut_ad(!trx->id); + trx->will_lock= false; + if (rollback_trx) + trx_deregister_from_2pc(trx); + DBUG_RETURN(0); + default: + case TRX_STATE_COMMITTED_IN_MEMORY: + case TRX_STATE_PREPARED_RECOVERED: + ut_ad("invalid state" == 0); + /* fall through */ + case TRX_STATE_PREPARED: + ut_ad(rollback_trx); + /* fall through */ + case TRX_STATE_ACTIVE: + /* If we had reserved the auto-inc lock for some table (if + we come here to roll back the latest SQL statement) we + release it now before a possibly lengthy rollback */ + lock_unlock_table_autoinc(trx); #ifdef WITH_WSREP - /* If trx was assigned wsrep XID in prepare phase and the - trx is being rolled back due to BF abort, clear XID in order - to avoid writing it to rollback segment out of order. The XID - will be reassigned when the transaction is replayed. */ - if (trx->state != TRX_STATE_NOT_STARTED - && wsrep_is_wsrep_xid(&trx->xid)) { - trx->xid.null(); - } + /* If trx was assigned wsrep XID in prepare phase and the + trx is being rolled back due to BF abort, clear XID in order + to avoid writing it to rollback segment out of order. The XID + will be reassigned when the transaction is replayed. */ + if (rollback_trx || wsrep_is_wsrep_xid(&trx->xid)) + trx->xid.null(); #endif /* WITH_WSREP */ - if (rollback_trx - || !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { - - error = trx_rollback_for_mysql(trx); - - trx_deregister_from_2pc(trx); - } else { - - error = trx_rollback_last_sql_stat_for_mysql(trx); - } - - DBUG_RETURN(convert_error_code_to_mysql(error, 0, trx->mysql_thd)); -} - -/*****************************************************************//** -Rolls back a transaction -@return 0 or error number */ -static -int -innobase_rollback_trx( -/*==================*/ - trx_t* trx) /*!< in: transaction */ -{ - DBUG_ENTER("innobase_rollback_trx"); - DBUG_PRINT("trans", ("aborting transaction")); - - /* If we had reserved the auto-inc lock for some table (if - we come here to roll back the latest SQL statement) we - release it now before a possibly lengthy rollback */ - lock_unlock_table_autoinc(trx); - trx_deregister_from_2pc(trx); - - DBUG_RETURN(convert_error_code_to_mysql(trx_rollback_for_mysql(trx), - 0, trx->mysql_thd)); + dberr_t error; + if (rollback_trx) + { + error= trx_rollback_for_mysql(trx); + trx_deregister_from_2pc(trx); + } + else + { + ut_a(trx_state == TRX_STATE_ACTIVE); + ut_ad(!trx->is_autocommit_non_locking() || trx->read_only); + error= trx->rollback(&trx->last_stmt_start); + if (trx->fts_trx) + { + fts_savepoint_rollback_last_stmt(trx); + fts_savepoint_laststmt_refresh(trx); + } + trx->last_stmt_start= trx->undo_no; + trx->end_bulk_insert(); + } + DBUG_RETURN(convert_error_code_to_mysql(error, 0, trx->mysql_thd)); + } } /** Invoke commit_checkpoint_notify_ha() on completed log flush requests. @@ -4874,34 +4997,43 @@ static int innobase_rollback_to_savepoint( /*===========================*/ - handlerton* hton, /*!< in: InnoDB handlerton */ + handlerton*, THD* thd, /*!< in: handle to the MySQL thread of the user whose transaction should be rolled back to savepoint */ void* savepoint) /*!< in: savepoint data */ { + DBUG_ENTER("innobase_rollback_to_savepoint"); + trx_t *trx= check_trx_exists(thd); - DBUG_ENTER("innobase_rollback_to_savepoint"); - DBUG_ASSERT(hton == innodb_hton_ptr); + /* We are reading trx->state without holding trx->mutex here, + because the savepoint rollback should be invoked for a running + active transaction that is associated with the current thread. */ + ut_ad(trx->mysql_thd); - trx_t* trx = check_trx_exists(thd); + if (UNIV_UNLIKELY(trx->state != TRX_STATE_ACTIVE)) + { + ut_ad("invalid state" == 0); + DBUG_RETURN(HA_ERR_NO_SAVEPOINT); + } - /* TODO: use provided savepoint data area to store savepoint data */ + const undo_no_t *savept= static_cast(savepoint); - char name[64]; + if (UNIV_UNLIKELY(*savept > trx->undo_no)) + /* row_mysql_handle_errors() should have invoked rollback during + a bulk insert into an empty table. */ + DBUG_RETURN(HA_ERR_NO_SAVEPOINT); - longlong2str(longlong(savepoint), name, 36); - - int64_t mysql_binlog_cache_pos; - - dberr_t error = trx_rollback_to_savepoint_for_mysql( - trx, name, &mysql_binlog_cache_pos); - - if (error == DB_SUCCESS && trx->fts_trx != NULL) { - fts_savepoint_rollback(trx, name); - } - - DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL)); + dberr_t error= trx->rollback(savept); + /* Store the position for rolling back the next SQL statement */ + if (trx->fts_trx) + { + fts_savepoint_laststmt_refresh(trx); + fts_savepoint_rollback(trx, savept); + } + trx->last_stmt_start= trx->undo_no; + trx->end_bulk_insert(); + DBUG_RETURN(convert_error_code_to_mysql(error, 0, nullptr)); } /*****************************************************************//** @@ -4934,106 +5066,6 @@ innobase_rollback_to_savepoint_can_release_mdl( DBUG_RETURN(false); } -/*****************************************************************//** -Release transaction savepoint name. -@return 0 if success, HA_ERR_NO_SAVEPOINT if no savepoint with the -given name */ -static -int -innobase_release_savepoint( -/*=======================*/ - handlerton* hton, /*!< in: handlerton for InnoDB */ - THD* thd, /*!< in: handle to the MySQL thread - of the user whose transaction's - savepoint should be released */ - void* savepoint) /*!< in: savepoint data */ -{ - dberr_t error; - trx_t* trx; - char name[64]; - - DBUG_ENTER("innobase_release_savepoint"); - DBUG_ASSERT(hton == innodb_hton_ptr); - - trx = check_trx_exists(thd); - - /* TODO: use provided savepoint data area to store savepoint data */ - - longlong2str(longlong(savepoint), name, 36); - - error = trx_release_savepoint_for_mysql(trx, name); - - if (error == DB_SUCCESS && trx->fts_trx != NULL) { - fts_savepoint_release(trx, name); - } - - DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL)); -} - -/*****************************************************************//** -Sets a transaction savepoint. -@return always 0, that is, always succeeds */ -static -int -innobase_savepoint( -/*===============*/ - handlerton* hton, /*!< in: handle to the InnoDB handlerton */ - THD* thd, /*!< in: handle to the MySQL thread */ - void* savepoint)/*!< in: savepoint data */ -{ - DBUG_ENTER("innobase_savepoint"); - DBUG_ASSERT(hton == innodb_hton_ptr); - - /* In the autocommit mode there is no sense to set a savepoint - (unless we are in sub-statement), so SQL layer ensures that - this method is never called in such situation. */ - - trx_t* trx = check_trx_exists(thd); - - /* Cannot happen outside of transaction */ - DBUG_ASSERT(trx_is_registered_for_2pc(trx)); - - /* TODO: use provided savepoint data area to store savepoint data */ - char name[64]; - - longlong2str(longlong(savepoint), name, 36); - - dberr_t error = trx_savepoint_for_mysql(trx, name, 0); - - if (error == DB_SUCCESS && trx->fts_trx != NULL) { - fts_savepoint_take(trx->fts_trx, name); - } - - DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL)); -} - - -/** - Frees a possible InnoDB trx object associated with the current THD. - - @param hton innobase handlerton - @param thd server thread descriptor, which resources should be free'd - - @return 0 always -*/ -static int innobase_close_connection(handlerton *hton, THD *thd) -{ - DBUG_ASSERT(hton == innodb_hton_ptr); - if (auto trx= thd_to_trx(thd)) - { - thd_set_ha_data(thd, innodb_hton_ptr, NULL); - if (trx->state == TRX_STATE_PREPARED && trx->has_logged_persistent()) - { - trx_disconnect_prepared(trx); - return 0; - } - innobase_rollback_trx(trx); - trx->free(); - DEBUG_SYNC(thd, "innobase_connection_closed"); - } - return 0; -} - /** Cancel any pending lock request associated with the current THD. @sa THD::awake() @sa ha_kill_query() */ static void innobase_kill_query(handlerton*, THD *thd, enum thd_kill_levels) @@ -7794,26 +7826,35 @@ ha_innobase::innobase_set_max_autoinc( return(error); } -/** @return whether the table is read-only */ -bool ha_innobase::is_read_only(bool altering_to_supported) const +int ha_innobase::is_valid_trx(bool altering_to_supported) const noexcept { ut_ad(m_prebuilt->trx == thd_to_trx(m_user_thd)); if (high_level_read_only) { ib_senderrf(m_user_thd, IB_LOG_LEVEL_WARN, ER_READ_ONLY_MODE); - return true; + return HA_ERR_TABLE_READONLY; } - if (altering_to_supported) - return false; + switch (UNIV_EXPECT(m_prebuilt->trx->state, TRX_STATE_ACTIVE)) { + default: + ut_ad("invalid state" == 0); + /* fall through */ + case TRX_STATE_ABORTED: + break; + case TRX_STATE_NOT_STARTED: + m_prebuilt->trx->will_lock= true; + /* fall through */ + case TRX_STATE_ACTIVE: + if (altering_to_supported || + !DICT_TF_GET_ZIP_SSIZE(m_prebuilt->table->flags) || + !innodb_read_only_compressed) + return 0; - if (!DICT_TF_GET_ZIP_SSIZE(m_prebuilt->table->flags) || - !innodb_read_only_compressed) - return false; - - ib_senderrf(m_user_thd, IB_LOG_LEVEL_WARN, ER_UNSUPPORTED_COMPRESSED_TABLE); - return true; + ib_senderrf(m_user_thd, IB_LOG_LEVEL_WARN, ER_UNSUPPORTED_COMPRESSED_TABLE); + return HA_ERR_TABLE_READONLY; + } + return HA_ERR_ROLLBACK; } /********************************************************************//** @@ -7839,12 +7880,8 @@ ha_innobase::write_row( trx_t* trx = thd_to_trx(m_user_thd); /* Validation checks before we commence write_row operation. */ - if (is_read_only()) { - DBUG_RETURN(HA_ERR_TABLE_READONLY); - } - - if (!trx_is_started(trx)) { - trx->will_lock = true; + if (int err = is_valid_trx()) { + DBUG_RETURN(err); } ins_mode_t vers_set_fields; @@ -8613,10 +8650,8 @@ ha_innobase::update_row( DBUG_ENTER("ha_innobase::update_row"); - if (is_read_only()) { - DBUG_RETURN(HA_ERR_TABLE_READONLY); - } else if (!trx_is_started(trx)) { - trx->will_lock = true; + if (int err = is_valid_trx()) { + DBUG_RETURN(err); } if (m_upd_buf == NULL) { @@ -8734,7 +8769,8 @@ func_exit: if (error == DB_SUCCESS && /* For sequences, InnoDB transaction may not have been started yet. Check THD-level wsrep state in that case. */ - (trx->is_wsrep() || (!trx_is_started(trx) && wsrep_on(m_user_thd))) + (trx->is_wsrep() + || (trx->state == TRX_STATE_NOT_STARTED && wsrep_on(m_user_thd))) && wsrep_thd_is_local(m_user_thd) && !wsrep_thd_ignore_table(m_user_thd) && (thd_sql_command(m_user_thd) != SQLCOM_CREATE_TABLE) @@ -8778,12 +8814,9 @@ ha_innobase::delete_row( DBUG_ENTER("ha_innobase::delete_row"); - if (is_read_only()) { - DBUG_RETURN(HA_ERR_TABLE_READONLY); - } else if (!trx_is_started(trx)) { - trx->will_lock = true; + if (int err = is_valid_trx()) { + DBUG_RETURN(err); } - if (!m_prebuilt->upd_node) { row_get_prebuilt_update_vector(m_prebuilt); } @@ -9060,10 +9093,15 @@ ha_innobase::index_read( DBUG_RETURN(HA_ERR_KEY_NOT_FOUND); } + const trx_state_t trx_state{m_prebuilt->trx->state}; + if (trx_state == TRX_STATE_ABORTED) { + DBUG_RETURN(HA_ERR_ROLLBACK); + } + /* For R-Tree index, we will always place the page lock to pages being searched */ if (index->is_spatial() && !m_prebuilt->trx->will_lock) { - if (trx_is_started(m_prebuilt->trx)) { + if (trx_state != TRX_STATE_NOT_STARTED) { DBUG_RETURN(HA_ERR_READ_ONLY_TRANSACTION); } else { m_prebuilt->trx->will_lock = true; @@ -9344,6 +9382,17 @@ ha_innobase::general_fetch( ut_ad(trx == thd_to_trx(m_user_thd)); + switch (UNIV_EXPECT(trx->state, TRX_STATE_ACTIVE)) { + default: + ut_ad("invalid state" == 0); + /* fall through */ + case TRX_STATE_ABORTED: + DBUG_RETURN(HA_ERR_ROLLBACK); + case TRX_STATE_ACTIVE: + case TRX_STATE_NOT_STARTED: + break; + } + if (m_prebuilt->table->is_readable()) { } else if (m_prebuilt->table->corrupted) { DBUG_RETURN(HA_ERR_CRASHED); @@ -9596,9 +9645,7 @@ ha_innobase::rnd_pos( Initialize FT index scan @return 0 or error number */ -int -ha_innobase::ft_init() -/*==================*/ +int ha_innobase::ft_init() { DBUG_ENTER("ft_init"); @@ -9608,9 +9655,14 @@ ha_innobase::ft_init() This is because the FTS implementation can acquire locks behind the scenes. This has not been verified but it is safer to treat them as regular read only transactions for now. */ - - if (!trx_is_started(trx)) { + switch (trx->state) { + default: + DBUG_RETURN(HA_ERR_ROLLBACK); + case TRX_STATE_ACTIVE: + break; + case TRX_STATE_NOT_STARTED: trx->will_lock = true; + break; } DBUG_RETURN(rnd_init(false)); @@ -9644,11 +9696,9 @@ ha_innobase::ft_init_ext( out.write(key->ptr(), key->length()); } - if (flags & FT_BOOL) { - ib::info() << "BOOL search"; - } else { - ib::info() << "NL search"; - } + sql_print_information((flags & FT_BOOL) + ? "InnoDB: BOOL search" + : "InnoDB: NL search"); } /* Multi byte character sets like utf32 and utf16 are not @@ -9673,8 +9723,17 @@ ha_innobase::ft_init_ext( the scenes. This has not been verified but it is safer to treat them as regular read only transactions for now. */ - if (!trx_is_started(trx)) { + switch (trx->state) { + default: + ut_ad("invalid state" == 0); + my_printf_error(HA_ERR_ROLLBACK, "Invalid tansaction state", + ME_ERROR_LOG); + return nullptr; + case TRX_STATE_ACTIVE: + break; + case TRX_STATE_NOT_STARTED: trx->will_lock = true; + break; } dict_table_t* ft_table = m_prebuilt->table; @@ -13354,12 +13413,8 @@ ha_innobase::discard_or_import_tablespace( DBUG_ENTER("ha_innobase::discard_or_import_tablespace"); - ut_a(m_prebuilt->trx != NULL); - ut_a(m_prebuilt->trx->magic_n == TRX_MAGIC_N); - ut_a(m_prebuilt->trx == thd_to_trx(ha_thd())); - - if (is_read_only()) { - DBUG_RETURN(HA_ERR_TABLE_READONLY); + if (int err = is_valid_trx()) { + DBUG_RETURN(err); } if (m_prebuilt->table->is_temporary()) { @@ -13907,8 +13962,8 @@ int ha_innobase::truncate() } #endif - if (is_read_only()) - DBUG_RETURN(HA_ERR_TABLE_READONLY); + if (int err= is_valid_trx()) + DBUG_RETURN(err); HA_CREATE_INFO info; dict_table_t *ib_table= m_prebuilt->table; @@ -15940,6 +15995,16 @@ ha_innobase::start_stmt( trx = m_prebuilt->trx; + switch (trx->state) { + default: + DBUG_RETURN(HA_ERR_ROLLBACK); + case TRX_STATE_ACTIVE: + break; + case TRX_STATE_NOT_STARTED: + trx->will_lock = true; + break; + } + /* Reset the AUTOINC statement level counter for multi-row INSERTs. */ trx->n_autoinc_rows = 0; @@ -15963,7 +16028,7 @@ ha_innobase::start_stmt( break; } trx->bulk_insert = false; - trx->last_sql_stat_start.least_undo_no = trx->undo_no; + trx->last_stmt_start = trx->undo_no; } m_prebuilt->sql_stat_start = TRUE; @@ -16021,10 +16086,6 @@ ha_innobase::start_stmt( innobase_register_trx(ht, thd, trx); - if (!trx_is_started(trx)) { - trx->will_lock = true; - } - DBUG_RETURN(0); } @@ -16076,6 +16137,28 @@ ha_innobase::external_lock( trx_t* trx = m_prebuilt->trx; ut_ad(m_prebuilt->table); + const bool not_autocommit = thd_test_options(thd, OPTION_NOT_AUTOCOMMIT + | OPTION_BEGIN); + bool not_started = false; + switch (trx->state) { + default: + case TRX_STATE_PREPARED: + ut_ad("invalid state" == 0); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); + case TRX_STATE_ABORTED: + if (lock_type != F_UNLCK && not_autocommit) { + DBUG_RETURN(HA_ERR_WRONG_COMMAND); + } + /* Reset the state if the transaction had been aborted. */ + trx->state = TRX_STATE_NOT_STARTED; + /* fall through */ + case TRX_STATE_NOT_STARTED: + not_started = true; + break; + case TRX_STATE_ACTIVE: + break; + } + /* Statement based binlogging does not work in isolation level READ UNCOMMITTED and READ COMMITTED since the necessary locks cannot be taken. In this case, we print an @@ -16151,7 +16234,7 @@ ha_innobase::external_lock( break; } trx->bulk_insert = false; - trx->last_sql_stat_start.least_undo_no = trx->undo_no; + trx->last_stmt_start = trx->undo_no; } switch (m_prebuilt->table->quiesce) { @@ -16197,25 +16280,47 @@ ha_innobase::external_lock( break; } - if (lock_type == F_WRLCK) { + switch (lock_type) { + case F_UNLCK: + DEBUG_SYNC_C("ha_innobase_end_statement"); + m_mysql_has_locked = false; + if (--trx->n_mysql_tables_in_use) { + break; + } + + /* If the lock count drops to zero we know that the + current SQL statement has ended */ + trx->mysql_n_tables_locked = 0; + m_prebuilt->used_in_HANDLER = FALSE; + + if (!not_autocommit) { + if (!not_started) { + innobase_commit(ht, thd, TRUE); + } + } else if (trx->isolation_level <= TRX_ISO_READ_COMMITTED) { + trx->read_view.close(); + } + break; + case F_WRLCK: /* If this is a SELECT, then it is in UPDATE TABLE ... or SELECT ... FOR UPDATE */ m_prebuilt->select_lock_type = LOCK_X; m_prebuilt->stored_select_lock_type = LOCK_X; - } - - if (lock_type != F_UNLCK) { - /* MySQL is setting a new table lock */ - + goto set_lock; + case F_RDLCK: + /* Ensure that trx->lock.trx_locks is empty for read-only + autocommit transactions */ + ut_ad(not_autocommit || trx->n_mysql_tables_in_use + || UT_LIST_GET_LEN(trx->lock.trx_locks) == 0); +set_lock: *trx->detailed_error = 0; innobase_register_trx(ht, thd, trx); - if (trx->isolation_level == TRX_ISO_SERIALIZABLE - && m_prebuilt->select_lock_type == LOCK_NONE - && thd_test_options( - thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { + if (not_autocommit + && trx->isolation_level == TRX_ISO_SERIALIZABLE + && m_prebuilt->select_lock_type == LOCK_NONE) { /* To get serializable execution, we let InnoDB conceptually add 'LOCK IN SHARE MODE' to all SELECTs @@ -16263,42 +16368,12 @@ ha_innobase::external_lock( trx->n_mysql_tables_in_use++; m_mysql_has_locked = true; - if (!trx_is_started(trx) + if (not_started && (m_prebuilt->select_lock_type != LOCK_NONE || m_prebuilt->stored_select_lock_type != LOCK_NONE)) { trx->will_lock = true; } - - DBUG_RETURN(0); - } - - DEBUG_SYNC_C("ha_innobase_end_statement"); - - /* MySQL is releasing a table lock */ - - trx->n_mysql_tables_in_use--; - m_mysql_has_locked = false; - - /* If the MySQL lock count drops to zero we know that the current SQL - statement has ended */ - - if (trx->n_mysql_tables_in_use == 0) { - - trx->mysql_n_tables_locked = 0; - m_prebuilt->used_in_HANDLER = FALSE; - - if (!thd_test_options( - thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) { - - if (trx_is_started(trx)) { - - innobase_commit(ht, thd, TRUE); - } - - } else if (trx->isolation_level <= TRX_ISO_READ_COMMITTED) { - trx->read_view.close(); - } } DBUG_RETURN(0); @@ -16639,14 +16714,6 @@ ha_innobase::store_lock( } m_prebuilt->skip_locked= (lock_type == TL_WRITE_SKIP_LOCKED || lock_type == TL_READ_SKIP_LOCKED); - - if (!trx_is_started(trx) - && (m_prebuilt->select_lock_type != LOCK_NONE - || m_prebuilt->stored_select_lock_type != LOCK_NONE)) { - - trx->will_lock = true; - } - return(to); } @@ -17120,7 +17187,7 @@ static int innobase_xa_prepare( /*================*/ - handlerton* hton, /*!< in: InnoDB handlerton */ + handlerton*, THD* thd, /*!< in: handle to the MySQL thread of the user whose XA transaction should be prepared */ @@ -17128,62 +17195,30 @@ innobase_xa_prepare( false - the current SQL statement ended */ { - trx_t* trx = check_trx_exists(thd); + trx_t *trx= check_trx_exists(thd); + ut_ad(trx_is_registered_for_2pc(trx)); + if (!prepare_trx) + prepare_trx= !thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN); - DBUG_ASSERT(hton == innodb_hton_ptr); - - thd_get_xid(thd, &reinterpret_cast(trx->xid)); - - if (!trx_is_registered_for_2pc(trx) && trx_is_started(trx)) { - - sql_print_error("Transaction not registered for MariaDB 2PC," - " but transaction is active"); - } - - if (prepare_trx - || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) { - - /* We were instructed to prepare the whole transaction, or - this is an SQL statement end and autocommit is on */ - - ut_ad(trx_is_registered_for_2pc(trx)); - - trx_prepare_for_mysql(trx); - } else { - /* We just mark the SQL statement ended and do not do a - transaction prepare */ - - /* If we had reserved the auto-inc lock for some - table in this SQL statement we release it now */ - - lock_unlock_table_autoinc(trx); - - /* Store the current undo_no of the transaction so that we - know where to roll back if we have to roll back the next - SQL statement */ - - trx_mark_sql_stat_end(trx); - } - - if (thd_sql_command(thd) != SQLCOM_XA_PREPARE - && (prepare_trx - || !thd_test_options( - thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) { - - /* For mysqlbackup to work the order of transactions in binlog - and InnoDB must be the same. Consider the situation - - thread1> prepare; write to binlog; ... - - thread2> prepare; write to binlog; commit - thread1> ... commit - - The server guarantees that writes to the binary log - and commits are in the same order, so we do not have - to handle this case. */ - } - - return(0); + switch (UNIV_EXPECT(trx->state, TRX_STATE_ACTIVE)) { + default: + ut_ad("invalid state" == 0); + return HA_ERR_GENERIC; + case TRX_STATE_NOT_STARTED: + if (prepare_trx) + trx_start_if_not_started_xa(trx, false);; + /* fall through */ + case TRX_STATE_ACTIVE: + thd_get_xid(thd, &reinterpret_cast(trx->xid)); + if (prepare_trx) + trx_prepare_for_mysql(trx); + else + { + lock_unlock_table_autoinc(trx); + trx_mark_stmt_end(trx); + } + return 0; + } } /*******************************************************************//** @@ -17193,12 +17228,10 @@ static int innobase_xa_recover( /*================*/ - handlerton* hton, /*!< in: InnoDB handlerton */ + handlerton*, XID* xid_list,/*!< in/out: prepared transactions */ uint len) /*!< in: number of slots in xid_list */ { - DBUG_ASSERT(hton == innodb_hton_ptr); - if (len == 0 || xid_list == NULL) { return(0); @@ -17241,43 +17274,6 @@ innobase_commit_by_xid( } } -/** This function is used to rollback one X/Open XA distributed transaction -which is in the prepared state - -@param[in] hton InnoDB handlerton -@param[in] xid X/Open XA transaction identification - -@return 0 or error number */ -int innobase_rollback_by_xid(handlerton* hton, XID* xid) -{ - DBUG_ASSERT(hton == innodb_hton_ptr); - - DBUG_EXECUTE_IF("innobase_xa_fail", - return XAER_RMFAIL;); - - if (high_level_read_only) { - return(XAER_RMFAIL); - } - - if (trx_t* trx = trx_get_trx_by_xid(xid)) { -#ifdef WITH_WSREP - /* If a wsrep transaction is being rolled back during - the recovery, we must clear the xid in order to avoid - writing serialisation history for rolled back transaction. */ - if (wsrep_is_wsrep_xid(&trx->xid)) { - trx->xid.null(); - } -#endif /* WITH_WSREP */ - int ret = innobase_rollback_trx(trx); - ut_ad(!trx->will_lock); - trx->free(); - - return(ret); - } else { - return(XAER_NOTA); - } -} - bool ha_innobase::check_if_incompatible_data( /*====================================*/ diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index 671acc3b3ca..534936f8fae 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -465,8 +465,13 @@ protected: @see build_template() */ void reset_template(); - /** @return whether the table is read-only */ - bool is_read_only(bool altering_to_supported= false) const; + /** Check the transaction is valid. + @param altering_to_supported whether an ALTER TABLE is being run + to something else than ROW_FORMAT=COMPRESSED + @retval 0 if the transaction is valid for the current operation + @retval HA_ERR_TABLE_READONLY if the table is read-only + @retval HA_ERR_ROLLBACK if the transaction has been aborted */ + int is_valid_trx(bool altering_to_supported= false) const noexcept; inline void update_thd(THD* thd); void update_thd(); @@ -937,12 +942,3 @@ ib_push_frm_error( @return true if index column length exceeds limit */ MY_ATTRIBUTE((warn_unused_result)) bool too_big_key_part_length(size_t max_field_len, const KEY& key); - -/** This function is used to rollback one X/Open XA distributed transaction -which is in the prepared state - -@param[in] hton InnoDB handlerton -@param[in] xid X/Open XA transaction identification - -@return 0 or error number */ -int innobase_rollback_by_xid(handlerton* hton, XID* xid); diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index c164956d9a0..ff427297375 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -2249,7 +2249,7 @@ ha_innobase::check_if_supported_inplace_alter( table->s->table_name.str); } - if (is_read_only(!high_level_read_only + if (is_valid_trx(!high_level_read_only && (ha_alter_info->handler_flags & ALTER_OPTIONS) && ha_alter_info->create_info->key_block_size == 0 && ha_alter_info->create_info->row_type @@ -9154,7 +9154,7 @@ inline bool rollback_inplace_alter_table(Alter_inplace_info *ha_alter_info, /* If we have not started a transaction yet, (almost) nothing has been or needs to be done. */ dict_sys.lock(SRW_LOCK_CALL); - else if (ctx->trx->state == TRX_STATE_NOT_STARTED) + else if (!ctx->trx->is_started()) goto free_and_exit; else if (ctx->new_table) { @@ -11382,7 +11382,7 @@ lock_fail: to remove the newly created table or index from data dictionary and table cache in rollback_inplace_alter_table() */ - if (trx->state == TRX_STATE_NOT_STARTED) { + if (!trx->is_started()) { trx_start_for_ddl(trx); } @@ -11549,7 +11549,7 @@ err_index: purge_sys.resume_FTS(); } - if (trx->state == TRX_STATE_NOT_STARTED) { + if (!trx->is_started()) { /* Transaction may have been rolled back due to a lock wait timeout, deadlock, or a KILL statement. So restart the diff --git a/storage/innobase/include/db0err.h b/storage/innobase/include/db0err.h index 960ec3905eb..921cc977e2e 100644 --- a/storage/innobase/include/db0err.h +++ b/storage/innobase/include/db0err.h @@ -68,8 +68,6 @@ enum dberr_t { noticed */ DB_CANNOT_DROP_CONSTRAINT, /*!< dropping a foreign key constraint from a table failed */ - DB_NO_SAVEPOINT, /*!< no savepoint exists with the given - name */ DB_TABLESPACE_EXISTS, /*!< we cannot create a new single-table tablespace because a file of the same name already exists */ diff --git a/storage/innobase/include/fts0fts.h b/storage/innobase/include/fts0fts.h index 1d2b409be01..810bfe6966b 100644 --- a/storage/innobase/include/fts0fts.h +++ b/storage/innobase/include/fts0fts.h @@ -201,9 +201,9 @@ struct fts_trx_t { /** Information required for transaction savepoint handling. */ struct fts_savepoint_t { - char* name; /*!< First entry is always NULL, the - default instance. Otherwise the name - of the savepoint */ + const void* name; /*!< First entry is always NULL, the + default instance. Otherwise the + savepoint */ ib_rbt_t* tables; /*!< Modified FTS tables */ }; @@ -666,7 +666,7 @@ void fts_savepoint_take( /*===============*/ fts_trx_t* fts_trx, /*!< in: fts transaction */ - const char* name); /*!< in: savepoint name */ + const void* name); /*!< in: savepoint */ /**********************************************************************//** Refresh last statement savepoint. */ @@ -681,7 +681,7 @@ void fts_savepoint_release( /*==================*/ trx_t* trx, /*!< in: transaction */ - const char* name); /*!< in: savepoint name */ + const void* name); /*!< in: savepoint */ /** Clear cache. @param[in,out] cache fts cache */ @@ -702,7 +702,7 @@ void fts_savepoint_rollback( /*===================*/ trx_t* trx, /*!< in: transaction */ - const char* name); /*!< in: savepoint name */ + const void* name); /*!< in: savepoint */ /*********************************************************************//** Rollback to and including savepoint indentified by name. */ diff --git a/storage/innobase/include/row0mysql.h b/storage/innobase/include/row0mysql.h index 878d9c9f1a2..8cbeed7d297 100644 --- a/storage/innobase/include/row0mysql.h +++ b/storage/innobase/include/row0mysql.h @@ -172,7 +172,7 @@ row_mysql_handle_errors( during the function entry */ trx_t* trx, /*!< in: transaction */ que_thr_t* thr, /*!< in: query thread, or NULL */ - trx_savept_t* savept) /*!< in: savepoint, or NULL */ + const undo_no_t*savept) /*!< in: pointer to savepoint, or nullptr */ MY_ATTRIBUTE((nonnull(1,2))); /********************************************************************//** Create a prebuilt struct for a MySQL table handle. diff --git a/storage/innobase/include/trx0roll.h b/storage/innobase/include/trx0roll.h index 9ef9ebe93b2..dd21597b270 100644 --- a/storage/innobase/include/trx0roll.h +++ b/storage/innobase/include/trx0roll.h @@ -74,62 +74,6 @@ trx_rollback_for_mysql( /*===================*/ trx_t* trx) /*!< in/out: transaction */ MY_ATTRIBUTE((nonnull)); -/*******************************************************************//** -Rollback the latest SQL statement for MySQL. -@return error code or DB_SUCCESS */ -dberr_t -trx_rollback_last_sql_stat_for_mysql( -/*=================================*/ - trx_t* trx) /*!< in/out: transaction */ - MY_ATTRIBUTE((nonnull)); -/*******************************************************************//** -Rolls back a transaction back to a named savepoint. Modifications after the -savepoint are undone but InnoDB does NOT release the corresponding locks -which are stored in memory. If a lock is 'implicit', that is, a new inserted -row holds a lock where the lock information is carried by the trx id stored in -the row, these locks are naturally released in the rollback. Savepoints which -were set after this savepoint are deleted. -@return if no savepoint of the name found then DB_NO_SAVEPOINT, -otherwise DB_SUCCESS */ -dberr_t -trx_rollback_to_savepoint_for_mysql( -/*================================*/ - trx_t* trx, /*!< in: transaction handle */ - const char* savepoint_name, /*!< in: savepoint name */ - int64_t* mysql_binlog_cache_pos) /*!< out: the MySQL binlog cache - position corresponding to this - savepoint; MySQL needs this - information to remove the - binlog entries of the queries - executed after the savepoint */ - MY_ATTRIBUTE((nonnull, warn_unused_result)); -/*******************************************************************//** -Creates a named savepoint. If the transaction is not yet started, starts it. -If there is already a savepoint of the same name, this call erases that old -savepoint and replaces it with a new. Savepoints are deleted in a transaction -commit or rollback. -@return always DB_SUCCESS */ -dberr_t -trx_savepoint_for_mysql( -/*====================*/ - trx_t* trx, /*!< in: transaction handle */ - const char* savepoint_name, /*!< in: savepoint name */ - int64_t binlog_cache_pos) /*!< in: MySQL binlog cache - position corresponding to this - connection at the time of the - savepoint */ - MY_ATTRIBUTE((nonnull)); -/*******************************************************************//** -Releases a named savepoint. Savepoints which -were set after this savepoint are deleted. -@return if no savepoint of the name found then DB_NO_SAVEPOINT, -otherwise DB_SUCCESS */ -dberr_t -trx_release_savepoint_for_mysql( -/*============================*/ - trx_t* trx, /*!< in: transaction handle */ - const char* savepoint_name) /*!< in: savepoint name */ - MY_ATTRIBUTE((nonnull, warn_unused_result)); /** Rollback node states */ enum roll_node_state { @@ -144,25 +88,9 @@ enum roll_node_state { struct roll_node_t{ que_common_t common; /*!< node type: QUE_NODE_ROLLBACK */ enum roll_node_state state; /*!< node execution state */ - const trx_savept_t* savept; /*!< savepoint to which to - roll back, in the case of a - partial rollback */ + undo_no_t savept; /*!< savepoint to which to + roll back; 0=entire transaction */ que_thr_t* undo_thr;/*!< undo query graph */ }; -/** A savepoint set with SQL's "SAVEPOINT savepoint_id" command */ -struct trx_named_savept_t{ - char* name; /*!< savepoint name */ - trx_savept_t savept; /*!< the undo number corresponding to - the savepoint */ - int64_t mysql_binlog_cache_pos; - /*!< the MySQL binlog cache position - corresponding to this savepoint, not - defined if the MySQL binlogging is not - enabled */ - UT_LIST_NODE_T(trx_named_savept_t) - trx_savepoints; /*!< the list of savepoints of a - transaction */ -}; - #endif diff --git a/storage/innobase/include/trx0sys.h b/storage/innobase/include/trx0sys.h index f76bdea33a4..f411e8420a9 100644 --- a/storage/innobase/include/trx0sys.h +++ b/storage/innobase/include/trx0sys.h @@ -495,19 +495,7 @@ class rw_trx_hash_t #ifdef UNIV_DEBUG - static void validate_element(trx_t *trx) - { - ut_ad(!trx->read_only || !trx->rsegs.m_redo.rseg); - ut_ad(!trx->is_autocommit_non_locking()); - /* trx->state can be anything except TRX_STATE_NOT_STARTED */ - ut_d(bool acquire_trx_mutex = !trx->mutex_is_owner()); - ut_d(if (acquire_trx_mutex) trx->mutex_lock()); - ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE) || - trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY) || - trx_state_eq(trx, TRX_STATE_PREPARED_RECOVERED) || - trx_state_eq(trx, TRX_STATE_PREPARED)); - ut_d(if (acquire_trx_mutex) trx->mutex_unlock()); - } + static void validate_element(trx_t *trx); struct debug_iterator_arg diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index 79d5b7be5da..e55c6f506ce 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -149,19 +149,15 @@ void trx_start_for_ddl_low(trx_t *trx); ut_ad((t)->start_file == 0); \ (t)->start_line = __LINE__; \ (t)->start_file = __FILE__; \ + t->state= TRX_STATE_NOT_STARTED; \ trx_start_for_ddl_low(t); \ } while (0) #else # define trx_start_for_ddl(t) trx_start_for_ddl_low(t) #endif /* UNIV_DEBUG */ -/**********************************************************************//** -Does the transaction commit for MySQL. -@return DB_SUCCESS or error number */ -dberr_t -trx_commit_for_mysql( -/*=================*/ - trx_t* trx); /*!< in/out: transaction */ +/** Commit a transaction */ +void trx_commit_for_mysql(trx_t *trx) noexcept; /** XA PREPARE a transaction. @param[in,out] trx transaction to prepare */ void trx_prepare_for_mysql(trx_t* trx); @@ -184,12 +180,6 @@ trx_t* trx_get_trx_by_xid(const XID* xid); /** Durably write log until trx->commit_lsn (if trx_t::commit_in_memory() was invoked with flush_log_later=true). */ void trx_commit_complete_for_mysql(trx_t *trx); -/**********************************************************************//** -Marks the latest SQL statement ended. */ -void -trx_mark_sql_stat_end( -/*==================*/ - trx_t* trx); /*!< in: trx handle */ /****************************************************************//** Prepares a transaction for commit/rollback. */ void @@ -663,6 +653,7 @@ public: Possible states: TRX_STATE_NOT_STARTED + TRX_STATE_ABORTED TRX_STATE_ACTIVE TRX_STATE_PREPARED TRX_STATE_PREPARED_RECOVERED (special case of TRX_STATE_PREPARED) @@ -672,6 +663,8 @@ public: Regular transactions: * NOT_STARTED -> ACTIVE -> COMMITTED -> NOT_STARTED + * NOT_STARTED -> ABORTED (when thd_mark_transaction_to_rollback() is called) + * ABORTED -> NOT_STARTED (acknowledging the rollback of a transaction) Auto-commit non-locking read-only: * NOT_STARTED -> ACTIVE -> NOT_STARTED @@ -708,16 +701,18 @@ public: do we remove it from the read-only list and put it on the read-write list. During this switch we assign it a rollback segment. - When a transaction is NOT_STARTED, it can be in trx_list. It cannot be - in rw_trx_hash. + When a transaction is NOT_STARTED or ABORTED, it can be in trx_list. + It cannot be in rw_trx_hash. - ACTIVE->PREPARED->COMMITTED is only possible when trx is in rw_trx_hash. - The transition ACTIVE->PREPARED is protected by trx->mutex. + ACTIVE->PREPARED->COMMITTED and ACTIVE->COMMITTED is only possible when + trx is in rw_trx_hash. These transitions are protected by trx_t::mutex. - ACTIVE->COMMITTED is possible when the transaction is in - rw_trx_hash. + COMMITTED->NOT_STARTED is possible when trx_t::mutex is being held. + The transaction would already have been removed from rw_trx_hash by + trx_sys_t::deregister_rw() on the transition to COMMITTED. - Transitions to COMMITTED are protected by trx_t::mutex. */ + Transitions between NOT_STARTED and ABORTED can be performed at any time by + the thread that is associated with the transaction. */ Atomic_relaxed state; /** The locks of the transaction. Protected by lock_sys.latch @@ -733,6 +728,14 @@ public: bool is_wsrep() const { return false; } #endif /* WITH_WSREP */ + /** @return whether the transaction has been started */ + bool is_started() const noexcept + { + static_assert(TRX_STATE_NOT_STARTED == 0, ""); + static_assert(TRX_STATE_ABORTED == 1, ""); + return state > TRX_STATE_ABORTED; + } + /** Consistent read view of the transaction */ ReadView read_view; @@ -839,10 +842,6 @@ public: it is a stored procedure with a COMMIT WORK statement, for instance */ /*------------------------------*/ - UT_LIST_BASE_NODE_T(trx_named_savept_t) - trx_savepoints; /*!< savepoints set with SAVEPOINT ..., - oldest first */ - /*------------------------------*/ undo_no_t undo_no; /*!< next undo log record number to assign; since the undo log is private for a transaction, this @@ -850,7 +849,7 @@ public: with no gaps; thus it represents the number of modified/inserted rows in a transaction */ - trx_savept_t last_sql_stat_start; + undo_no_t last_stmt_start; /*!< undo_no when the last sql statement was started: in case of an error, trx is rolled back down to this number */ @@ -949,16 +948,17 @@ public: void evict_table(table_id_t table_id, bool reset_only= false); /** Initiate rollback. - @param savept savepoint to which to roll back + @param savept pointer to savepoint; nullptr=entire transaction @return error code or DB_SUCCESS */ - dberr_t rollback(trx_savept_t *savept= nullptr); + dberr_t rollback(const undo_no_t *savept= nullptr) noexcept; /** Roll back an active transaction. - @param savept savepoint to which to roll back */ - inline void rollback_low(trx_savept_t *savept= nullptr); + @param savept pointer to savepoint; nullptr=entire transaction + @return error code or DB_SUCCESS */ + dberr_t rollback_low(const undo_no_t *savept= nullptr) noexcept; /** Finish rollback. @return whether the rollback was completed normally @retval false if the rollback was aborted by shutdown */ - inline bool rollback_finish(); + bool rollback_finish() noexcept; private: /** Apply any changes to tables for which online DDL is in progress. */ ATTRIBUTE_COLD void apply_log(); @@ -968,9 +968,10 @@ private: @param mtr mini-transaction (if there are any persistent modifications) */ inline void commit_in_memory(const mtr_t *mtr); /** Write log for committing the transaction. */ - void commit_persist(); - /** Clean up the transaction after commit_in_memory() */ - void commit_cleanup(); + void commit_persist() noexcept; + /** Clean up the transaction after commit_in_memory() + @return false (always) */ + bool commit_cleanup() noexcept; /** Commit the transaction in a mini-transaction. @param mtr mini-transaction (if there are any persistent modifications) */ void commit_low(mtr_t *mtr= nullptr); @@ -985,7 +986,7 @@ private: inline void write_serialisation_history(mtr_t *mtr); public: /** Commit the transaction. */ - void commit(); + void commit() noexcept; /** Try to drop a persistent table. @param table persistent table @@ -1005,16 +1006,6 @@ public: void commit(std::vector &deleted); - /** Discard all savepoints */ - void savepoints_discard() - { savepoints_discard(UT_LIST_GET_FIRST(trx_savepoints)); } - - - /** Discard all savepoints starting from a particular savepoint. - @param savept first savepoint to discard */ - void savepoints_discard(trx_named_savept_t *savept); - - bool is_referenced() const { return (skip_lock_inheritance_and_n_ref & ~(1U << 31)) > 0; @@ -1128,15 +1119,6 @@ private: trx_rseg_t *assign_temp_rseg(); }; -/** -Check if transaction is started. -@param[in] trx Transaction whose state we need to check -@reutrn true if transaction is in state started */ -inline bool trx_is_started(const trx_t* trx) -{ - return trx->state != TRX_STATE_NOT_STARTED; -} - /* Transaction isolation levels (trx->isolation_level) */ #define TRX_ISO_READ_UNCOMMITTED 0 /* dirty read: non-locking SELECTs are performed so that diff --git a/storage/innobase/include/trx0trx.inl b/storage/innobase/include/trx0trx.inl index b063c920e2f..317f1f5cd0d 100644 --- a/storage/innobase/include/trx0trx.inl +++ b/storage/innobase/include/trx0trx.inl @@ -67,6 +67,8 @@ trx_state_eq( && thd_get_error_number(trx->mysql_thd))); return(true); + case TRX_STATE_ABORTED: + break; } ut_error; #endif /* UNIV_DEBUG */ diff --git a/storage/innobase/include/trx0types.h b/storage/innobase/include/trx0types.h index 86e1f67bc02..d0a3c1d7892 100644 --- a/storage/innobase/include/trx0types.h +++ b/storage/innobase/include/trx0types.h @@ -52,6 +52,8 @@ constexpr uint innodb_purge_batch_size_MAX= 5000; /** Transaction states (trx_t::state) */ enum trx_state_t { TRX_STATE_NOT_STARTED, + /** The transaction was aborted (rolled back) due to an error */ + TRX_STATE_ABORTED, TRX_STATE_ACTIVE, /** XA PREPARE has been executed; only XA COMMIT or XA ROLLBACK @@ -59,6 +61,7 @@ enum trx_state_t { TRX_STATE_PREPARED, /** XA PREPARE transaction that was returned to ha_recover() */ TRX_STATE_PREPARED_RECOVERED, + /** The transaction has been committed (or completely rolled back) */ TRX_STATE_COMMITTED_IN_MEMORY }; @@ -76,8 +79,6 @@ struct trx_undo_t; struct roll_node_t; /** Commit command node in a query graph */ struct commit_node_t; -/** SAVEPOINT command node in a query graph */ -struct trx_named_savept_t; /* @} */ /** Row identifier (DB_ROW_ID, DATA_ROW_ID) */ @@ -89,11 +90,6 @@ typedef ib_id_t roll_ptr_t; /** Undo number */ typedef ib_id_t undo_no_t; -/** Transaction savepoint */ -struct trx_savept_t{ - undo_no_t least_undo_no; /*!< least undo number to undo */ -}; - /** File objects */ /* @{ */ /** Undo segment header */ diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index 2392d67bb04..1795852ec94 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -6692,23 +6692,19 @@ lock_unlock_table_autoinc( /*======================*/ trx_t* trx) /*!< in/out: transaction */ { - lock_sys.assert_unlocked(); - ut_ad(!trx->mutex_is_owner()); - ut_ad(!trx->lock.wait_lock); + /* This function is invoked for a running transaction by the thread + that is serving the transaction. Therefore it is not necessary to + hold trx->mutex here. */ - /* This can be invoked on NOT_STARTED, ACTIVE, PREPARED, - but not COMMITTED transactions. */ + lock_sys.assert_unlocked(); + ut_ad(!trx->mutex_is_owner()); + ut_ad(!trx->lock.wait_lock); + ut_d(trx_state_t trx_state{trx->state}); + ut_ad(trx_state == TRX_STATE_ACTIVE || trx_state == TRX_STATE_PREPARED || + trx_state == TRX_STATE_NOT_STARTED); - ut_ad(trx_state_eq(trx, TRX_STATE_NOT_STARTED) - || !trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY)); - - /* This function is invoked for a running transaction by the - thread that is serving the transaction. Therefore it is not - necessary to hold trx->mutex here. */ - - if (lock_trx_holds_autoinc_locks(trx)) { - lock_release_autoinc_locks(trx); - } + if (lock_trx_holds_autoinc_locks(trx)) + lock_release_autoinc_locks(trx); } /** Handle a pending lock wait (DB_LOCK_WAIT) in a semi-consistent read diff --git a/storage/innobase/que/que0que.cc b/storage/innobase/que/que0que.cc index 5e1e0686c97..81d93505d70 100644 --- a/storage/innobase/que/que0que.cc +++ b/storage/innobase/que/que0que.cc @@ -514,8 +514,7 @@ que_thr_step( for_step(thr); } else if (type == QUE_NODE_PROC) { if (thr->prev_node == que_node_get_parent(node)) { - trx->last_sql_stat_start.least_undo_no - = trx->undo_no; + trx->last_stmt_start = trx->undo_no; } proc_step(thr); diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc index 30d186b4863..bb3b03aef14 100644 --- a/storage/innobase/row/row0mysql.cc +++ b/storage/innobase/row/row0mysql.cc @@ -625,7 +625,7 @@ row_mysql_handle_errors( function */ trx_t* trx, /*!< in: transaction */ que_thr_t* thr, /*!< in: query thread, or NULL */ - trx_savept_t* savept) /*!< in: savepoint, or NULL */ + const undo_no_t*savept) /*!< in: pointer to savepoint, or nullptr */ { dberr_t err; @@ -681,8 +681,7 @@ handle_new_error: } /* MariaDB will roll back the entire transaction. */ trx->bulk_insert = false; - trx->last_sql_stat_start.least_undo_no = 0; - trx->savepoints_discard(); + trx->last_stmt_start = 0; break; case DB_LOCK_WAIT: err = lock_wait(thr); @@ -700,7 +699,6 @@ handle_new_error: rollback: /* Roll back the whole transaction; this resolution was added to version 3.23.43 */ - trx->rollback(); break; @@ -1137,7 +1135,7 @@ row_lock_table_autoinc_for_mysql( trx->error_state = err; } while (err != DB_SUCCESS - && row_mysql_handle_errors(&err, trx, thr, NULL)); + && row_mysql_handle_errors(&err, trx, thr, nullptr)); trx->op_info = ""; @@ -1179,7 +1177,7 @@ row_lock_table(row_prebuilt_t* prebuilt) prebuilt->select_lock_type), thr); trx->error_state = err; } while (err != DB_SUCCESS - && row_mysql_handle_errors(&err, trx, thr, NULL)); + && row_mysql_handle_errors(&err, trx, thr, nullptr)); trx->op_info = ""; @@ -1218,7 +1216,6 @@ row_insert_for_mysql( row_prebuilt_t* prebuilt, ins_mode_t ins_mode) { - trx_savept_t savept; que_thr_t* thr; dberr_t err; ibool was_lock_wait; @@ -1272,7 +1269,7 @@ row_insert_for_mysql( roll back to the start of the transaction. For correctness, it would suffice to roll back to the start of the first insert into this empty table, but we will keep it simple and efficient. */ - savept.least_undo_no = trx->bulk_insert ? 0 : trx->undo_no; + const undo_no_t savept{trx->bulk_insert ? 0 : trx->undo_no}; thr = que_fork_get_first_thr(prebuilt->ins_graph); @@ -1586,7 +1583,6 @@ init_fts_doc_id_for_ref( dberr_t row_update_for_mysql(row_prebuilt_t* prebuilt) { - trx_savept_t savept; dberr_t err; que_thr_t* thr; dict_index_t* clust_index; @@ -1643,7 +1639,7 @@ row_update_for_mysql(row_prebuilt_t* prebuilt) generated for the table: MySQL does not know anything about the row id used as the clustered index key */ - savept.least_undo_no = trx->undo_no; + undo_no_t savept = trx->undo_no; thr = que_fork_get_first_thr(prebuilt->upd_graph); diff --git a/storage/innobase/row/row0sel.cc b/storage/innobase/row/row0sel.cc index a6340ccbc08..38b79af8da4 100644 --- a/storage/innobase/row/row0sel.cc +++ b/storage/innobase/row/row0sel.cc @@ -1208,8 +1208,7 @@ re_scan: mtr->commit(); trx->error_state = err; thr->lock_state = QUE_THR_LOCK_ROW; - if (row_mysql_handle_errors( - &err, trx, thr, NULL)) { + if (row_mysql_handle_errors(&err, trx, thr, 0)) { thr->lock_state = QUE_THR_LOCK_NOLOCK; mtr->start(); @@ -5873,7 +5872,7 @@ lock_table_wait: trx->error_state = err; thr->lock_state = QUE_THR_LOCK_ROW; - if (row_mysql_handle_errors(&err, trx, thr, NULL)) { + if (row_mysql_handle_errors(&err, trx, thr, nullptr)) { /* It was a lock wait, and it ended */ thr->lock_state = QUE_THR_LOCK_NOLOCK; diff --git a/storage/innobase/trx/trx0i_s.cc b/storage/innobase/trx/trx0i_s.cc index 4a661c51183..c1784b94bb1 100644 --- a/storage/innobase/trx/trx0i_s.cc +++ b/storage/innobase/trx/trx0i_s.cc @@ -1054,7 +1054,7 @@ static void fetch_data_into_cache(trx_i_s_cache_t *cache) &trx != (purge_sys.query ? purge_sys.query->trx : nullptr)) { trx.mutex_lock(); - if (trx.state != TRX_STATE_NOT_STARTED) + if (trx.is_started()) fetch_data_into_cache_low(cache, &trx); trx.mutex_unlock(); } diff --git a/storage/innobase/trx/trx0roll.cc b/storage/innobase/trx/trx0roll.cc index d1baab9e684..a59a07b35a9 100644 --- a/storage/innobase/trx/trx0roll.cc +++ b/storage/innobase/trx/trx0roll.cc @@ -43,6 +43,7 @@ Created 3/26/1996 Heikki Tuuri #include "trx0sys.h" #include "trx0trx.h" #include "trx0undo.h" +#include "log.h" #ifdef UNIV_PFS_THREAD mysql_pfs_key_t trx_rollback_clean_thread_key; @@ -54,10 +55,7 @@ bool trx_rollback_is_active; /** In crash recovery, the current trx to be rolled back; NULL otherwise */ const trx_t* trx_roll_crash_recv_trx; -/** Finish transaction rollback. -@return whether the rollback was completed normally -@retval false if the rollback was aborted by shutdown */ -inline bool trx_t::rollback_finish() +bool trx_t::rollback_finish() noexcept { mod_tables.clear(); apply_online_log= false; @@ -85,34 +83,28 @@ inline bool trx_t::rollback_finish() undo= nullptr; } commit_low(); - commit_cleanup(); - return false; + return commit_cleanup(); } -/** Roll back an active transaction. */ -inline void trx_t::rollback_low(trx_savept_t *savept) +dberr_t trx_t::rollback_low(const undo_no_t *savept) noexcept { + op_info= "rollback"; mem_heap_t *heap= mem_heap_create(512); roll_node_t *roll_node= roll_node_create(heap); - roll_node->savept= savept; + + roll_node->savept= savept ? *savept : 0; ut_ad(!in_rollback); #ifdef UNIV_DEBUG + if (savept) { - const auto s= state; - ut_ad(s == TRX_STATE_ACTIVE || - s == TRX_STATE_PREPARED || - s == TRX_STATE_PREPARED_RECOVERED); - if (savept) - { - ut_ad(s == TRX_STATE_ACTIVE); - ut_ad(mysql_thd); - ut_ad(!is_recovered); - } + ut_ad(state == TRX_STATE_ACTIVE); + ut_ad(mysql_thd); + ut_ad(!is_recovered); } #endif - error_state = DB_SUCCESS; + error_state= DB_SUCCESS; if (has_logged()) { @@ -139,7 +131,7 @@ inline void trx_t::rollback_low(trx_savept_t *savept) victim. Galera transaction abort can be invoked during partial rollback. */ ut_ad(!(lock.was_chosen_as_deadlock_victim & 1)); ut_a(error_state == DB_SUCCESS); - const undo_no_t limit= savept->least_undo_no; + const undo_no_t limit{*savept}; apply_online_log= false; for (trx_mod_tables_t::iterator i= mod_tables.begin(); i != mod_tables.end(); ) @@ -155,49 +147,34 @@ inline void trx_t::rollback_low(trx_savept_t *savept) } mem_heap_free(heap); + op_info= ""; + return error_state; } -/** Initiate rollback. -@param savept savepoint -@return error code or DB_SUCCESS */ -dberr_t trx_t::rollback(trx_savept_t *savept) +dberr_t trx_t::rollback(const undo_no_t *savept) noexcept { ut_ad(!mutex_is_owner()); - if (state == TRX_STATE_NOT_STARTED) - { + switch (state) { + case TRX_STATE_ABORTED: + ut_ad(!savept); + state= TRX_STATE_NOT_STARTED; + /* fall through */ + case TRX_STATE_NOT_STARTED: error_state= DB_SUCCESS; return DB_SUCCESS; + case TRX_STATE_PREPARED: + case TRX_STATE_PREPARED_RECOVERED: + case TRX_STATE_COMMITTED_IN_MEMORY: + ut_ad("invalid state" == 0); + /* fall through */ + case TRX_STATE_ACTIVE: + break; } - ut_ad(state == TRX_STATE_ACTIVE); #ifdef WITH_WSREP if (!savept && is_wsrep() && wsrep_thd_is_SR(mysql_thd)) wsrep_handle_SR_rollback(nullptr, mysql_thd); #endif /* WITH_WSREP */ - rollback_low(savept); - return error_state; -} - -/*******************************************************************//** -Rollback a transaction used in MySQL. -@return error code or DB_SUCCESS */ -static -dberr_t -trx_rollback_for_mysql_low( -/*=======================*/ - trx_t* trx) /*!< in/out: transaction */ -{ - trx->op_info = "rollback"; - - /* If we are doing the XA recovery of prepared transactions, - then the transaction object does not have an InnoDB session - object, and we set a dummy session that we use for all MySQL - transactions. */ - - trx->rollback_low(); - - trx->op_info = ""; - - return(trx->error_state); + return rollback_low(savept); } /** Rollback a transaction used in MySQL @@ -211,6 +188,9 @@ dberr_t trx_rollback_for_mysql(trx_t* trx) that is associated with the current thread. */ switch (trx->state) { + case TRX_STATE_ABORTED: + trx->state = TRX_STATE_NOT_STARTED; + /* fall through */ case TRX_STATE_NOT_STARTED: trx->will_lock = false; ut_ad(trx->mysql_thd); @@ -222,13 +202,13 @@ dberr_t trx_rollback_for_mysql(trx_t* trx) ut_ad(!trx->is_wsrep()); trx->lock.was_chosen_as_deadlock_victim= false; #endif + ut_a(UT_LIST_GET_LEN(trx->lock.trx_locks) == 0); return(DB_SUCCESS); - case TRX_STATE_ACTIVE: ut_ad(trx->mysql_thd); ut_ad(!trx->is_recovered); ut_ad(!trx->is_autocommit_non_locking() || trx->read_only); - return(trx_rollback_for_mysql_low(trx)); + return trx->rollback_low(); case TRX_STATE_PREPARED: case TRX_STATE_PREPARED_RECOVERED: @@ -266,7 +246,7 @@ dberr_t trx_rollback_for_mysql(trx_t* trx) mtr.commit(); ut_ad(mtr.commit_lsn() > 0); } - return(trx_rollback_for_mysql_low(trx)); + return trx->rollback_low(); case TRX_STATE_COMMITTED_IN_MEMORY: ut_ad(!trx->is_autocommit_non_locking()); @@ -277,296 +257,6 @@ dberr_t trx_rollback_for_mysql(trx_t* trx) return(DB_CORRUPTION); } -/*******************************************************************//** -Rollback the latest SQL statement for MySQL. -@return error code or DB_SUCCESS */ -dberr_t -trx_rollback_last_sql_stat_for_mysql( -/*=================================*/ - trx_t* trx) /*!< in/out: transaction */ -{ - dberr_t err; - - /* We are reading trx->state without holding trx->mutex - here, because the statement rollback should be invoked for a - running active MySQL transaction that is associated with the - current thread. */ - ut_ad(trx->mysql_thd); - - switch (trx->state) { - case TRX_STATE_NOT_STARTED: - return(DB_SUCCESS); - - case TRX_STATE_ACTIVE: - ut_ad(trx->mysql_thd); - ut_ad(!trx->is_recovered); - ut_ad(!trx->is_autocommit_non_locking() || trx->read_only); - - trx->op_info = "rollback of SQL statement"; - - err = trx->rollback(&trx->last_sql_stat_start); - - if (trx->fts_trx != NULL) { - fts_savepoint_rollback_last_stmt(trx); - fts_savepoint_laststmt_refresh(trx); - } - - trx->last_sql_stat_start.least_undo_no = trx->undo_no; - trx->end_bulk_insert(); - - trx->op_info = ""; - - return(err); - - case TRX_STATE_PREPARED: - case TRX_STATE_PREPARED_RECOVERED: - case TRX_STATE_COMMITTED_IN_MEMORY: - /* The statement rollback is only allowed on an ACTIVE - transaction, not a PREPARED or COMMITTED one. */ - break; - } - - ut_error; - return(DB_CORRUPTION); -} - -/*******************************************************************//** -Search for a savepoint using name. -@return savepoint if found else NULL */ -static -trx_named_savept_t* -trx_savepoint_find( -/*===============*/ - trx_t* trx, /*!< in: transaction */ - const char* name) /*!< in: savepoint name */ -{ - trx_named_savept_t* savep; - - for (savep = UT_LIST_GET_FIRST(trx->trx_savepoints); - savep != NULL; - savep = UT_LIST_GET_NEXT(trx_savepoints, savep)) { - if (!strcmp(savep->name, name)) { - return(savep); - } - } - - return(NULL); -} - -/*******************************************************************//** -Frees a single savepoint struct. */ -static -void -trx_roll_savepoint_free( -/*=====================*/ - trx_t* trx, /*!< in: transaction handle */ - trx_named_savept_t* savep) /*!< in: savepoint to free */ -{ - UT_LIST_REMOVE(trx->trx_savepoints, savep); - - ut_free(savep->name); - ut_free(savep); -} - -/** Discard all savepoints starting from a particular savepoint. -@param savept first savepoint to discard */ -void trx_t::savepoints_discard(trx_named_savept_t *savept) -{ - while (savept) - { - auto next= UT_LIST_GET_NEXT(trx_savepoints, savept); - trx_roll_savepoint_free(this, savept); - savept= next; - } -} - -/*******************************************************************//** -Rolls back a transaction back to a named savepoint. Modifications after the -savepoint are undone but InnoDB does NOT release the corresponding locks -which are stored in memory. If a lock is 'implicit', that is, a new inserted -row holds a lock where the lock information is carried by the trx id stored in -the row, these locks are naturally released in the rollback. Savepoints which -were set after this savepoint are deleted. -@return if no savepoint of the name found then DB_NO_SAVEPOINT, -otherwise DB_SUCCESS */ -static MY_ATTRIBUTE((nonnull, warn_unused_result)) -dberr_t -trx_rollback_to_savepoint_for_mysql_low( -/*====================================*/ - trx_t* trx, /*!< in/out: transaction */ - trx_named_savept_t* savep, /*!< in/out: savepoint */ - int64_t* mysql_binlog_cache_pos) - /*!< out: the MySQL binlog - cache position corresponding - to this savepoint; MySQL needs - this information to remove the - binlog entries of the queries - executed after the savepoint */ -{ - dberr_t err; - - ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE)); - ut_ad(trx->mysql_thd); - - /* Free all savepoints strictly later than savep. */ - - trx->savepoints_discard(UT_LIST_GET_NEXT(trx_savepoints, savep)); - - *mysql_binlog_cache_pos = savep->mysql_binlog_cache_pos; - - trx->op_info = "rollback to a savepoint"; - - err = trx->rollback(&savep->savept); - - /* Store the current undo_no of the transaction so that - we know where to roll back if we have to roll back the - next SQL statement: */ - - trx_mark_sql_stat_end(trx); - - trx->op_info = ""; - return(err); -} - -/*******************************************************************//** -Rolls back a transaction back to a named savepoint. Modifications after the -savepoint are undone but InnoDB does NOT release the corresponding locks -which are stored in memory. If a lock is 'implicit', that is, a new inserted -row holds a lock where the lock information is carried by the trx id stored in -the row, these locks are naturally released in the rollback. Savepoints which -were set after this savepoint are deleted. -@return if no savepoint of the name found then DB_NO_SAVEPOINT, -otherwise DB_SUCCESS */ -dberr_t -trx_rollback_to_savepoint_for_mysql( -/*================================*/ - trx_t* trx, /*!< in: transaction handle */ - const char* savepoint_name, /*!< in: savepoint name */ - int64_t* mysql_binlog_cache_pos) /*!< out: the MySQL binlog cache - position corresponding to this - savepoint; MySQL needs this - information to remove the - binlog entries of the queries - executed after the savepoint */ -{ - trx_named_savept_t* savep; - - /* We are reading trx->state without holding trx->mutex - here, because the savepoint rollback should be invoked for a - running active MySQL transaction that is associated with the - current thread. */ - ut_ad(trx->mysql_thd); - - savep = trx_savepoint_find(trx, savepoint_name); - - if (savep == NULL) { - return(DB_NO_SAVEPOINT); - } - - switch (trx->state) { - case TRX_STATE_NOT_STARTED: - ib::error() << "Transaction has a savepoint " - << savep->name - << " though it is not started"; - return(DB_ERROR); - - case TRX_STATE_ACTIVE: - - return(trx_rollback_to_savepoint_for_mysql_low( - trx, savep, mysql_binlog_cache_pos)); - - case TRX_STATE_PREPARED: - case TRX_STATE_PREPARED_RECOVERED: - case TRX_STATE_COMMITTED_IN_MEMORY: - /* The savepoint rollback is only allowed on an ACTIVE - transaction, not a PREPARED or COMMITTED one. */ - break; - } - - ut_error; - return(DB_CORRUPTION); -} - -/*******************************************************************//** -Creates a named savepoint. If the transaction is not yet started, starts it. -If there is already a savepoint of the same name, this call erases that old -savepoint and replaces it with a new. Savepoints are deleted in a transaction -commit or rollback. -@return always DB_SUCCESS */ -dberr_t -trx_savepoint_for_mysql( -/*====================*/ - trx_t* trx, /*!< in: transaction handle */ - const char* savepoint_name, /*!< in: savepoint name */ - int64_t binlog_cache_pos) /*!< in: MySQL binlog cache - position corresponding to this - connection at the time of the - savepoint */ -{ - trx_named_savept_t* savep; - - trx_start_if_not_started_xa(trx, false); - - savep = trx_savepoint_find(trx, savepoint_name); - - if (savep) { - /* There is a savepoint with the same name: free that */ - - UT_LIST_REMOVE(trx->trx_savepoints, savep); - - ut_free(savep->name); - ut_free(savep); - } - - /* Create a new savepoint and add it as the last in the list */ - - savep = static_cast( - ut_malloc_nokey(sizeof(*savep))); - - savep->name = mem_strdup(savepoint_name); - - savep->savept.least_undo_no = trx->undo_no; - trx->last_sql_stat_start.least_undo_no = trx->undo_no; - - savep->mysql_binlog_cache_pos = binlog_cache_pos; - - UT_LIST_ADD_LAST(trx->trx_savepoints, savep); - - trx->end_bulk_insert(); - - return(DB_SUCCESS); -} - -/*******************************************************************//** -Releases only the named savepoint. Savepoints which were set after this -savepoint are left as is. -@return if no savepoint of the name found then DB_NO_SAVEPOINT, -otherwise DB_SUCCESS */ -dberr_t -trx_release_savepoint_for_mysql( -/*============================*/ - trx_t* trx, /*!< in: transaction handle */ - const char* savepoint_name) /*!< in: savepoint name */ -{ - trx_named_savept_t* savep; - - ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE, true) - || trx_state_eq(trx, TRX_STATE_PREPARED, true)); - ut_ad(trx->mysql_thd); - - savep = trx_savepoint_find(trx, savepoint_name); - - if (savep != NULL) { - trx_roll_savepoint_free(trx, savep); - return DB_SUCCESS; - } else if (trx->last_sql_stat_start.least_undo_no == 0) { - /* Bulk insert could have discarded savepoints */ - return DB_SUCCESS; - } - - return DB_NO_SAVEPOINT; -} - /*******************************************************************//** Roll back an active transaction. */ static @@ -618,7 +308,9 @@ trx_rollback_active( if (UNIV_UNLIKELY(!trx->rollback_finish())) { ut_ad(!dictionary_locked); } else { - ib::info() << "Rolled back recovered transaction " << trx_id; + sql_print_information( + "InnoDB: Rolled back recovered transaction " + TRX_ID_FMT, trx_id); } if (dictionary_locked) { @@ -904,7 +596,6 @@ trx_rollback_step( if (node->state == ROLL_NODE_SEND) { trx_t* trx; - ib_id_t roll_limit; trx = thr_get_trx(thr); @@ -912,13 +603,11 @@ trx_rollback_step( ut_a(node->undo_thr == NULL); - roll_limit = node->savept ? node->savept->least_undo_no : 0; - trx->mutex_lock(); trx_commit_or_rollback_prepare(trx); - node->undo_thr = trx_rollback_start(trx, roll_limit); + node->undo_thr = trx_rollback_start(trx, node->savept); trx->mutex_unlock(); } else { diff --git a/storage/innobase/trx/trx0sys.cc b/storage/innobase/trx/trx0sys.cc index df3755f6c10..68c3e79a3d1 100644 --- a/storage/innobase/trx/trx0sys.cc +++ b/storage/innobase/trx/trx0sys.cc @@ -47,6 +47,31 @@ trx_sys_t trx_sys; #ifdef UNIV_DEBUG /* Flag to control TRX_RSEG_N_SLOTS behavior debugging. */ uint trx_rseg_n_slots_debug = 0; + +void rw_trx_hash_t::validate_element(trx_t *trx) +{ + ut_ad(!trx->read_only || !trx->rsegs.m_redo.rseg); + ut_ad(!trx->is_autocommit_non_locking()); + ut_d(bool acquire_trx_mutex= !trx->mutex_is_owner()); + ut_d(if (acquire_trx_mutex) trx->mutex_lock()); + switch (trx->state) { + case TRX_STATE_NOT_STARTED: + case TRX_STATE_ABORTED: + ut_error; + case TRX_STATE_PREPARED: + case TRX_STATE_PREPARED_RECOVERED: + case TRX_STATE_COMMITTED_IN_MEMORY: + ut_ad(!trx->is_autocommit_non_locking()); + break; + case TRX_STATE_ACTIVE: + if (!trx->is_autocommit_non_locking()) + break; + ut_ad(!trx->is_recovered); + ut_ad(trx->read_only); + ut_ad(trx->mysql_thd); + } + ut_d(if (acquire_trx_mutex) trx->mutex_unlock()); +} #endif /** Display the MySQL binlog offset info if it is present in the trx @@ -364,6 +389,7 @@ size_t trx_sys_t::any_active_transactions(size_t *prepared) trx_sys.trx_list.for_each([&](const trx_t &trx) { switch (trx.state) { case TRX_STATE_NOT_STARTED: + case TRX_STATE_ABORTED: break; case TRX_STATE_ACTIVE: if (!trx.id) diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index baa2694c179..94ed416eb60 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -144,7 +144,7 @@ trx_init( trx->magic_n = TRX_MAGIC_N; - trx->last_sql_stat_start.least_undo_no = 0; + trx->last_stmt_start = 0; ut_ad(!trx->read_view.is_open()); @@ -192,10 +192,6 @@ struct TrxFactory { UT_LIST_INIT(trx->lock.evicted_tables, &dict_table_t::table_LRU); - UT_LIST_INIT( - trx->trx_savepoints, - &trx_named_savept_t::trx_savepoints); - trx->mutex_init(); } @@ -437,9 +433,8 @@ void trx_t::free() MEM_NOACCESS(&error_info, sizeof error_info); MEM_NOACCESS(&error_key_num, sizeof error_key_num); MEM_NOACCESS(&graph, sizeof graph); - MEM_NOACCESS(&trx_savepoints, sizeof trx_savepoints); MEM_NOACCESS(&undo_no, sizeof undo_no); - MEM_NOACCESS(&last_sql_stat_start, sizeof last_sql_stat_start); + MEM_NOACCESS(&last_stmt_start, sizeof last_stmt_start); MEM_NOACCESS(&rsegs, sizeof rsegs); MEM_NOACCESS(&roll_limit, sizeof roll_limit); MEM_NOACCESS(&in_rollback, sizeof in_rollback); @@ -467,9 +462,10 @@ void trx_t::free() /** Transition to committed state, to release implicit locks. */ TRANSACTIONAL_INLINE inline void trx_t::commit_state() { - ut_ad(state == TRX_STATE_PREPARED - || state == TRX_STATE_PREPARED_RECOVERED - || state == TRX_STATE_ACTIVE); + ut_d(auto trx_state{state}); + ut_ad(trx_state == TRX_STATE_PREPARED || + trx_state == TRX_STATE_PREPARED_RECOVERED || + trx_state == TRX_STATE_ACTIVE); /* This makes the transaction committed in memory and makes its changes to data visible to other transactions. NOTE that there is a small discrepancy from the strict formal visibility rules here: a @@ -1490,8 +1486,6 @@ TRANSACTIONAL_INLINE inline void trx_t::commit_in_memory(const mtr_t *mtr) } } - savepoints_discard(); - if (fts_trx) trx_finalize_for_fts(this, undo_no != 0); @@ -1510,7 +1504,7 @@ TRANSACTIONAL_INLINE inline void trx_t::commit_in_memory(const mtr_t *mtr) lock.was_chosen_as_deadlock_victim= false; } -void trx_t::commit_cleanup() +bool trx_t::commit_cleanup() noexcept { ut_ad(!dict_operation); ut_ad(!was_dict_operation); @@ -1527,6 +1521,7 @@ void trx_t::commit_cleanup() mutex.wr_unlock(); ut_a(error_state == DB_SUCCESS); + return false; } /** Commit the transaction in a mini-transaction. @@ -1591,7 +1586,7 @@ TRANSACTIONAL_TARGET void trx_t::commit_low(mtr_t *mtr) } -void trx_t::commit_persist() +void trx_t::commit_persist() noexcept { mtr_t *mtr= nullptr; mtr_t local_mtr; @@ -1605,7 +1600,7 @@ void trx_t::commit_persist() } -void trx_t::commit() +void trx_t::commit() noexcept { ut_ad(!was_dict_operation); ut_d(was_dict_operation= dict_operation); @@ -1641,6 +1636,7 @@ trx_commit_or_rollback_prepare( return; case TRX_STATE_COMMITTED_IN_MEMORY: + case TRX_STATE_ABORTED: break; } @@ -1708,33 +1704,26 @@ trx_commit_step( return(thr); } -/**********************************************************************//** -Does the transaction commit for MySQL. -@return DB_SUCCESS or error number */ -dberr_t -trx_commit_for_mysql( -/*=================*/ - trx_t* trx) /*!< in/out: transaction */ +void trx_commit_for_mysql(trx_t *trx) noexcept { - /* Because we do not do the commit by sending an Innobase - sig to the transaction, we must here make sure that trx has been - started. */ - - switch (trx->state) { - case TRX_STATE_NOT_STARTED: - return DB_SUCCESS; - case TRX_STATE_ACTIVE: - case TRX_STATE_PREPARED: - case TRX_STATE_PREPARED_RECOVERED: - trx->op_info = "committing"; - trx->commit(); - trx->op_info = ""; - return(DB_SUCCESS); - case TRX_STATE_COMMITTED_IN_MEMORY: - break; - } - ut_error; - return(DB_CORRUPTION); + switch (trx->state) { + case TRX_STATE_ABORTED: + trx->state= TRX_STATE_NOT_STARTED; + /* fall through */ + case TRX_STATE_NOT_STARTED: + trx->will_lock= false; + break; + case TRX_STATE_ACTIVE: + case TRX_STATE_PREPARED: + case TRX_STATE_PREPARED_RECOVERED: + trx->op_info= "committing"; + trx->commit(); + trx->op_info= ""; + break; + case TRX_STATE_COMMITTED_IN_MEMORY: + ut_error; + break; + } } /** Durably write log until trx->commit_lsn @@ -1754,42 +1743,6 @@ void trx_commit_complete_for_mysql(trx_t *trx) trx_flush_log_if_needed(lsn, trx); } -/**********************************************************************//** -Marks the latest SQL statement ended. */ -void -trx_mark_sql_stat_end( -/*==================*/ - trx_t* trx) /*!< in: trx handle */ -{ - ut_a(trx); - - switch (trx->state) { - case TRX_STATE_PREPARED: - case TRX_STATE_PREPARED_RECOVERED: - case TRX_STATE_COMMITTED_IN_MEMORY: - break; - case TRX_STATE_NOT_STARTED: - trx->undo_no = 0; - /* fall through */ - case TRX_STATE_ACTIVE: - if (trx->fts_trx != NULL) { - fts_savepoint_laststmt_refresh(trx); - } - - if (trx->is_bulk_insert()) { - /* Allow a subsequent INSERT into an empty table - if !unique_checks && !foreign_key_checks. */ - return; - } - - trx->last_sql_stat_start.least_undo_no = trx->undo_no; - trx->end_bulk_insert(); - return; - } - - ut_error; -} - /**********************************************************************//** Prints info about a transaction. */ void @@ -1815,9 +1768,16 @@ trx_print_low( fprintf(f, "TRANSACTION (%p)", trx); } + THD* thd = trx->mysql_thd; + switch (trx->state) { case TRX_STATE_NOT_STARTED: fputs(", not started", f); + thd = nullptr; + goto state_ok; + case TRX_STATE_ABORTED: + fputs(", forced rollback done", f); + thd = nullptr; goto state_ok; case TRX_STATE_ACTIVE: fprintf(f, ", ACTIVE %lu sec", @@ -1883,9 +1843,8 @@ state_ok: putc('\n', f); } - if (trx->state != TRX_STATE_NOT_STARTED && trx->mysql_thd != NULL) { - innobase_mysql_print_thd( - f, trx->mysql_thd, static_cast(max_query_len)); + if (thd) { + innobase_mysql_print_thd(f, thd, uint(max_query_len)); } } @@ -2201,6 +2160,7 @@ trx_start_if_not_started_xa_low( bool read_write) /*!< in: true if read write transaction */ { switch (trx->state) { + case TRX_STATE_ABORTED: case TRX_STATE_NOT_STARTED: trx_start_low(trx, read_write); return; @@ -2244,6 +2204,7 @@ trx_start_if_not_started_low( } return; + case TRX_STATE_ABORTED: case TRX_STATE_PREPARED: case TRX_STATE_PREPARED_RECOVERED: case TRX_STATE_COMMITTED_IN_MEMORY: diff --git a/storage/innobase/ut/ut0ut.cc b/storage/innobase/ut/ut0ut.cc index 3cdc49c4b32..55ace7b12f6 100644 --- a/storage/innobase/ut/ut0ut.cc +++ b/storage/innobase/ut/ut0ut.cc @@ -340,8 +340,6 @@ ut_strerr( return("Data structure corruption"); case DB_CANNOT_DROP_CONSTRAINT: return("Cannot drop constraint"); - case DB_NO_SAVEPOINT: - return("No such savepoint"); case DB_TABLESPACE_EXISTS: return("Tablespace already exists"); case DB_TABLESPACE_DELETED: