From 87125d7a010a0e138d78b022f003ab77bcc4717d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Lindstr=C3=B6m?= Date: Mon, 23 Mar 2026 09:43:00 +0200 Subject: [PATCH] MDEV-28750 : Assertion `trans_safe || !updated || thd->transaction->stmt.modified_non_trans_table' failed in virtual bool multi_update::send_eof() Problem was that in multi-table update that contains tables that are not transactional and tables that are transactional error handling does not work correctly if write set size is limited. Because, Galera replication for MyISAM is experimental it is better to avoid multi-table updates in this case. Fixed by not allowing multi-table updates if statement contains non transactional and transactional tables and write set size is limited. --- .../suite/galera/r/galera_max_ws_rows.result | 112 ++++++++++++++++++ .../galera/r/galera_var_max_ws_rows.result | 1 + .../suite/galera/t/galera_max_ws_rows.test | 104 ++++++++++++++++ .../galera/t/galera_var_max_ws_rows.test | 1 + sql/sql_update.cc | 35 ++++++ sql/sys_vars.cc | 6 +- sql/wsrep_var.cc | 23 ++++ sql/wsrep_var.h | 2 + 8 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 mysql-test/suite/galera/r/galera_max_ws_rows.result create mode 100644 mysql-test/suite/galera/t/galera_max_ws_rows.test diff --git a/mysql-test/suite/galera/r/galera_max_ws_rows.result b/mysql-test/suite/galera/r/galera_max_ws_rows.result new file mode 100644 index 0000000000000..7fef519357f8f --- /dev/null +++ b/mysql-test/suite/galera/r/galera_max_ws_rows.result @@ -0,0 +1,112 @@ +connection node_2; +connection node_1; +CREATE TABLE ti (a INT UNSIGNED, b SMALLINT, id BIGINT NOT NULL, KEY(b), PRIMARY KEY(id)) ENGINE=InnoDB; +CREATE TABLE tm (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=MyISAM; +SET GLOBAL wsrep_max_ws_rows=2; +START TRANSACTION; +INSERT INTO tm SET b=NULL, a=2; +INSERT INTO ti VALUES (1,2,4); +UPDATE tm AS t1 SET t1.a=t1.a * 2; +UPDATE ti AS t1 SET t1.a=t1.a * 2; +UPDATE tm AS t1 SET t1.a=t1.a * 2; +UPDATE ti AS t1 SET t1.a=t1.a * 2; +ERROR HY000: Some non-transactional changed tables couldn't be rolled back +SHOW WARNINGS; +Level Code Message +Error 1196 Some non-transactional changed tables couldn't be rolled back +Error 1180 wsrep_max_ws_rows exceeded +Error 1030 Got error 1180 "Unknown error 1180" from storage engine InnoDB +COMMIT; +SELECT * FROM ti; +a b id +SELECT * from tm; +a b +8 NULL +DROP TABLE ti,tm; +CREATE TABLE ti (a INT UNSIGNED, b SMALLINT, id BIGINT NOT NULL, KEY(b), PRIMARY KEY(id)) ENGINE=InnoDB; +CREATE TABLE tm (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=MyISAM; +SET GLOBAL wsrep_max_ws_rows=2; +START TRANSACTION; +INSERT INTO tm SET b=NULL, a=2; +SET sql_mode=only_full_group_by; +INSERT INTO ti VALUES (1,2,4); +UPDATE tm AS t1, ti AS t2 SET t1.a=t1.a * 2, t2.a=t2.a * 2; +ERROR HY000: Galera replication not supported +UPDATE tm AS t1, ti AS t2 SET t1.a=t1.a * 2, t2.a=t2.a * 2; +ERROR HY000: Galera replication not supported +COMMIT; +SELECT * FROM ti; +a b id +1 2 4 +SELECT * from tm; +a b +2 NULL +DROP TABLE ti,tm; +CREATE TABLE ti (a INT UNSIGNED, b SMALLINT, id BIGINT NOT NULL, KEY(b), PRIMARY KEY(id)) ENGINE=InnoDB; +CREATE TABLE tm (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=MyISAM; +START TRANSACTION; +INSERT INTO tm SET b=NULL, a=2; +SET sql_mode=only_full_group_by; +INSERT INTO ti VALUES (1,2,4); +SET GLOBAL wsrep_max_ws_rows=2; +ERROR HY000: WSREP transaction is already active +set GLOBAL wsrep_max_ws_size=2047; +ERROR HY000: WSREP transaction is already active +COMMIT; +SELECT * FROM ti; +a b id +1 2 4 +SELECT * from tm; +a b +2 NULL +DROP TABLE ti,tm; +CREATE TABLE ti (a INT UNSIGNED, b SMALLINT, id BIGINT NOT NULL, KEY(b), PRIMARY KEY(id)) ENGINE=InnoDB; +CREATE TABLE tm (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=InnoDB; +SET GLOBAL wsrep_max_ws_rows=2; +START TRANSACTION; +INSERT INTO tm SET b=NULL, a=2; +INSERT INTO ti VALUES (1,2,4); +UPDATE tm AS t1, ti AS t2 SET t1.a=t1.a * 2, t2.a=t2.a * 2; +ERROR HY000: wsrep_max_ws_rows exceeded +UPDATE tm AS t1, ti AS t2 SET t1.a=t1.a * 2, t2.a=t2.a * 2; +COMMIT; +SELECT * FROM ti; +a b id +SELECT * from tm; +a b +DROP TABLE ti,tm; +CREATE TABLE ti (a INT UNSIGNED, b SMALLINT, id BIGINT NOT NULL, KEY(b), PRIMARY KEY(id)) ENGINE=InnoDB; +CREATE TABLE tm (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=MyISAM; +SET GLOBAL wsrep_max_ws_rows=0; +START TRANSACTION; +INSERT INTO tm SET b=NULL, a=2; +INSERT INTO ti VALUES (1,2,4); +UPDATE tm AS t1, ti AS t2 SET t1.a=t1.a * 2, t2.a=t2.a * 2; +UPDATE tm AS t1, ti AS t2 SET t1.a=t1.a * 2, t2.a=t2.a * 2; +COMMIT; +SELECT * FROM ti; +a b id +4 2 4 +SELECT * from tm; +a b +8 NULL +DROP TABLE ti,tm; +CREATE TABLE t1 (col INT); +INSERT INTO t1 VALUES (0xF4AB); +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=mrg_myisam UNION=(t1) insert_method=FIRST; +SET GLOBAL wsrep_max_ws_rows=1; +DROP TABLES t1; +INSERT INTO t1 VALUES (6373); +CREATE TABLE t2 ENGINE=heap SELECT * FROM t1; +DROP TABLE t1, t2; +SET default_storage_engine="HEAP"; +CREATE TABLE t1 (f1 BIGINT); +SET GLOBAL wsrep_max_ws_rows = 1; +INSERT INTO t1 VALUES (NOW()),(NOW()),(NOW()); +SELECT COUNT(*) AS EXPECT_3 FROM t1; +EXPECT_3 +3 +DROP TABLE t1; +CREATE TABLE t1 (i INT) ENGINE=MyISAM DEFAULT CHARSET=utf8 SELECT 1 as i; +DROP TABLE t1; +SET GLOBAL wsrep_max_ws_rows=0; diff --git a/mysql-test/suite/galera/r/galera_var_max_ws_rows.result b/mysql-test/suite/galera/r/galera_var_max_ws_rows.result index 72d16d196ee57..ff85d417a2361 100644 --- a/mysql-test/suite/galera/r/galera_var_max_ws_rows.result +++ b/mysql-test/suite/galera/r/galera_var_max_ws_rows.result @@ -125,6 +125,7 @@ CREATE TABLE t1(c1 INT)ENGINE = INNODB; SET GLOBAL wsrep_max_ws_rows= DEFAULT; INSERT INTO t1 VALUES(1); INSERT INTO t1 SELECT * FROM t1; +COMMIT; SET GLOBAL wsrep_max_ws_rows= 1; ALTER TABLE t1 CHANGE COLUMN c1 c1 BIGINT; connection node_2; diff --git a/mysql-test/suite/galera/t/galera_max_ws_rows.test b/mysql-test/suite/galera/t/galera_max_ws_rows.test new file mode 100644 index 0000000000000..1243874d71e89 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_max_ws_rows.test @@ -0,0 +1,104 @@ +--source include/galera_cluster.inc + +# +# MDEV-28750 Assertion `trans_safe || !updated || thd->transaction->stmt.modified_non_trans_table' failed in virtual bool multi_update::send_eof() +# +CREATE TABLE ti (a INT UNSIGNED, b SMALLINT, id BIGINT NOT NULL, KEY(b), PRIMARY KEY(id)) ENGINE=InnoDB; +CREATE TABLE tm (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=MyISAM; +SET GLOBAL wsrep_max_ws_rows=2; +START TRANSACTION; +INSERT INTO tm SET b=NULL, a=2; +INSERT INTO ti VALUES (1,2,4); +UPDATE tm AS t1 SET t1.a=t1.a * 2; +UPDATE ti AS t1 SET t1.a=t1.a * 2; +UPDATE tm AS t1 SET t1.a=t1.a * 2; +--error ER_WARNING_NOT_COMPLETE_ROLLBACK +UPDATE ti AS t1 SET t1.a=t1.a * 2; +SHOW WARNINGS; +COMMIT; +SELECT * FROM ti; +SELECT * from tm; +DROP TABLE ti,tm; + +CREATE TABLE ti (a INT UNSIGNED, b SMALLINT, id BIGINT NOT NULL, KEY(b), PRIMARY KEY(id)) ENGINE=InnoDB; +CREATE TABLE tm (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=MyISAM; +SET GLOBAL wsrep_max_ws_rows=2; +START TRANSACTION; +INSERT INTO tm SET b=NULL, a=2; +SET sql_mode=only_full_group_by; +INSERT INTO ti VALUES (1,2,4); +--error ER_GALERA_REPLICATION_NOT_SUPPORTED +UPDATE tm AS t1, ti AS t2 SET t1.a=t1.a * 2, t2.a=t2.a * 2; +--error ER_GALERA_REPLICATION_NOT_SUPPORTED +UPDATE tm AS t1, ti AS t2 SET t1.a=t1.a * 2, t2.a=t2.a * 2; +COMMIT; +SELECT * FROM ti; +SELECT * from tm; +DROP TABLE ti,tm; + +CREATE TABLE ti (a INT UNSIGNED, b SMALLINT, id BIGINT NOT NULL, KEY(b), PRIMARY KEY(id)) ENGINE=InnoDB; +CREATE TABLE tm (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=MyISAM; +START TRANSACTION; +INSERT INTO tm SET b=NULL, a=2; +SET sql_mode=only_full_group_by; +INSERT INTO ti VALUES (1,2,4); +--error ER_WRONG_ARGUMENTS +SET GLOBAL wsrep_max_ws_rows=2; +--error ER_WRONG_ARGUMENTS +set GLOBAL wsrep_max_ws_size=2047; +COMMIT; +SELECT * FROM ti; +SELECT * from tm; +DROP TABLE ti,tm; + +CREATE TABLE ti (a INT UNSIGNED, b SMALLINT, id BIGINT NOT NULL, KEY(b), PRIMARY KEY(id)) ENGINE=InnoDB; +CREATE TABLE tm (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=InnoDB; +SET GLOBAL wsrep_max_ws_rows=2; +START TRANSACTION; +INSERT INTO tm SET b=NULL, a=2; +INSERT INTO ti VALUES (1,2,4); +--error ER_ERROR_DURING_COMMIT +UPDATE tm AS t1, ti AS t2 SET t1.a=t1.a * 2, t2.a=t2.a * 2; +UPDATE tm AS t1, ti AS t2 SET t1.a=t1.a * 2, t2.a=t2.a * 2; +COMMIT; +SELECT * FROM ti; +SELECT * from tm; +DROP TABLE ti,tm; + +CREATE TABLE ti (a INT UNSIGNED, b SMALLINT, id BIGINT NOT NULL, KEY(b), PRIMARY KEY(id)) ENGINE=InnoDB; +CREATE TABLE tm (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=MyISAM; +SET GLOBAL wsrep_max_ws_rows=0; +START TRANSACTION; +INSERT INTO tm SET b=NULL, a=2; +INSERT INTO ti VALUES (1,2,4); +UPDATE tm AS t1, ti AS t2 SET t1.a=t1.a * 2, t2.a=t2.a * 2; +UPDATE tm AS t1, ti AS t2 SET t1.a=t1.a * 2, t2.a=t2.a * 2; +COMMIT; +SELECT * FROM ti; +SELECT * from tm; +DROP TABLE ti,tm; + +# +# MDEV-25548 Assertion `transactional_table || !changed || thd->transaction.stmt.modified_non_trans_table' failed. +# +CREATE TABLE t1 (col INT); +INSERT INTO t1 VALUES (0xF4AB); +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=mrg_myisam UNION=(t1) insert_method=FIRST; +SET GLOBAL wsrep_max_ws_rows=1; +DROP TABLES t1; +INSERT INTO t1 VALUES (6373); +CREATE TABLE t2 ENGINE=heap SELECT * FROM t1; + +DROP TABLE t1, t2; + +SET default_storage_engine="HEAP"; +CREATE TABLE t1 (f1 BIGINT); +SET GLOBAL wsrep_max_ws_rows = 1; +INSERT INTO t1 VALUES (NOW()),(NOW()),(NOW()); +SELECT COUNT(*) AS EXPECT_3 FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 (i INT) ENGINE=MyISAM DEFAULT CHARSET=utf8 SELECT 1 as i; +DROP TABLE t1; + +SET GLOBAL wsrep_max_ws_rows=0; diff --git a/mysql-test/suite/galera/t/galera_var_max_ws_rows.test b/mysql-test/suite/galera/t/galera_var_max_ws_rows.test index ab6a3390c068e..22234cca469be 100644 --- a/mysql-test/suite/galera/t/galera_var_max_ws_rows.test +++ b/mysql-test/suite/galera/t/galera_var_max_ws_rows.test @@ -159,6 +159,7 @@ CREATE TABLE t1(c1 INT)ENGINE = INNODB; SET GLOBAL wsrep_max_ws_rows= DEFAULT; INSERT INTO t1 VALUES(1); INSERT INTO t1 SELECT * FROM t1; +COMMIT; SET GLOBAL wsrep_max_ws_rows= 1; ALTER TABLE t1 CHANGE COLUMN c1 c1 BIGINT; diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 11a27fc190846..7a18cc640ee5c 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -46,6 +46,11 @@ #include "sql_insert.h" // For vers_insert_history_row() that may be // needed for System Versioning. +#ifdef WITH_WSREP +#include "wsrep_mysqld.h" // wsrep_max_ws_rows, wsrep_max_ws_size +#include "wsrep_binlog.h" // WSREP_MAX_WS_SIZE +#endif + /** True if the table's input and output record buffers are comparable using compare_record(TABLE*). @@ -2029,6 +2034,36 @@ bool mysql_multi_update(THD *thd, TABLE_LIST *table_list, List *fields, if (select_lex->vers_setup_conds(thd, table_list)) DBUG_RETURN(1); +#ifdef WITH_WSREP + if (WSREP(thd)) + { + bool transactional= false; + bool non_trans= false; + for (TABLE_LIST *tablel= table_list; tablel; tablel= tablel->next_global) + { + TABLE *table= tablel->table; + if (table->file->has_transactions_and_rollback()) + transactional= true; + else + non_trans= true; + } + /* In multi-table update Galera does not support update to both + transactional and non-transactional engines if write-set + size is limited. */ + bool limited = (wsrep_max_ws_rows || wsrep_max_ws_size != WSREP_MAX_WS_SIZE); + if (transactional && non_trans && limited) + { + my_error(ER_GALERA_REPLICATION_NOT_SUPPORTED, MYF(0)); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_GALERA_REPLICATION_NOT_SUPPORTED, + "Galera does not support multi-table update", + " to both transactional and non-transactional engines" + " if write-set size is limited."); + DBUG_RETURN(1); + } + } +#endif /* WITH_WSREP */ + res= mysql_select(thd, table_list, total_list, conds, select_lex->order_list.elements, diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index e058d5ffca87e..9341b20410f28 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -6215,7 +6215,7 @@ static Sys_var_charptr Sys_wsrep_start_position ( CMD_LINE(REQUIRED_ARG), DEFAULT(WSREP_START_POSITION_ZERO), NO_MUTEX_GUARD, NOT_IN_BINLOG, - ON_CHECK(wsrep_start_position_check), + ON_CHECK(wsrep_start_position_check), ON_UPDATE(wsrep_start_position_update)); static Sys_var_ulong Sys_wsrep_max_ws_size ( @@ -6228,7 +6228,9 @@ static Sys_var_ulong Sys_wsrep_max_ws_size ( static Sys_var_ulong Sys_wsrep_max_ws_rows ( "wsrep_max_ws_rows", "Max number of rows in write set", GLOBAL_VAR(wsrep_max_ws_rows), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(0, 1048576), DEFAULT(0), BLOCK_SIZE(1)); + VALID_RANGE(0, 1048576), DEFAULT(0), + BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(wsrep_max_ws_rows_check), ON_UPDATE(0)); static Sys_var_charptr Sys_wsrep_notify_cmd( "wsrep_notify_cmd", "", diff --git a/sql/wsrep_var.cc b/sql/wsrep_var.cc index feee88fdd4273..98c000add0ade 100644 --- a/sql/wsrep_var.cc +++ b/sql/wsrep_var.cc @@ -1,4 +1,5 @@ /* Copyright 2008-2025 Codership Oy + Copyright 2025-2026 MariaDB plc This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1005,6 +1006,11 @@ bool wsrep_max_ws_size_check(sys_var *self, THD* thd, set_var* var) my_message(ER_WRONG_ARGUMENTS, "WSREP (galera) not started", MYF(0)); return true; } + if (thd->wsrep_trx().active()) + { + my_message(ER_WRONG_ARGUMENTS, "WSREP transaction is active", MYF(0)); + return true; + } return false; } @@ -1231,3 +1237,20 @@ bool wsrep_slave_threads_check (sys_var *self, THD* thd, set_var* var) return false; } + +bool wsrep_max_ws_rows_check(sys_var *self, THD* thd, set_var* var) +{ + unsigned long long max_rows= (unsigned long long)var->save_result.ulonglong_value; + + // Default 0 is always allowed + if (max_rows == 0) + return false; + + // Note that we allow changing this even when WSREP is not on + if (thd->wsrep_trx().active()) + { + my_message(ER_WRONG_ARGUMENTS, "WSREP transaction is active", MYF(0)); + return true; + } + return false; +} diff --git a/sql/wsrep_var.h b/sql/wsrep_var.h index 942c64ceff5e8..eed60645bf66e 100644 --- a/sql/wsrep_var.h +++ b/sql/wsrep_var.h @@ -1,4 +1,5 @@ /* Copyright (C) 2013-2025 Codership Oy + Copyright (C) 2025-2026 MariaDB plc This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -101,6 +102,7 @@ extern bool wsrep_trx_fragment_unit_update UPDATE_ARGS; extern bool wsrep_max_ws_size_check CHECK_ARGS; extern bool wsrep_max_ws_size_update UPDATE_ARGS; +extern bool wsrep_max_ws_rows_check CHECK_ARGS; extern bool wsrep_reject_queries_update UPDATE_ARGS;