Details
-
Bug
-
Status: Open (View Workflow)
-
Major
-
Resolution: Unresolved
-
None
-
None
-
Unexpected results
-
Description
Summary
In MariaDB primary/replica replication, a stored function can write to a
nontransactional MyISAM table, then execute ROLLBACK TO SAVEPOINT before
returning to the outer transaction. On the primary, the MyISAM row remains
visible because the table is nontransactional. With binlog_format=ROW and
binlog_format=MIXED, the replica reaches the primary binary-log position
but the MyISAM row is missing on the replica.
The same SQL schedule reproduces on MariaDB 10.3, 10.6, and 12.2.2. The same
test schedule did not reproduce on MySQL 5.6, 5.7, or 8.0.36 in local controls.
Environment
- Product: MariaDB Server
- Observed versions:
- MariaDB 10.3, primary/replica
- MariaDB 10.6, primary/replica
- MariaDB 12.2.2, primary/replica
- Topology: one primary and one replica
- Replication: asynchronous file/position replication
- Binary logging modes showing the mismatch:
- binlog_format=ROW
- binlog_format=MIXED
- Storage engines:
- InnoDB for ordinary transactional tables
- MyISAM for the nontransactional side-effect table
Steps to reproduce
Set up an ordinary MariaDB primary/replica pair using file/position replication.
Run the following SQL on an empty primary database and wait until the replica
has caught up after the DDL.
CREATE TABLE base_t ( |
id BIGINT NOT NULL PRIMARY KEY, |
code INT NOT NULL UNIQUE, |
payload INT NOT NULL |
) ENGINE=InnoDB;
|
|
|
CREATE TABLE transactional_side ( |
id INT PRIMARY KEY, |
payload INT NOT NULL |
) ENGINE=InnoDB;
|
|
|
CREATE TABLE nontransactional_side ( |
id INT PRIMARY KEY, |
payload INT NOT NULL |
) ENGINE=MyISAM;
|
|
|
CREATE FUNCTION write_sides() RETURNS INT |
DETERMINISTIC MODIFIES SQL DATA
|
BEGIN
|
SAVEPOINT routine_savepoint;
|
INSERT INTO transactional_side VALUES (1, 50); |
INSERT INTO nontransactional_side VALUES (1, 60); |
ROLLBACK TO SAVEPOINT routine_savepoint; |
RETURN 0; |
END; |
Use row-based binary logging for the first reproduction:
SET GLOBAL binlog_format='ROW'; |
Then run this transaction on the primary:
BEGIN; |
SELECT write_sides(); |
INSERT INTO base_t(id, code, payload) VALUES (1, 1, 20); |
SAVEPOINT before_update;
|
UPDATE base_t SET payload = payload + 1 WHERE code = 1; |
ROLLBACK TO SAVEPOINT before_update; |
COMMIT; |
After the replica reaches the corresponding primary binary-log position,
inspect all three tables on both endpoints:
SELECT * FROM base_t ORDER BY id; |
SELECT * FROM transactional_side ORDER BY id; |
SELECT * FROM nontransactional_side ORDER BY id; |
SHOW SLAVE STATUS\G
|
Repeat the same schedule with binlog_format=MIXED.
Expected result
The replica should reflect the committed persistent state of the primary.
The InnoDB write inside the stored function is rolled back by
ROLLBACK TO SAVEPOINT routine_savepoint, so transactional_side should be
empty on both endpoints.
The MyISAM write is nontransactional, so it remains visible on the primary even
after the savepoint rollback. Since the primary committed and the replica
reached the corresponding binary-log position without a SQL-thread error, the
same MyISAM row should be visible on the replica:
base_t:
|
(1, 1, 20)
|
|
|
transactional_side:
|
empty
|
|
|
nontransactional_side:
|
(1, 60)
|
Actual result
The ordinary InnoDB table converges and the rolled-back transactional side
effect is absent on both endpoints, but the nontransactional MyISAM row is
present only on the primary.
Observed with binlog_format=ROW:
primary.base_t:
|
(1, 1, 20)
|
|
|
replica.base_t:
|
(1, 1, 20)
|
|
|
primary.transactional_side:
|
empty
|
|
|
replica.transactional_side:
|
empty
|
|
|
primary.nontransactional_side:
|
(1, 60)
|
|
|
replica.nontransactional_side:
|
empty
|
Observed with binlog_format=MIXED:
primary.nontransactional_side:
|
(1, 60)
|
|
|
replica.nontransactional_side:
|
empty
|
The replica reached the primary binary-log position and the replica SQL thread
did not report a SQL error in these runs.
Relevant controls
The following controls were run with the same primary/replica setup and the
same comparison:
- MyISAM write without the stored function/savepoint shape: both endpoints
matched. - Stored function plus MyISAM write, without the savepoint rollback inside the
function: both endpoints matched. - Savepoint rollback plus stored function, without the MyISAM table: both
endpoints matched. - The full three-part schedule with binlog_format=STATEMENT matched in the
sampled runs, because the replica re-executed the stored function and created
the MyISAM row locally.
The mismatch therefore requires the combination of:
stored function
|
+ savepoint rollback inside the function
|
+ nontransactional MyISAM side effect inside the rolled-back region
|
+ ROW or MIXED replication
|
Repetition and scope
The minimized schedule reproduced the mismatch on every tested MariaDB version:
MariaDB 10.3, ROW/MIXED: reproduced
|
MariaDB 10.6, ROW/MIXED: reproduced
|
MariaDB 12.2.2, ROW/MIXED: reproduced
|
The same minimized schedule did not reproduce in local MySQL-family controls:
MySQL 5.6, ROW/MIXED: matched
|
MySQL 5.7, ROW/MIXED: matched
|
MySQL 8.0.36, ROW/MIXED: matched
|
The broader generated search also found the same mismatch independently in a
different sampled schema on MariaDB 10.6 and MariaDB 12.2.2.
Nearby issues checked
The behavior is not a replica-local trigger or replica-local constraint case;
the primary and replica have the same schema, and no replica-local SQL object is
installed.
The behavior is also not a simple MyISAM rollback surprise on one server: the
primary keeps the MyISAM row, as expected for a nontransactional table. The
mismatch appears after ROW/MIXED replication applies the committed transaction.
Could the MariaDB ROW/MIXED logging or apply path for stored-function side
effects inside a savepoint rollback be checked? In particular, should the
nontransactional table row that remains visible on the primary after
ROLLBACK TO SAVEPOINT also be represented on the replica after the
transaction commits?