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;